././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581772733.9114225 qutebrowser-1.10.1/0000755000175000017510000000000000000000000015264 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1570652766.0 qutebrowser-1.10.1/LICENSE0000644000175000017510000010451300000000000016275 0ustar00florianflorian00000000000000 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 . ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1579082658.0 qutebrowser-1.10.1/MANIFEST.in0000644000175000017510000000226700000000000017031 0ustar00florianflorian00000000000000recursive-include qutebrowser *.py recursive-include qutebrowser/img *.svg *.png recursive-include qutebrowser/javascript *.js graft qutebrowser/html graft qutebrowser/3rdparty graft icons graft doc/img graft misc/apparmor graft misc/userscripts graft misc/requirements recursive-include scripts *.py *.sh *.js include qutebrowser/utils/testfile include qutebrowser/git-commit-id include LICENSE doc/* README.asciidoc include misc/org.qutebrowser.qutebrowser.desktop include misc/org.qutebrowser.qutebrowser.appdata.xml include misc/Makefile include requirements.txt include tox.ini include qutebrowser.py include misc/cheatsheet.svg include qutebrowser/config/configdata.yml prune www prune scripts/dev prune scripts/testbrowser/cpp prune .github exclude scripts/asciidoc2html.py recursive-exclude doc *.asciidoc include doc/qutebrowser.1.asciidoc include doc/changelog.asciidoc prune tests prune qutebrowser/3rdparty exclude pytest.ini exclude mypy.ini exclude qutebrowser/javascript/.eslintrc.yaml exclude qutebrowser/javascript/.eslintignore exclude doc/help exclude .* exclude misc/qutebrowser.spec exclude misc/qutebrowser.rcc prune doc/extapi prune misc/nsis global-exclude __pycache__ *.pyc *.pyo ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581772733.9114225 qutebrowser-1.10.1/PKG-INFO0000644000175000017510000004070400000000000016366 0ustar00florianflorian00000000000000Metadata-Version: 2.1 Name: qutebrowser Version: 1.10.1 Summary: A keyboard-driven, vim-like browser based on PyQt5. Home-page: https://www.qutebrowser.org/ Author: Florian Bruhin Author-email: mail@qutebrowser.org License: GPL Description: // If you are reading this in plaintext or on PyPi: // // A rendered version is available at: // https://github.com/qutebrowser/qutebrowser/blob/master/README.asciidoc qutebrowser =========== // QUTE_WEB_HIDE image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.* image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/qutebrowser/qutebrowser"] image:https://ci.appveyor.com/api/projects/status/5pyauww2k68bbow2/branch/master?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/qutebrowser/qutebrowser"] image:https://codecov.io/github/qutebrowser/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/qutebrowser/qutebrowser?branch=master"] link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/faq.asciidoc[FAQ] | https://www.qutebrowser.org/doc/contributing.html[contributing] | link:https://github.com/qutebrowser/qutebrowser/releases[releases] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc[installing] // QUTE_WEB_HIDE_END qutebrowser is a keyboard-focused browser with a minimal GUI. It's based on Python and PyQt5 and free software, licensed under the GPL. It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl. // QUTE_WEB_HIDE **qutebrowser's primary maintainer, The-Compiler, is currently working part-time on qutebrowser, funded by donations.** To sustain this for a long time, your help is needed! See the https://github.com/sponsors/The-Compiler/[GitHub Sponsors page] for more information. Depending on your sign-up date and how long you keep a certain level, you can get qutebrowser t-shirts, stickers and more! Thanks to the GitHub Sponsors Matching Fund, all donations done via GitHub Sponsors (up to a $5000 total) will be doubled until October 2020. // QUTE_WEB_HIDE_END Screenshots ----------- image:doc/img/main.png["screenshot 1",width=300,link="doc/img/main.png"] image:doc/img/downloads.png["screenshot 2",width=300,link="doc/img/downloads.png"] image:doc/img/completion.png["screenshot 3",width=300,link="doc/img/completion.png"] image:doc/img/hints.png["screenshot 4",width=300,link="doc/img/hints.png"] Downloads --------- See the https://github.com/qutebrowser/qutebrowser/releases[github releases page] for available downloads and the link:doc/install.asciidoc[INSTALL] file for detailed instructions on how to get qutebrowser running on various platforms. Documentation ------------- In addition to the topics mentioned in this README, the following documents are available: * https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png[Key binding cheatsheet]: + image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png"] * link:doc/quickstart.asciidoc[Quick start guide] * https://www.shortcutfoo.com/app/dojos/qutebrowser[Free training course] to remember those key bindings * link:doc/faq.asciidoc[Frequently asked questions] * link:doc/help/configuring.asciidoc[Configuring qutebrowser] * link:doc/contributing.asciidoc[Contributing to qutebrowser] * link:doc/install.asciidoc[Installing qutebrowser] * link:doc/changelog.asciidoc[Change Log] * link:doc/stacktrace.asciidoc[Reporting segfaults] * link:doc/userscripts.asciidoc[How to write userscripts] Getting help ------------ You can get help in the IRC channel irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on https://freenode.net/[Freenode] (https://webchat.freenode.net/?channels=#qutebrowser[webchat]), or by writing a message to the https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. There's also an https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist] at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also get sent to the general qutebrowser@ list). If you're a reddit user, there's a https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] subreddit there. Contributions / Bugs -------------------- You want to contribute to qutebrowser? Awesome! Please read link:doc/contributing.asciidoc[the contribution guidelines] for details and useful hints. If you found a bug or have a feature request, you can report it in several ways: * Use the built-in `:report` command or the automatic crash dialog. * Open an issue in the Github issue tracker. * Write a mail to the https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. For security bugs, please contact me directly at mail@qutebrowser.org, GPG ID https://www.the-compiler.org/pubkey.asc[0x916eb0c8fd55a072]. Requirements ------------ The following software and libraries are required to run qutebrowser: * https://www.python.org/[Python] 3.5.2 or newer (3.6 recommended) * https://www.qt.io/[Qt] 5.7.1 or newer (5.14 recommended; support for < 5.9 will be dropped soon) with the following modules: - QtCore / qtbase - QtQuick (part of qtbase in some distributions) - QtSQL (part of qtbase in some distributions) - QtOpenGL - QtWebEngine, or - alternatively QtWebKit - only the link:https://github.com/qtwebkit/qtwebkit/wiki[updated fork] (5.212) is supported. **Note: The latest QtWebKit release is based on old WebKit revision with known unpatched vulnerabilities. Please use it carefully and avoid visiting untrusted websites and using it for transmission of sensitive data.** * https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer (5.14 recommended, support for < 5.9 will be dropped soon) for Python 3 * https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools] * https://fdik.org/pyPEG/[pyPEG2] * http://jinja.pocoo.org/[jinja2] * http://pygments.org/[pygments] * https://github.com/yaml/pyyaml[PyYAML] * https://www.attrs.org/[attrs] The following libraries are optional: * http://cthedot.de/cssutils/[cssutils] (for an improved `:download --mhtml` with QtWebKit). * On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log output. * http://asciidoc.org/[asciidoc] to generate the documentation for the `:help` command, when using the git repository (rather than a release). See link:doc/install.asciidoc[the documentation] for directions on how to install qutebrowser and its dependencies. Donating -------- **qutebrowser's primary maintainer, The-Compiler, is currently working part-time on qutebrowser, funded by donations.** To sustain this for a long time, your help is needed! See the https://github.com/sponsors/The-Compiler/[GitHub Sponsors page] for more information. Depending on your sign-up date and how long you keep a certain level, you can get qutebrowser t-shirts, stickers and more! Thanks to the GitHub Sponsors Matching Fund, all donations done via GitHub Sponsors (up to a $5000 total) will be doubled until October 2020! Alternatively, the following donation methods are available -- note that eligibility for swag (shirts/stickers/etc.) is handled on a case-by-case basis for those, please mailto:mail@qutebrowser.org[get in touch] for details. * SEPA bank transfer inside Europe (no fee): - Account holder: Florian Bruhin - Country: Switzerland - IBAN (EUR): CH13 0900 0000 9160 4094 6 - IBAN (other): CH80 0900 0000 8711 8587 3 - Bank: PostFinance AG, Mingerstrasse 20, 3030 Bern, Switzerland (BIC: POFICHBEXXX) - If you need any other information: Contact me at mail@qutebrowser.org. * PayPal: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=me%40the-compiler.org&item_name=qutebrowser¤cy_code=CHF&source=url[CHF], https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=me%40the-compiler.org&item_name=qutebrowser¤cy_code=EUR&source=url[EUR], https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=me%40the-compiler.org&item_name=qutebrowser¤cy_code=USD&source=url[USD] * Bitcoin: link:bitcoin:1PMzbcetAHfpxoXww8Bj5XqquHtVvMjJtE[1PMzbcetAHfpxoXww8Bj5XqquHtVvMjJtE] Sponsors -------- Thanks a lot to https://www.macstadium.com/[MacStadium] for supporting qutebrowser with a free hosted Mac Mini via their https://www.macstadium.com/opensource[Open Source Project]. (They don't require including this here - I've just been very happy with their offer, and without them, no macOS releases or tests would exist) Thanks to the https://www.hsr.ch/[HSR Hochschule für Technik Rapperswil], which made it possible to work on qutebrowser extensions as a student research project. image:doc/img/sponsors/macstadium.png["powered by MacStadium",width=200,link="https://www.macstadium.com/"] image:doc/img/sponsors/hsr.png["HSR Hochschule für Technik Rapperswil",link="https://www.hsr.ch/"] Authors ------- qutebrowser's primary author is Florian Bruhin (The Compiler), but qutebrowser wouldn't be what it is without the help of https://github.com/qutebrowser/qutebrowser/graphs/contributors[hundreds of contributors]! Additionally, the following people have contributed graphics: * Jad/link:https://yelostudio.com[yelo] (new icon) * WOFall (original icon) * regines (key binding cheatsheet) Also, thanks to everyone who contributed to one of qutebrowser's link:doc/backers.asciidoc[crowdfunding campaigns]! Similar projects ---------------- Many projects with a similar goal as qutebrowser exist. Most of them were inspirations for qutebrowser in some way, thanks for that! Active ~~~~~~ * https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2) * https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2) * https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2) * https://github.com/next-browser/next/[next] (Lisp, Emacs-like but also offers Vim bindings, various backends - note there was a http://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly) * https://github.com/parkouss/webmacs/[webmacs] (Python, Emacs-like with QtWebEngine) * Chrome/Chromium addons: https://vimium.github.io/[Vimium], * Firefox addons (based on WebExtensions): https://github.com/tridactyl/tridactyl[Tridactyl], https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental), https://github.com/ueokande/vim-vixen[Vim Vixen], https://github.com/amedama41/vvimpulation[VVimpulation] * Addons for Firefox and Chrome: https://github.com/brookhong/Surfingkeys[Surfingkeys], https://github.com/lusakasa/saka-key[Saka Key], https://krabby.netlify.com/[Krabby], https://lydell.github.io/LinkHints/[Link Hints] (hinting only) * Addons for Safari: https://televator.net/vimari/[Vimari] Inactive ~~~~~~~~ * https://bitbucket.org/portix/dwb[dwb] (C, GTK+ with WebKit1, https://bitbucket.org/portix/dwb/pull-requests/22/several-cleanups-to-increase-portability/diff[unmaintained] - main inspiration for qutebrowser) * https://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with WebKit1) * https://wiki.archlinux.org/index.php?title=Jumanji[jumanji] (C, GTK+ with WebKit1, original site is gone but the Arch Linux wiki has some data) * http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko) * https://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2) * https://github.com/conformal/xombrero[xombrero] (C, GTK+ with WebKit1) * https://github.com/linkdd/cream-browser[Cream Browser] (C, GTK+ with WebKit1) * Firefox addons (not based on WebExtensions or no recent activity): http://www.vimperator.org/[Vimperator], http://bug.5digits.org/pentadactyl/index[Pentadactyl], https://github.com/akhodakivskiy/VimFx[VimFx], https://github.com/shinglyu/QuantumVim[QuantumVim] * Chrome/Chromium addons: https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome], https://github.com/jinzhu/vrome[Vrome], https://github.com/lusakasa/saka-key[Saka Key], https://github.com/1995eaton/chromium-vim[cVim], https://glee.github.io/[GleeBox] License ------- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, 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 . pdf.js ------ qutebrowser optionally uses https://github.com/mozilla/pdf.js/[pdf.js] to display PDF files in the browser. Windows releases come with a bundled pdf.js. pdf.js is distributed under the terms of the Apache License. You can find a copy of the license in `qutebrowser/3rdparty/pdfjs/LICENSE` (in the Windows release or after running `scripts/dev/update_3rdparty.py`), or online https://www.apache.org/licenses/LICENSE-2.0.html[here]. Keywords: pyqt browser web qt webkit qtwebkit qtwebengine Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: X11 Applications :: Qt Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) Classifier: Natural Language :: English Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: MacOS Classifier: Operating System :: POSIX :: BSD Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Topic :: Internet Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: Browsers Requires-Python: >=3.5 Description-Content-Type: text/plain ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/README.asciidoc0000644000175000017510000003213000000000000017720 0ustar00florianflorian00000000000000// If you are reading this in plaintext or on PyPi: // // A rendered version is available at: // https://github.com/qutebrowser/qutebrowser/blob/master/README.asciidoc qutebrowser =========== // QUTE_WEB_HIDE image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.* image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/qutebrowser/qutebrowser"] image:https://ci.appveyor.com/api/projects/status/5pyauww2k68bbow2/branch/master?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/qutebrowser/qutebrowser"] image:https://codecov.io/github/qutebrowser/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/qutebrowser/qutebrowser?branch=master"] link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/faq.asciidoc[FAQ] | https://www.qutebrowser.org/doc/contributing.html[contributing] | link:https://github.com/qutebrowser/qutebrowser/releases[releases] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc[installing] // QUTE_WEB_HIDE_END qutebrowser is a keyboard-focused browser with a minimal GUI. It's based on Python and PyQt5 and free software, licensed under the GPL. It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl. // QUTE_WEB_HIDE **qutebrowser's primary maintainer, The-Compiler, is currently working part-time on qutebrowser, funded by donations.** To sustain this for a long time, your help is needed! See the https://github.com/sponsors/The-Compiler/[GitHub Sponsors page] for more information. Depending on your sign-up date and how long you keep a certain level, you can get qutebrowser t-shirts, stickers and more! Thanks to the GitHub Sponsors Matching Fund, all donations done via GitHub Sponsors (up to a $5000 total) will be doubled until October 2020. // QUTE_WEB_HIDE_END Screenshots ----------- image:doc/img/main.png["screenshot 1",width=300,link="doc/img/main.png"] image:doc/img/downloads.png["screenshot 2",width=300,link="doc/img/downloads.png"] image:doc/img/completion.png["screenshot 3",width=300,link="doc/img/completion.png"] image:doc/img/hints.png["screenshot 4",width=300,link="doc/img/hints.png"] Downloads --------- See the https://github.com/qutebrowser/qutebrowser/releases[github releases page] for available downloads and the link:doc/install.asciidoc[INSTALL] file for detailed instructions on how to get qutebrowser running on various platforms. Documentation ------------- In addition to the topics mentioned in this README, the following documents are available: * https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png[Key binding cheatsheet]: + image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png"] * link:doc/quickstart.asciidoc[Quick start guide] * https://www.shortcutfoo.com/app/dojos/qutebrowser[Free training course] to remember those key bindings * link:doc/faq.asciidoc[Frequently asked questions] * link:doc/help/configuring.asciidoc[Configuring qutebrowser] * link:doc/contributing.asciidoc[Contributing to qutebrowser] * link:doc/install.asciidoc[Installing qutebrowser] * link:doc/changelog.asciidoc[Change Log] * link:doc/stacktrace.asciidoc[Reporting segfaults] * link:doc/userscripts.asciidoc[How to write userscripts] Getting help ------------ You can get help in the IRC channel irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on https://freenode.net/[Freenode] (https://webchat.freenode.net/?channels=#qutebrowser[webchat]), or by writing a message to the https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. There's also an https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist] at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also get sent to the general qutebrowser@ list). If you're a reddit user, there's a https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] subreddit there. Contributions / Bugs -------------------- You want to contribute to qutebrowser? Awesome! Please read link:doc/contributing.asciidoc[the contribution guidelines] for details and useful hints. If you found a bug or have a feature request, you can report it in several ways: * Use the built-in `:report` command or the automatic crash dialog. * Open an issue in the Github issue tracker. * Write a mail to the https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. For security bugs, please contact me directly at mail@qutebrowser.org, GPG ID https://www.the-compiler.org/pubkey.asc[0x916eb0c8fd55a072]. Requirements ------------ The following software and libraries are required to run qutebrowser: * https://www.python.org/[Python] 3.5.2 or newer (3.6 recommended) * https://www.qt.io/[Qt] 5.7.1 or newer (5.14 recommended; support for < 5.9 will be dropped soon) with the following modules: - QtCore / qtbase - QtQuick (part of qtbase in some distributions) - QtSQL (part of qtbase in some distributions) - QtOpenGL - QtWebEngine, or - alternatively QtWebKit - only the link:https://github.com/qtwebkit/qtwebkit/wiki[updated fork] (5.212) is supported. **Note: The latest QtWebKit release is based on old WebKit revision with known unpatched vulnerabilities. Please use it carefully and avoid visiting untrusted websites and using it for transmission of sensitive data.** * https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer (5.14 recommended, support for < 5.9 will be dropped soon) for Python 3 * https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools] * https://fdik.org/pyPEG/[pyPEG2] * http://jinja.pocoo.org/[jinja2] * http://pygments.org/[pygments] * https://github.com/yaml/pyyaml[PyYAML] * https://www.attrs.org/[attrs] The following libraries are optional: * http://cthedot.de/cssutils/[cssutils] (for an improved `:download --mhtml` with QtWebKit). * On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log output. * http://asciidoc.org/[asciidoc] to generate the documentation for the `:help` command, when using the git repository (rather than a release). See link:doc/install.asciidoc[the documentation] for directions on how to install qutebrowser and its dependencies. Donating -------- **qutebrowser's primary maintainer, The-Compiler, is currently working part-time on qutebrowser, funded by donations.** To sustain this for a long time, your help is needed! See the https://github.com/sponsors/The-Compiler/[GitHub Sponsors page] for more information. Depending on your sign-up date and how long you keep a certain level, you can get qutebrowser t-shirts, stickers and more! Thanks to the GitHub Sponsors Matching Fund, all donations done via GitHub Sponsors (up to a $5000 total) will be doubled until October 2020! Alternatively, the following donation methods are available -- note that eligibility for swag (shirts/stickers/etc.) is handled on a case-by-case basis for those, please mailto:mail@qutebrowser.org[get in touch] for details. * SEPA bank transfer inside Europe (no fee): - Account holder: Florian Bruhin - Country: Switzerland - IBAN (EUR): CH13 0900 0000 9160 4094 6 - IBAN (other): CH80 0900 0000 8711 8587 3 - Bank: PostFinance AG, Mingerstrasse 20, 3030 Bern, Switzerland (BIC: POFICHBEXXX) - If you need any other information: Contact me at mail@qutebrowser.org. * PayPal: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=me%40the-compiler.org&item_name=qutebrowser¤cy_code=CHF&source=url[CHF], https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=me%40the-compiler.org&item_name=qutebrowser¤cy_code=EUR&source=url[EUR], https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=me%40the-compiler.org&item_name=qutebrowser¤cy_code=USD&source=url[USD] * Bitcoin: link:bitcoin:1PMzbcetAHfpxoXww8Bj5XqquHtVvMjJtE[1PMzbcetAHfpxoXww8Bj5XqquHtVvMjJtE] Sponsors -------- Thanks a lot to https://www.macstadium.com/[MacStadium] for supporting qutebrowser with a free hosted Mac Mini via their https://www.macstadium.com/opensource[Open Source Project]. (They don't require including this here - I've just been very happy with their offer, and without them, no macOS releases or tests would exist) Thanks to the https://www.hsr.ch/[HSR Hochschule für Technik Rapperswil], which made it possible to work on qutebrowser extensions as a student research project. image:doc/img/sponsors/macstadium.png["powered by MacStadium",width=200,link="https://www.macstadium.com/"] image:doc/img/sponsors/hsr.png["HSR Hochschule für Technik Rapperswil",link="https://www.hsr.ch/"] Authors ------- qutebrowser's primary author is Florian Bruhin (The Compiler), but qutebrowser wouldn't be what it is without the help of https://github.com/qutebrowser/qutebrowser/graphs/contributors[hundreds of contributors]! Additionally, the following people have contributed graphics: * Jad/link:https://yelostudio.com[yelo] (new icon) * WOFall (original icon) * regines (key binding cheatsheet) Also, thanks to everyone who contributed to one of qutebrowser's link:doc/backers.asciidoc[crowdfunding campaigns]! Similar projects ---------------- Many projects with a similar goal as qutebrowser exist. Most of them were inspirations for qutebrowser in some way, thanks for that! Active ~~~~~~ * https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2) * https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2) * https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2) * https://github.com/next-browser/next/[next] (Lisp, Emacs-like but also offers Vim bindings, various backends - note there was a http://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly) * https://github.com/parkouss/webmacs/[webmacs] (Python, Emacs-like with QtWebEngine) * Chrome/Chromium addons: https://vimium.github.io/[Vimium], * Firefox addons (based on WebExtensions): https://github.com/tridactyl/tridactyl[Tridactyl], https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental), https://github.com/ueokande/vim-vixen[Vim Vixen], https://github.com/amedama41/vvimpulation[VVimpulation] * Addons for Firefox and Chrome: https://github.com/brookhong/Surfingkeys[Surfingkeys], https://github.com/lusakasa/saka-key[Saka Key], https://krabby.netlify.com/[Krabby], https://lydell.github.io/LinkHints/[Link Hints] (hinting only) * Addons for Safari: https://televator.net/vimari/[Vimari] Inactive ~~~~~~~~ * https://bitbucket.org/portix/dwb[dwb] (C, GTK+ with WebKit1, https://bitbucket.org/portix/dwb/pull-requests/22/several-cleanups-to-increase-portability/diff[unmaintained] - main inspiration for qutebrowser) * https://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with WebKit1) * https://wiki.archlinux.org/index.php?title=Jumanji[jumanji] (C, GTK+ with WebKit1, original site is gone but the Arch Linux wiki has some data) * http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko) * https://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2) * https://github.com/conformal/xombrero[xombrero] (C, GTK+ with WebKit1) * https://github.com/linkdd/cream-browser[Cream Browser] (C, GTK+ with WebKit1) * Firefox addons (not based on WebExtensions or no recent activity): http://www.vimperator.org/[Vimperator], http://bug.5digits.org/pentadactyl/index[Pentadactyl], https://github.com/akhodakivskiy/VimFx[VimFx], https://github.com/shinglyu/QuantumVim[QuantumVim] * Chrome/Chromium addons: https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome], https://github.com/jinzhu/vrome[Vrome], https://github.com/lusakasa/saka-key[Saka Key], https://github.com/1995eaton/chromium-vim[cVim], https://glee.github.io/[GleeBox] License ------- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, 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 . pdf.js ------ qutebrowser optionally uses https://github.com/mozilla/pdf.js/[pdf.js] to display PDF files in the browser. Windows releases come with a bundled pdf.js. pdf.js is distributed under the terms of the Apache License. You can find a copy of the license in `qutebrowser/3rdparty/pdfjs/LICENSE` (in the Windows release or after running `scripts/dev/update_3rdparty.py`), or online https://www.apache.org/licenses/LICENSE-2.0.html[here]. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581772733.8680897 qutebrowser-1.10.1/doc/0000755000175000017510000000000000000000000016031 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581772706.0 qutebrowser-1.10.1/doc/changelog.asciidoc0000644000175000017510000040401700000000000021466 0ustar00florianflorian00000000000000Change Log =========== // http://keepachangelog.com/ All notable changes to this project will be documented in this file. This project adheres to http://semver.org/[Semantic Versioning], though minor breaking changes (such as renamed commands) can happen in minor releases. // tags: // `Added` for new features. // `Changed` for changes in existing functionality. // `Deprecated` for once-stable features removed in upcoming releases. // `Removed` for deprecated features removed in this release. // `Fixed` for any bug fixes. // `Security` to invite users to upgrade in case of vulnerabilities. v1.10.1 (2020-02-15) -------------------- Fixed ~~~~~ - Crash when saving data fails during shutdown (which was a regression introduced in v1.9.0). - Error while reading config.py when `fonts.tabs` or `fonts.debug_console` is set to a value including `default_size`. - When a `state` file contains invalid UTF-8 data, a proper error is now displayed. Changed ~~~~~~~ - When the Qt version changes (and also on the first start of v1.10.1 on Qt 5.14), service workers registered by websites are now deleted. This is done as a workaround for QtWebEngine issues causing crashes when visiting pages using service workers (such as Google Mail/Drive). No persistent data should be affected as websites can re-register their service workers, but a (single) backup is kept at `webengine/Service Worker-bak` in qutebrowser's data directory. - Better output on stdout when config errors occur. - The `mkvenv.py` now ensures the latest versions of `setuptools` and `wheel` are installed in the virtual environment, which should speed up installation and fix install issues. - The default for `colors.statusbar.command.private.bg` has been changed to a slightly different gray, as a workaround for a Qt issue where the cursor was invisible in that case. v1.10.0 (2020-02-02) -------------------- Added ~~~~~ - New `colors.webpage.prefers_color_scheme_dark` setting which allows forcing `prefers-color-scheme: dark` colors for websites (QtWebEngine with Qt 5.14 or newer). - New `fonts.default_size` setting which can be used to set a bigger font size for all UI fonts. Changed ~~~~~~~ - The `fonts.monospace` setting has been removed and replaced by `fonts.default_family`. The new `default_family` setting is improved in various ways: * It accepts a list of font families (or a single font family) rather than a comma-separated string. As an example, instead of `fonts.monospace = "Courier, Monaco"`, use `fonts.default_family = ["Courier", "Monaco"]`. * Since a list is now accepted as value, no quoting of font names with spaces is required anymore. As an example, instead of `fonts.monospace = '"xos4 Terminus"'`, use `fonts.default_family = 'xos4 Terminus'`. * It is now empty by default rather than having a long list of font names in the default config. When the value is empty, the system's default monospaced font is used. - If `monospace` is now used in a font value, it's used literally and not replaced anymore. Instead, `default_family` is replaced as explained above. - The default `content.headers.accept_language` value now adds a `;q=0.9` classifier which should make the value sent more in-line with what other browsers do. - The `qute-pass` userscript now has a new `--mode gopass` switch which uses gopass rather than pass. - The `tox -e mkvenv` (or `mkvenv-pypi`) way of installing qutebrowser is now replaced by a `mkvenv.py` script. See the updated link:install{outfilesuffix}#tox[install instructions] for details. - macOS and Windows releases now ship with Qt/QtWebEngine 5.14.1 * Based on Chromium 77.0.3865.129 with security fixes up to Chromium 79.0.3945.117. * Sandboxing is now enabled on Windows. * Monospace fonts are now used when a website requests them on macOS 10.15. * Web notifications are now supported. Fixed ~~~~~ - When quitting qutebrowser, components are now cleaned up differently. This should fix certain (rare) segmentation faults and exceptions when quitting, especially with the new exit scheme introduced in in PyQt5 5.13.1. - Added a workaround for per-domain settings (e.g. a JavaScript whitelist) not being applied in some scenarios with Qt 5.13 and above. - Added additional site-specific quirk for WhatsApp Web. - The `qute-pass` userscript now works correctly when a `PASSWORD_STORE_DIR` ending with a trailing slash is given. v1.9.0 (2020-01-08) ------------------- Added ~~~~~ - Initial support for Qt 5.14. - New `content.site_specific_quirks` setting which enables workarounds for websites with broken user agent parsing (enabled by default, see the "Fixed" section for fixed websites). - New `qt.force_platformtheme` setting to force Qt to use a given platform theme. - New `tabs.tooltips` setting which can be used to disable hover tooltips for tabs. - New settings to configure the appearance of context menus: * `fonts.contextmenu` * `colors.contextmenu.menu.bg` * `colors.contextmenu.menu.fg` * `colors.contextmenu.selected.bg` * `colors.contextmenu.selected.fg` Changed ~~~~~~~ - The macOS binaries now require macOS 10.13 High Sierra or newer. Support for macOS 10.12 Sierra has been dropped. - The `content.headers.user_agent` setting now is a format string with the default value resembling the behavior of it being set to null before. This slightly changes the sent user agent for QtWebKit: Instead of mentioning qutebrowser and its version it now mentions the Qt version. - The `qute-pass` userscript now has a new `--extra-url-suffixes` (`-s`) argument which passes extra URL suffixes to the tldextract library. - A stack is now used for `:tab-focus last` rather than just saving one tab. Additionally, `:tab-focus` now understands `stack-prev` and `stack-next` arguments to traverse that stack. - `:hint` now has a new `right-click` target which allows right-clicking elements via hints. - The Terminus font has been removed from the default monospace fonts since it caused trouble with HighDPI setups. To get it back, add either `"xos4 Terminus"` or `Terminus` (depending on fontconfig version) to the beginning of the `fonts.monospace` setting. - As a workaround for a Qt bug causing a segfault, desktop sharing is now automatically rejected on Qt versions before 5.13.2. Note that screen sharing still won't work on Linux before Qt 5.14. - Comment lines in quickmarks/bookmarks files are now ignored. However, note that qutebrowser will overwrite those files if bookmark/quickmark commands are used. - Reopening PDF.js pages from e.g. a session file will now re-download and display those PDFs. - Improved behavior when using `:open-download` in a sandboxed environment (KDE Flatpak). - qutebrowser now enables the new PyQt exit scheme, which should result in things being cleaned up more properly (e.g. cookies being saved even without a timeout) on PyQt 5.13.1 and newer. - The `:spawn` command has a new `-m` / `--output-messages` argument which shows qutebrowser messages based on a command's standard output/error. - Improved insert mode detection for some CodeMirror usages (e.g. in JupyterLab and Jupyter Notebook). - If JavaScript is disabled globally, `file://*` now doesn't automatically have it enabled anymore. Run `:set -u file://* content.javascript.enabled true` to restore the previous behavior. - Settings with URL patterns can now be used to affect the behavior of the QtWebEngine inspector. Note that the underlying URL is `chrome-devtools://*` from Qt 5.11 to Qt 5.13, but `devtools://*` with Qt 5.14. - Improvements when `tabs.tabs_are_windows` is set: * Using `:tab-take` and `:tab-give` now shows an error, as the effect of doing so would be equal to `:tab-clone`. * The `:buffer` completion doesn't show any window sections anymore, only a flat list of tabs. - Improved parsing in some corner cases for the `QtFont` type (used for `fonts.tabs` and `fonts.debug_console`). - Performance improvements for the following areas: * Adding settings with URL patterns * Matching of settings using URL patterns Fixed ~~~~~ - Downloads (e.g. via `:download`) now see the same user agent header as webpages, which fixes cases where overly restrictive servers/WAFs closed the connection before. - `dictcli.py` now works correctly on Windows again. - The logic for `:restart` has been revisited, which should fix issues with relative basedirs. - Remaining issues related to Python 3.8 are now fixed (mostly warnings, especially on QtWebKit). - Workaround for a Qt bug where a page never finishes loading with a non-overridable TLS error (e.g. due to HSTS). - The `qute://configdiff` page now doesn't show built-in settings (e.g. javascript being enabled for `qute://` and `chrome://` pages) anymore. - The `qute-lastpass` userscript now stops prompting for passwords when cancelling the password input. - The tab hover text now shows ampersands (&) correctly. - With QtWebEngine and Qt >= 5.11, the inspector now shows its icons correctly even if loading of images is disabled via the `content.images` setting. - Entering a very long string (over 50k characters) in the completion used to crash, now it shows an error message instead. - Various improvements for URL/searchengine detection: * Strings with a dot but with characters not allowed in a URL (e.g. an underscore) are now not treated as URL anymore. * Strings like "5/8" are now not treated as IP anymore. * URLs with an explicit scheme and a space (%20) are correctly treated as URLs. * Mail addresses are now treated as search terms. * With `url.open_base_url` set, searching for a search engine name now works. * `url.open_base_url = True` together with `url.auto_search = 'never'` is now handled correctly. * Fixed crash when a search engine URL turns out to be invalid. - New "site specific quirks", which work around some broken websites: * WhatsApp Web * Google Accounts * Slack (with older QtWebEngine versions) * Dell.com support pages (with Qt 5.7) * Google Docs (fixes broken IME/compose key) v1.8.3 (2019-12-05) ------------------- Fixed ~~~~~ - Segmentation fault introduced in v1.8.2 when a tab gets closed immediately after it has finished loading (e.g. with certain login flows). v1.8.2 (2019-11-22) ------------------- Changed ~~~~~~~ - Windows/macOS releases now ship with Qt 5.12.6. This includes security fixes up to Chromium 77.0.3865.120 plus a security fix for CVE-2019-13720 from Chromium 78. Fixed ~~~~~ - Unbinding keys via `config.bind(key, None)` accidentally worked in v1.7.0 but raises an exception in v1.8.0. It now works again, but is deprecated and shows an error. Note that `:config-py-write` did write such invalid lines before v1.8.0, so existing config files might need adjustments. - The `readability-js` userscript now handles encodings correctly (which it didn't before for some websites). - can now be used to paste text starting with a hyphen. - Following hints via the number keypad now works properly again. - Errors while reading the state file are now displayed instead of causing a crash. - Crash when using `:debug-log-level` without a console attached. - Downloads are now hidden properly when the browser is in fullscreen mode. - Crash when setting `colors.webpage.bg` to an empty value with QtWebKit. - Crash when the history database file is not a proper sqlite database. - Workaround for missing/broken error pages on Debian. - A deprecation warning (caused by pywin32) about the imp module on Windows is now hidden. v1.8.1 (2019-09-27) ------------------- Changed ~~~~~~~ - No code changes - this release only repackages the Windows/macOS releases due to issues with the v1.8.0 release. - Updated dependencies for Windows/macOS releases: * macOS and Windows releases now ship with Qt/QtWebEngine 5.12.5. Those are based on Chromium 69.0.3497.128 with security fixes up to Chromium 76.0.3809.87. * Qt 5.13 couldn't be used yet due to various bugs in Qt 5.13.0 and .1. v1.8.0 (2019-09-25) ------------------- Added ~~~~~ - New userscripts: * `readability-js` which uses Mozilla's node.js readability library. * `qute-bitwarden` which integrates the Bitwarden CLI. Changed ~~~~~~~ - The statusbar text for passthrough mode now shows all configured bindings to leave the mode, not only one. - When `:config-source` is used with a relative filename, the file is now searched in the config directory instead of the current working directory. - HTML5 inputs with date/time types now enter insert mode when selected. - `dictcli.py` now shows where dictionaries are installed to and complains when running it as root if doing so would result in a wrong installation path. - The Makefile now can also run `setup.py build` when invoked without a target. - Changes to userscripts: * qute-pass: Don't run `pass` if only a username is requested. * qute-pass: Support private domains like `myrouter.local`. * readability: Improved CSS styling. - Performance improvements in various areas: * Loading config files * Typing without any completion matches * General keyboard handling * Scrolling - `:version` now shows details about the loaded autoconfig.yml/config.py. - Hosts are now additionally looked up including their ports in netrc files. - With Qt 5.10 or newer, qutebrowser now doesn't force software rendering with Nouveau drivers anymore. However, QtWebEngine/Chromium still do so. - The XSS Auditor is now disabled by default (`content.xss_auditing` = `false`). This reflects a similar change in Chromium, see their https://www.chromium.org/developers/design-documents/xss-auditor[XSS Auditor Design Document] for details. Fixed ~~~~~ - `:config-write-py` now correctly writes `config.unbind(...)` lines (instead of `config.bind(..., None)`) when unbinding a default keybinding. - Prevent repeat keyup events for JavaScript when a key is held down. - The Makefile now rebuilds the manpage correctly. - `~/.config/qutebrowser/blocked-hosts` can now also contain /etc/hosts-like lines, not just simple hostnames. - Restored compatibility with Jinja2 2.8 (e.g. used on Debian Stretch or Ubuntu 16.04 LTS). - Fixed implicit type conversion warning with Python 3.8. - The desktop file now sets `StartupWMClass` correctly, so the qutebrowser icon is no longer shown twice in the Gnome dock when pinned. - Bindings involving keys which need the AltGr key now work properly. - Fixed crash (caused by a Qt bug) when typing characters above the Unicode BMP (such as certain emoji or CJK characters). - `dictcli.py` now works properly again. - Shift can now be used while typing hint keystrings, which e.g. allows typing number hints on French keyboards. - With rapid hinting in number mode, backspace now edits the filter text after following a hint. - A certain type of error ("locking protocol") while initializing sqlite now isn't handled as crash anymore. - Crash when showing a permission request in certain scenarios. Removed ~~~~~~~ - At least Python 3.5.2 is now required to run qutebrowser, support for 3.5.0 and 3.5.1 was dropped. v1.7.0 (2019-07-18) ------------------- Added ~~~~~ - New settings: * `colors.tabs.pinned.*` to control colors of pinned tabs. * `hints.leave_on_load` which allows disabling leaving of hint mode when a new page is loaded. * `colors.completion.item.selected.match.fg` which allows configuring the text color for the matching text in the currently selected completion item. * `tabs.undo_stack_size` to limit how many undo entries are kept for closed tabs. - New commands: * `:reverse-selection` (`o` in caret mode) to swap the stationary/moving ends of a selection. - New commandline replacements: * `{url:domain}`, `{url:auth}`, `{url:scheme}`, `{url:username}`, `{url:password}`, `{url:host}`, `{url:port}`, `{url:path}`, `{url:query}` for the respective parts of the current URL. * `{title}` for the current page title. - The `{title}` field in `tabs.title.format`, `tabs.title.format_pinned` and `window.title_format` got renamed to `{current_title}` (mirroring `{current_url}`) in order to not conflict with the new `{title}` commandline replacement. - New `delete` target for `:hint` which removes the hinted element from the DOM. - New `--config-py` commandline argument to use a custom `config.py` file. - Qt 5.13: Support for notifications (shown via system tray). Changed ~~~~~~~ - Updated dependencies for Windows/macOS releases: - PyQt5 5.12.3 / PyQtWebEngine 5.12.1 - Qt 5.12.4, which includes security fixes up to Chromium 74.0.3729.157 - Python 3.7.4 - OpenSSL 1.1.1 - Note: This release includes Qt 5.12.4 instead of Qt 5.13.0 due to https://bugreports.qt.io/browse/QTBUG-76913[QTBUG-76913] causing frequent segfaults with Qt 5.13. After Qt 5.13.1 is released, qutebrowser v1.8.0 will be released with an updated Qt. - Completely revamped Windows installer which allows installing without admin permissions and allows setting qutebrowser as default browser. - The desktop file `qutebrowser.desktop` is now renamed to `org.qutebrowser.qutebrowser.desktop`. - Pinned tabs now always show a favicon (even if the site doesn't provide one) when shrinking. - Setting `downloads.location.directory` now changes the directory displayed in the download prompt even if `downloads.location.remember` is set. - The `yank` command gained a new `inline` argument, which allows to e.g. use `:yank inline [{title}]({url})`. - Duplicate consecutive history entries with the same URL are now ignored. - More detailed error messages when spawning a process failed. - The `content.pdfjs` setting now supports domain patterns. - Improved process status output with `:spawn -o`. - The `colors.tabs.bar.bg` setting is now of type `QssColor` and thus supports gradients. - The `:fullscreen` command now understands a new `--enter` flag which causes it to always enter fullscreen instead of toggling the current state. - `--debug-flag stack` is now needed to show stack traces on renderer process crashes. - `--debug-flag chromium` can be used to easily turn on verbose Chromium logging. - For runtime data (such as the IPC socket), a proper runtime path is now used on BSD; only macOS/Windows continue to use the temporary directory. - PDF.js is now also searched in `/app/share/pdf.js/` (for Flatpak) - Permission prompts can now be answered with `Y` (`:prompt-accept --save yes`) and `N` (`:prompt-accept --save no`) to save the answer as a per-domain setting. - `content.dns_prefetch` is now turned off by default, as it causes crashes inside QtWebEngine. - The (still unofficial) interceptor plugin API now contains `resource_type` for a request and allows redirecting requests. - `:bookmark-remove` now shows a message for consistency with `:bookmark-add`. - Very early segfaults are now also caught by the crash handler. - The appdata XML now contains proper release information and an (empty) OARS content rating. - Improved Linux distribution detection. - Qt 5.13: Request filtering now happens in the UI rather than IO thread. - Qt 5.13: Support for PDFium (Chromium's PDF viewer) is disabled for now so that PDFs can still be downloaded (or shown with PDF.js) properly. - Various performance improvements (e.g. for showing hints or the :open completion). Deprecated ~~~~~~~~~~ - `:yank markdown` got deprecated, as `:yank inline [{title}]({url})` can now be used instead. Fixed ~~~~~ - Various QtWebEngine load signals are now handled differently, which should fix issues with insert mode being left while typing on sites like Google Translate. - Race condition causing a colored statusbar in normal mode when entering/exiting caret mode quickly. - Using `100%` for a hue in a `hsv(...)` config value now corresponds to 359 (rather than 255), matching the fixed behavior in Qt 5.13. - Chaining commands with `;;` used to abort with some failing commands. It now runs the second command no matter whether the first one succeeded or not. - Handling of profiles and private windows (and resulting crashes with Qt 5.12.2). - Fixes for corner-cases when using `:navigate increment/decrement`. - The type for the `colors.hints.match.fg` setting was changed to `QtColor`. Gradients were never supported for this setting, and with this change, values like `rgb(0, 0, 0)` now work as well. - Permission prompts now show a properly normalized URL with QtWebKit. - Crash on start when PyQt was built without SSL support with Qt >= 5.12. - Minor memory leaks. v1.6.3 (2019-06-18) ------------------- Fixed ~~~~~ - Crash when hinting and changing/closing the tab before hints are displayed. - Crash on redirects with Qt 5.13. - Hide bogus `AA_ShareOpenGLContexts` warning with Qt 5.12.4. - Workaround for renderer process crashes with Qt 5.12.4. If you're unable to update, you can remove `~/.cache/qutebrowser` for the same result. v1.6.2 (2019-05-06) ------------------- Changed ~~~~~~~ - Windows/macOS releases now ship with Qt 5.12.3, which includes security fixes up to Chromium 73.0.3683.75. Fixed ~~~~~ - Crash when SQL errors occur while using the completion. - Crash when cancelling a download prompt started in an already closed window. - Crash when many prompts are opened at the same time. - Running without Qt installed now displays a proper error again. - High CPU usage when using the keyhint widget with a low delay. - Crash with Qt >= 5.14 on redirects. v1.6.1 (2019-03-20) ------------------- Changed ~~~~~~~ - Windows/macOS releases now ship with Qt 5.12.2, which includes security fixes up to Chromium 72.0.3626.121 (including CVE-2019-5786 which is known to be exploited in the wild). Fixed ~~~~~ - Crash when using `:config-{dict,list}-{add,remove}` with an invalid setting. - Functionality like hinting on pages with an element with ID `_qutebrowser` (such as qutebrowser.org) on Qt 5.12. - The .desktop file in v1.6.0 was missing the "Actions" key, which is now fixed. - The SVG icon now has a size of 256x256px set to comply with freedesktop standards. - Setting `colors.statusbar.*.bg` to a gradient now has the expected effect of the gradient spanning the entire statusbar. v1.6.0 (2019-02-25) ------------------- Added ~~~~~ - New settings: * `tabs.new_position.stacking` which controls whether new tabs opened from a page should stack on each other or not. * `completion.open_categories` which allows to configure which categories are shown in the `:open` completion, and how they are ordered. * `tabs.pinned.frozen` to allow/deny navigating in pinned tabs. * `hints.selectors` which allows to configure what CSS selectors are used for hints, and also allows adding custom hint groups. * `input.insert_mode.leave_on_load` to turn off leaving insert mode when a new page is loaded. - New config manipulation commands: * `:config-dict-add` and `:config-list-add` to a new element to a dict/list setting. * `:config-dict-remove` and `:config-list-remove` to remove an element from a dict/list setting. - New `:yank markdown` feature which yanks the current URL and title in markdown format. - Support for new QtWebEngine features in Qt 5.12: * Basic support for client certificates. Selecting the certificate to use when there are multiple matching certificates isn't implemented yet. * Support for DNS prefetching (plus new `content.dns_prefetch` setting). Changed ~~~~~~~ - Various changes to the Windows and macOS builds: * Bundling Qt 5.12.1, based on Chromium 69.0.3497.128 with security fixes up to 71.0.3578.94. * Windows: A 32-bit build is available again. * Windows: The builds now bundle the Universal CRT DLLs, causing them to work on earlier versions of Windows 10. * macOS: Support for OS X 10.11 El Capitan was dropped, requiring macOS 10.12 Sierra or newer. * macOS: The IPC socket path used to communicate with existing instances changed due to changes in Qt 5.12. Please make sure to quit qutebrowser before upgrading. - `:q` now closes the current window instead of quitting qutebrowser completely (`:close`), while `:qa` quits (`:quit`). The behavior of `:wq` remains unchanged (`:quit --save`), as closing a window while saving the session doesn't make sense. - Completion highlighting is now done differently (using `QSyntaxHighlighter`), which should fix some highlighting corner-cases. - The `QtColor` config type now also understands colors like `rgb(...)`. - `:yank` now has a `--quiet` option which causes it to not display a message. - The `:open` completion now also shows search engines by default. - The `content.host_blocking.enabled` setting now supports URL patterns, so the adblocker can be disabled on a given page. - Elements with a `tabindex` attribute now also get hints by default. - Various small performance improvements for hints and the completion. - The Wayland check for QtWebEngine is now disabled on Qt >= 5.11.2, as those versions should work without any issues. - The JavaScript `console` object is now available in PAC files. - PAC proxies currently don't work properly on QtWebEngine (and never did), so an error is now shown when trying to configure a PAC proxy. - The metainfo file `qutebrowser.appdata.xml` is now renamed to `org.qutebrowser.qutebrowser.appdata.xml`. - The `qute-pass` userscript now understands domains in gpg filenames in addition to directory names. - The autocompletion for `content.headers.user_agent` got updated to only include the default and Chrome, as setting the UA to Firefox has various bad side-effects. - Combining Qt 5.12 with an older PyQt can lead to issues, so a warning is now shown when starting qutebrowser with that combination. Fixed ~~~~~ - Invalid world IDs now get rejected for `:jseval` and GreaseMonkey scripts. - When websites suggest download filenames with invalid characters, those are now correctly replaced. - Invalid hint length calculation in certain rare cases. - Dragging tabs in the tab bar (which was broken in v1.5.0) - Using Shift-Home in command mode now works properly. - Workaround for a Qt bug which prevented `content.cookies.accept = no-3rdparty` from working properly on some pages like GMail. However, the default for `content.cookies.accept` is still `all` to be in line with what other browsers do. - `:navigate` not incrementing in anchors or queries. - Crash when trying to use a proxy requiring authentication with QtWebKit. - Slashes in search terms are now percent-escaped. - When `scrolling.bar = True` was set in versions before v1.5.0, this now correctly gets migrated to `always` instead of `when-searching`. - Completion highlighting now works again on Qt 5.11.3 and 5.12.1. - The non-standard header `X-Do-Not-Track` is no longer sent. - PAC proxies were never correctly supported with QtWebEngine, but are now explicitly disallowed. - macOS: Context menus for download items now show in the correct macOS style. - Issues with fullscreen handling when exiting a video player. - Various fixes for Qt 5.12 issues: * A javascript error on page load was fixed. * `window.print()` works with Qt 5.12 now. * Fixed handling of duplicate download filenames. * Fixed broken `qute://history` page. * Fixed PDF.js not working properly. * The download button in PDF.js now works (it's not possible to make it work with earlier Qt versions). * Since Greasemonkey scripts modifying the DOM fail when being run at document-start, some known-broken scripts (Iridium, userstyles.org) are now forced to run at document-end. v1.5.2 (2018-10-26) ------------------- Changed ~~~~~~~ - The `content.cookies.accept` setting is now set to `all` instead of `no-3rdparty` by default, as `no-3rdparty` breaks various pages such as GMail. v1.5.1 (2018-10-10) ------------------- Fixed ~~~~~ - Flickering when opening/closing tabs (as soon as more than 10 are open) on some pages. - PDF.js is now bundled again with the macOS/Windows release. - PDF.js is now searched in the correct path (if not installed system-wide) instead of hardcoding `~/.local/share/qutebrowser`. - Improved logging for PDF.js resources which fail to load. - Crash when closing a tab after doing a search. - Tabs appearing when hidden after e.g. closing tabs. v1.5.0 (2018-10-03) ------------------- Added ~~~~~ - Rewritten PDF.js support: * PDF.js support and the `content.pdfjs` setting are now also available with QtWebEngine. * Opening a PDF file now doesn't start a second request anymore. * Opening PDFs on https:// sites now works properly. * New `--pdfjs` flag for `prompt-open-download`, so PDFs can be opened in PDF.js with `` in the download prompt. - New settings: * `content.mouse_lock` to handle HTML5 pointer locking. * `completion.web_history.exclude` which hides a list of URL patterns from the completion. * `qt.process_model` which can be used to change Chromium's process model. * `qt.low_end_device_mode` which turns on Chromium's low-end device mode. This mode uses less RAM, but the expense of performance. * `content.webrtc_ip_handling_policy`, which allows more fine-grained/restrictive control about which IPs are exposed via WebRTC. * `tabs.max_width` which allows to have a more "normal" look for tabs. * `content.mute` which allows to mute pages (or all tabs) by default. - Running qutebrowser with QtWebKit or Qt < 5.9 now shows a warning (only once), as support for those is going to be removed in a future release. - New t[iI][hHu] default bindings (similar to `tsh` etc.) to toggle images. - The qute-pass userscript now has optional OTP support. - When `:spawn --userscript` is called with a count, that count is now passed to userscripts as `$QUTE_COUNT`. Changed ~~~~~~~ - Windows and macOS releases now bundle Python 3.7, PyQt 5.11.3 and Qt 5.11.2. QtWebEngine includes security fixes up to Chromium 68.0.3440.75 and http://code.qt.io/cgit/qt/qtwebengine.git/tree/dist/changes-5.11.2/?h=v5.11.2[various other fixes]. - Various performance improvements when many tabs are opened. - The `content.headers.referer` setting now works on QtWebEngine. - The `:repeat` command now takes a count which is multiplied with the given "times" argument. - The default keybinding to leave passthrough mode was changed from `` to ``, which makes pasting from the clipboard easier in passthrough mode and is also unlikely to conflict with webpage bindings. - The `app_id` is now set to `qutebrowser` for Wayland. - `Command` or `Cmd` can now be used (instead of `Meta`) to map the Command key on macOS. - Using `:set option` now shows the value of the setting (like `:set option?` already did). - The `completion.web_history_max_items` setting got renamed to `completion.web_history.max_items`. - The Makefile shipped with qutebrowser now supports overriding variables `DATADIR` and `MANDIR`. - Regenerating completion history now shows a progress dialog. - The `content.autoplay` setting now supports URL patterns on Qt >= 5.11. - The `content.host_blocking.whitelist` setting now takes a list of URL patterns instead of globs. - In passthrough mode, Ctrl + Mousewheel now also gets passed through to the page instead of zooming. - Editing text in an external editor now simulates a JS "input" event, which improves compatibility with websites reacting via JS to input. - The `qute://settings` page is now properly sorted on Python 3.5. - `:zoom`, `:zoom-in` and `:zoom-out` now have a `--quiet` switch which causes them to not display a message. - The `scrolling.bar` setting now takes three values instead of being a boolean: `always`, `never`, and `when-searching` (which only displays it while a search is active). - '@@' now repeats the last run macro. - The `content.host_blocking.lists` setting now accepts a `file://` URL to a directory, and reads all files in that directory. - The `:tab-give` and `:tab-take` command now have a new flag `--keep` which causes them to keep the old tab around. - `:navigate` now clears the URL query. Fixed ~~~~~ - `qute://` pages now work properly on Qt 5.11.2 - Error when passing a substring with spaces to `:tab-take`. - Greasemonkey scripts which start with an UTF-8 BOM are now handled correctly. - When no documentation has been generated, the plaintext documentation now can be shown for more files such as `qute://help/userscripts.html`. - Crash when doing initial run on Wayland without XWayland. - Crash when trying to load an empty session file. - `:hint` with an invalid `--mode=` value now shows a proper error. - Rare crash on Qt 5.11.2 when clicking on ` {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/html/history.html0000644000175000017510000000274000000000000023204 0ustar00florianflorian00000000000000{% extends "styled.html" %} {% block style %} {{super()}} body { max-width: 1440px; } td.title { word-break: break-all; } td.time { color: #555; text-align: right; white-space: nowrap; } table { margin-bottom: 30px; } .date { color: #555; font-size: 12pt; padding-bottom: 15px; font-weight: bold; text-align: left; } #load { color: #555; font-weight: bold; text-decoration: none; } #eof { color: #aaa; margin-bottom: 30px; text-align: center; width: 100%; } .session-separator { color: #aaa; height: 40px; text-align: center; } {% endblock %} {% block content %}

Browsing history

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/html/license.html0000644000175000017510000011074700000000000023134 0ustar00florianflorian00000000000000 GNU General Public License v3.0 - GNU Project - Free Software Foundation (FSF)

GNU GENERAL PUBLIC LICENSE

Version 3, 29 June 2007

Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>

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.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    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 <http://www.gnu.org/licenses/>.

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:

    <program>  Copyright (C) <year>  <name of author>
    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 <http://www.gnu.org/licenses/>.

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 <http://www.gnu.org/philosophy/why-not-lgpl.html>.

././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/html/log.html0000644000175000017510000000124500000000000022263 0ustar00florianflorian00000000000000{% extends "base.html" %} {% block script %} window.onload=toBottom; function toBottom() { window.scrollTo(0, document.body.scrollHeight); } {% endblock %} {% block style %} body { background-color: black; color: white; font-size: 11px; } table { border: 1px solid #222; border-collapse: collapse; } pre { margin: 2px; } th, td { border: 1px solid #222; padding-left: 5px; padding-right: 5px; } {% endblock %} {% block content %} {{ super() }} {% if content %} {{ content | safe() }}
{% elif content is not none %}

No messages to show.

{% else %}

Log output was disabled.

{% endif %} {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/html/no_pdfjs.html0000644000175000017510000000667200000000000023315 0ustar00florianflorian00000000000000{% extends "base.html" %} {% block style %} {{ super() }} * { margin: 0px 0px; padding: 0px 0px; } body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; -webkit-text-size-adjust: none; color: #333333; background-color: #EEEEEE; font-size: 1.2em; } #error-container { margin-left: 20px; margin-right: 20px; margin-top: 20px; border: 1px solid #CCCCCC; box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.20); border-radius: 5px; background-color: #FFFFFF; padding: 20px 20px; } #header { border-bottom: 1px solid #CCC; } .qutebrowser-broken { display: block; width: 100%; } span.warning { text-weight: bold; color: red; } td { margin-top: 20px; color: #555; } h1, h2 { font-weight: normal; color: #1e89c6; margin-bottom: 10px; } ul { margin-left: 20px; margin-top: 20px; margin-bottom: 20px; } li { margin-top: 10px; margin-bottom: 10px; } {% endblock %} {% block content %}

No pdf.js installation found

Error while opening {{ url }}

qutebrowser can't find a suitable pdf.js installation

It looks like you set content.pdfjs to true but qutebrowser can't find the required files.


Possible fixes

  • Disable content.pdfjs and reload the page. You will need to download the pdf-file and open it with an external tool instead.
  • If you have installed a packaged version of qutebrowser, make sure the required packages for pdf.js are also installed.
    The package is named pdfjs on Archlinux (AUR) and libjs-pdf on Debian.
  • If you have installed a pdf.js package and qutebrowser still can't find it, please send us a report with your system and the package name, so we can add it to the list of supported packages.
  • If you're running a self-built version or the source version, make sure you have pdf.js in qutebrowser/3rdparty/pdfjs. You can use the scripts/dev/update_3rdparty.py script to download the latest version.
  • You can manually download the pdf.js archive here and extract it to {{ pdfjs_dir }}
    Warning: Using this method you are responsible for yourself to keep the installation updated! If a vulnerability is found in pdf.js, neither qutebrowser nor your system's package manager will update your pdf.js installation. Use it at your own risk!

If none of these fixes work for you, please send us a bug report so we can fix the issue.

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/html/pre.html0000644000175000017510000000014600000000000022267 0ustar00florianflorian00000000000000{% extends "base.html" %} {% block content %} {{ super() }}
{{ content }}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/html/settings.html0000644000175000017510000000347100000000000023345 0ustar00florianflorian00000000000000{% extends "base.html" %} {% block script %} var cset = function(option, value) { // FIXME:conf we might want some error handling here? var url = "qute://user:{{csrf_token}}@settings/set" url += "?option=" + encodeURIComponent(option); url += "&value=" + encodeURIComponent(value); var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.send(); } {% endblock %} {% block style %} table { border: 1px solid grey; border-collapse: collapse; } pre { margin: 2px; } th, td { border: 1px solid grey; padding: 0px 5px; } th { background: lightgrey; } th pre { color: grey; text-align: left; } input { width: 98%; } .setting { width: 75%; } .value { width: 25%; text-align: center; } .noscript, .noscript-text { color:red; } .noscript-text { margin-bottom: 5cm; } .option_description { margin: .5ex 0; color: grey; font-size: 80%; font-style: italic; white-space: pre-line; } {% endblock %} {% block content %}

{{ title }}

{% for option in configdata.DATA.values()|sort(attribute='name') if not option.no_autoconfig %} {% endfor %}
Setting Value
{{ option.name }} (Current: {{ confget(option.name) | string |truncate(100) }}) {% if option.description %}

{{ option.description|e }}

{% endif %}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/html/styled.html0000644000175000017510000000142100000000000023002 0ustar00florianflorian00000000000000{% extends "base.html" %} {% block style %} a { text-decoration: none; color: #2562dc } a:hover { text-decoration: underline; } body { background: #fefefe; font-family: sans-serif; margin: 0 auto; max-width: 1280px; padding-left: 20px; padding-right: 20px; } h1 { color: #444; font-weight: normal; } h2 { font-weight: normal; } table { border-collapse: collapse; width: 100%; } tbody tr:nth-child(odd) { background-color: #f8f8f8; } td { max-width: 50%; padding: 2px 5px; text-align: left; } .hostname { color: #858585; font-size: 0.9em; margin-left: 10px; text-decoration: none; } .note { font-size: smaller; color: grey; } .mono { font-family: monospace; } {% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/html/tabs.html0000644000175000017510000000170700000000000022436 0ustar00florianflorian00000000000000{% extends "styled.html" %} {% block style %} {{super()}} h1 { margin-bottom: 10px; } .url a { color: #444; } th { text-align: left; } .qmarks .name { padding-left: 5px; } .empty-msg { background-color: #f8f8f8; color: #444; display: inline-block; text-align: center; width: 100%; } details { margin-top: 20px; } {% endblock %} {% block content %}

Tab list

{% for win_id, tabs in tab_list_by_window.items() %}

Window {{ win_id }}

{% for name, url in tabs %} {% endfor %}
{{name}} {{url}}
{% endfor %}
Raw list {% for win_id, tabs in tab_list_by_window.items() %}{% for name, url in tabs %} {{url}}
{% endfor %} {% endfor %}
{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/html/version.html0000644000175000017510000000225200000000000023166 0ustar00florianflorian00000000000000{% extends "base.html" %} {% block script %} function paste_version() { const xhr = new XMLHttpRequest(); xhr.open("GET", "qute://pastebin-version"); xhr.send(); } {% endblock %} {% block style %} html { margin-left: 10px; } {% endblock %} {% block content %} {{ super() }}

Version info

{{ version }}

Copyright info

{{ copyright }}

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 http://www.gnu.org/licenses/ or open qute://gpl.

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/html/warning-old-qt.html0000644000175000017510000000210500000000000024341 0ustar00florianflorian00000000000000{% extends "styled.html" %} {% block content %}

{{ title }}

Note this warning will only appear once. Use :open qute://warning/old-qt to show it again at a later time.

You're using qutebrowser with Qt {{qt_version}}.

Qt 5.7 was released in June 2016, with the 5.7.1 patch release in December 2016. It is based on Chromium 49 (March 2016) with (some) security fixes up to Chromium 54 (October 2016). It is also not covered by Debian security updates.

Qt 5.8 has had various bugs, and has been unsupported (but working to some degree) in qutebrowser for a while.

Because of those security issues and the maintaince burden coming with supporting old versions, support for Qt < 5.9 will be dropped in a future qutebrowser release. You might want to check alternate installation methods which allow you to get a newer Qt.

{% endblock %} ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1579082658.0 qutebrowser-1.10.1/qutebrowser/html/warning-webkit.html0000644000175000017510000000777100000000000024444 0ustar00florianflorian00000000000000{% extends "styled.html" %} {% block content %}

{{ title }}

Note this warning will only appear once. Use :open qute://warning/webkit to show it again at a later time.

You're using qutebrowser with the QtWebKit backend.

While QtWebKit has gained some traction again recently, its latest release (5.212.0 Alpha 3) is still based on an old upstream WebKit. It also lacks various security features (process isolation/sandboxing) present in QtWebEngine. From the upstream release notes:

WARNING: This release is based on old WebKit revision with known unpatched vulnerabilities. Please use it carefully and avoid visiting untrusted websites and using it for transmission of sensitive data. Wait for new release from qtwebkit-dev branch to use it with untrusted content.

It's recommended that you use QtWebEngine instead.

(Outdated) reasons to use QtWebKit

Most reasons why people preferred the QtWebKit backend aren't relevant anymore:

PDF.js support: Supported with QtWebEngine since qutebrowser v1.5.0.

Missing control over Referer header: content.headers.referer is supported with QtWebEngine since qutebrowser v1.5.0.

Missing control over cookies: With Qt 5.11 or newer, the content.cookies.accept setting works on QtWebEngine.

Graphical glitches: The new values for the qt.force_software_rendering setting added in v1.4.0 should hopefully help.

Missing support for notifications: With qutebrowser v1.7.0, initial notification support was added for Qt 5.13.0.

Resource usage: qutebrowser v1.5.0 added the qt.process_model and qt.low_end_device_mode settings which can be used to decrease the resource usage of QtWebEngine (but come with other drawbacks).

Not trusting Google: Various people have checked the connections made by QtWebEngine/qutebrowser, and it doesn't make any connections to Google (or any other unsolicited connections at all). Arguably, having to trust Google also is a smaller issue than having to trust every website you visit because of heaps of security issues...

Nouveau graphic driver: You can use QtWebEngine with software rendering. With Qt 5.13 (~May 2019) it might be possible to run with Nouveau without software rendering.

Wayland: It's possible to use QtWebEngine with XWayland. With Qt 5.11.2 or newer, qutebrowser also runs natively with Wayland.

Instability on FreeBSD: Those seem to be FreeBSD-specific crashes, and unfortunately nobody has looked into them yet so far...

QtWebEngine being unavailable in ArchlinuxARM's PyQt package: QtWebEngine itself is available on the armv7h/aarch64 architectures, but their PyQt package is broken and doesn't come with QtWebEngine support. This has been reported in their forums, but without any change so far. It should however be possible to rebuild the PyQt package from source with QtWebEngine installed.

QtWebEngine being unavailable on Parabola: Claims of Parabola developers about QtWebEngine being "non-free" have repeatedly been disputed, and so far nobody came up with solid evidence about that being the case. Also, note that their qutebrowser package was often outdated in the past (even qutebrowser security fixes took months to arrive there). You might be better off chosing an alternative install method.

White flashing between loads with a custom stylesheet: This doesn't seem to happen with qt.process_model = single-process set. However, note that that setting comes with decreased security and stability, but QtWebKit doesn't have any process isolation at all.

{% endblock %} ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581772733.9014227 qutebrowser-1.10.1/qutebrowser/img/0000755000175000017510000000000000000000000020422 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158377.0 qutebrowser-1.10.1/qutebrowser/img/broken_qutebrowser_logo.png0000644000175000017510000016200200000000000026073 0ustar00florianflorian00000000000000PNG  IHDRغMgAMA a cHRMz&u0`:pQ<bKGD pHYs B(xtIME rIDATxw|gvSv7=ԄދT^vbM^UszłDH( Uz'$gyX)fg7;g$;yss***\bCth,upJhKVɈ&b^+2GH -hk5KF$ +XԴO(TiuJJBJZy'UC%4& }%:$JuJ(J0HhQB(B 8ٖՙP q"dJ%RA"Q-P8[ք***^.N6qQN':9DwH;msƹ$$a3!A!0$͡:'ߝJME%PXE>)"KmF:%guR''Jm q Q i m#SB>#`q:1jE'[bq"!NNz co#fkm*vOTzTT>`TT9ʘKiIt7 p6Չ1N8{(~***a[85‰\lv NI"W2c@;0POHXƆɮ]**r_;:䌒z;A빁SXxh*mVCbBɸ]8J8$wPi0Һpq D{6F%bϪxS h裦Bq _қR4_K$*mJ@b1/t8Z+zve q!aWp葤

UB**-طm2%F4PN*Kl0dC"6zFzWFP# C:IicTUU36 70z$3ѲzB~!}lR u&H*,tFGRQ >T!V9'WdO $N񌮉 m 3٢ZBA15j)$( 7%/L Q T!V9œ+89V3$U$=#."9/@.ihF kJɥ"Ҿ"-%1/YGZQQUU"uKdN3 Rb :@o[k_{"甠WڜPCCU @g^ԁ)%":o4GxNk[Q̏]!*GVH )`ՉJ!)!΃j_`ʍ~uvdtS#̿3kFtmJ?Ԣlcԗ@ `0y' 6𔚡uPlq^Itn$CI`?ژHWDE˄K; 3}q`ԯUO8m 7t,{V-otZ^#d,iљ+mw3%'ODHدpGcFE6FW`ڎR=tHn*gf߶~ڝc)@8h9$`1Bqg`r_b*JS{ZBJ0 ͉ qDخ63EJ$Do2;0`{L63#%N A,؎JBsD03m*3#F v?ğ >NXOʃ$ǫhӭ)K1S ksc9NjJ˔@.wm+GUJ!Db1GM< ((z9W }>HK]7!#oox BjK<#IzX%?4=w >&hmDj$C h ޏF)q@Jlv(=6%\(Hjd Uڐֆ,7r5ۖL–m|k"PX(OtN`tN`$DB@atsF} v o,~T!#e#Shƴ&T!7Z./;fIt`Jn~6{D((f:s:JgGgfVV<>D@b1]͎O ݫ  Bu:kTژւ*~lZMf.y˛.ސ#5@82S@dFg6`KV tx>R^/6 gV1,зKKѾ1U[ܥ+,HDQuÕ& :γ@! I j?{*K㢀mـFN!RnGE~MfnyYlv+ZfzXLQژPGb?&N?Vǫv>mFr#8=(m۲[AD}+q83]Ƌˡ*jV TI0kw巖 *JBQe#x8x$p:3 Bڑ RL6&Q XˏabЎRu]k qP@$*}33Pi*M DIbQSWu3lbhjjjllg&oO scc\. .Z-bcu#.FXq&)$".֠ˊf;?$/jsB$J*ͣ q aU=fDl9<)۟,*qCum=*P]S:TԣHzk{.:&a^HUri~(*{Vq1z4 1uhJB sH?3?'ozTnisM9T%eU8VVҊjUTC[-ՎÅ0YlFMf KW˴]K$T|x_6&P +̸c)qh*mVژ`@ys4]h pB(.•M*!hkh:JQH`P~EG+:cp"EWK[dJ8aK~Z I=J4<#li2i&–bϫ+5D Oe1\q p>\E]Np$IA=fUgrJiO4A({œv"%K|~U=xy+|48,x no 6']Zf}k6cןk[>J:&"$w/9*mJQӭzPeFK;!~5uvmԪ3;س;lPg!Fkx>n9ݱSp܈^ÏJ6j-#}z۹ *!fx7?GIYZhן:+PVnWM(mW +!N?WvՂ;6cӺը(?9*>g 4uF: JvB)ȻnE MnFg3$'e%ؚ]pւRM;[KP^YxD0xG[S]Nc)|҄O/0DV+j ROƺM$,exEMV3+'ZU 5`(m'XFl\S}3/^?k=[I:Fb6'hr7)mhuBF.0**d+ep:TV?R_{*-P[CS >擁\m'Qp*!~y+꫱!W$Iؼ]jJdw> g1P[Z +CkJȴM$(m?hUBeD^qJGo?~fRiS(Jh{sa8ESmT~DxnKcД_WZsK1Hi;X=;(mJc;Eytf{K~ " r`١G+mJ`$>Z#t V!oIeF<Mpر_yTiBqJi qN$qɎ<~@ 5pl_RpFg9e~VME<ͮU]k [ԀҋRo y!r_maÎU˰q!SF%x4)*2BiE0a"gN%J !-į?1Bi;PW[)*!٢"6YQԽ*,ۛJx:-a~AUFKi;bZ7PEX'#tTZ'% F]8[a /2JwJ !'oUǍ+=W/ʟVHR#i5h5![g`-Z+Pcu8n‚6KH_e]?<҅/XW)*ofxQpPݾ/]8*m;Ԍ lt-cÚX|UUdÛ]'V]M (=}v܄!l'BJ๋hii2 &E'~^BP%xSc7*׉8]N0.ǿpץ!~+#^\ñ`$9Ls = @Iل/+=*a7k9/fJÂJ!#N;Vr9S?'S$eT(?+= *a7ٵTtxbsEK^XeFJ!!m;M8@Gb|rG4FP 3pZ_xs'sQ@ Cn@ڰf~ @l_ g!47JhQn$>$ʍ`F}q+7Z!UV#g~E9+év&&-H#F֮ /U*a7kjV9L=B2ޞف(Ct$h}-/6e`gqNb>dE ZgbʟFުWYբ -Pao&F|}lHP qLЄglYLKxz3-y o5m3ւSr8\%O*cPkG''d7N/ H~zz9/0g*L/ ~DXjw**Û}đ+vFZRB4{:+lf)ד/X/Vc%V I,㜜}TItOU XtUTNMV8/`3Z.SGcoqP ;kkIW <+3fo2e Jjf-OQCY8oM$^ b6zX/W-5ۭY** C Y+ p2#|AB/iF{9|邉UǷҲ_ 8,yshIL)(r&6>pvMNK%x"`.H!fr3})J @g%nCz-_buyLdk:JJxWYuO0(cj, !~;_Sg@kk{0CzJØ ԯǜ7w*1O[PBE9W86|=81'0[QA1^7s氆%TT^TdjPpywH,NL{>mK2l種,bVs<Րe͜nѥe'f-VRiXMG\@ECՀox*,5 BjX@HJƿIy_ńxQ>x=ϖk{s"`đSuY9Xsj[eݜҳ!ݯPеS{ӧ+Cj6H鐌mE5zMB^pwM<-P(&"Bblܘ("Lr.?VU?&U+2a,ys`Č QQS]@hг[ A{`^3xF8'qM:4 ė6 =7?VJvK=_v ӳg =pE`g @#g_As0~`| $)^Q=^jVH!)3PmZȒxXkTWU})$<~g;yd- 2p(AApAPZs"#"v慰DEFmI@uqh Ө6#uA_;#~f;bG-5C{}@uIƳ܄̙y?4C-dоM"2S.Lk2Y.Ta0VsMꕶeLV` ،x26G"{CYzd1Ө0ҴnN qAv ȺJ|:I\;e|EjMbϰ;T!,PU/LookOfcDk %,3H.C*?Хeݦe̝gÃy0Ր9IxAJ qvd\19>MMp/5:l+%JpgB{dFV>7 rW0"/16Ҳ$Ɍ;iݼ  Jh4.Oj.-{YG\P !+1ѭ"G%<ըiPg ?*)*mT[yQ_$W X`u7.?V[7 9EĹE1f=e͜aQ 7 [5a5C` 3PocfŲ qn.ʍJ{+pew钓ҳ1mY/,֙eеQȺJ<؝HJA yp/"?F;Au6R&V+G۲ (e]v9( /꘯=lɛl 'z^xQp΂ήVoņ!t†cuZFbhr-7 ebU?Np1^mHZp糎,0rhXZ7.ۄsi?o}*]#/$CpM)*rw h0oAqQ\+ 10]\yV Kެbs{ a4[jPpSlaՋ`0Qӊ "'p$c%&VQ90:UO?,g ͐uqc'-04꣏0p* ca.X+K+3PZOXyPck|&fqbk,<.IIQ'LcVB 4W! [pa'}z uyyݼ1g]d'rMOݍ^]Sׄ؛}<^q1ڀ#55Lb,N+Y6ebG8嗈Һ10z'̌+PVO(#ԻM $!6 _0;ؙ"t>#%sd X ?((ѫ{/?uMߗ3JttIqۗ/卑K ~v3G!kzK!p]s:_;ƅ$hh,7Bp7K7l݃PAƳz{ƊIsߠ A[P]5-IB-x3^*Sn$~@[Z :|'=ψYx4hk`5'D:\$ sM|MXΆO3?Хgݢ8˸~3O9onS7;&WQӀ+r:Bsp͆,1*"PPLmh0z `?xqe`@xF6nI6EPa)6ߟ&4\( b0<5bEmGa8 J+mvp.[f_&k-Qewp?ҭWr-8QLbEog'rH, p>&>#VX>,N@+ܒ-vToh˯BL]rud? j>6g?%⪂B9ݩfx` JgL'*BHBO&% Ԟdi&/. ?@.t}׮%:+tD'Zc0*ݟ3 VNSsOPpϋHvjx"?W_~=(m+#p %eS/;FCvIٵ#zuKEn=q1&YpS,/cX9:u9ÁJŸe54i"4x$mE {\r5 㣈Ar* BU$I8ڿ+mY!ft['2SHc:ڳh-):'_wg/|#lw?U-WOŒ./D&u1I|AoZ.Q؝nD3/1/O)#bG?x'ygLCrq V;8x6l݃[`뮃.z[ѠG0t4zƯCD=Gg!.($H[Ͽ>dx{ أ!)khJ/͝WP*0fB{ɭO䌢$1h.[ `wc'%bWႌ]9Wc޽ɶ$i:;G [bmXe7ӣ91`(t9A{<:zsBD|o-W!Gb|,>x$%g&@)tsvH{t#+gxU )1fZZauͬJ6AƾhHdvb rߪVIљZ*$snG'[, \(M0fD84S<1c0͛k}BE R dJQSg~y k]ĸQݴ1 MMX_$=~sAQi fv7(X_e~>GhڋXKyuCdQ*}D<:c҆VF%Ȟ֛~), t'!F$&ba붎bc}Eۘ=:ղx/j>qx~f~hD'm"K_![2) ĭA 3}Ƞ7+Y9B‰_> ڝ:DxɁ͏mS ؂@bRy*(9bF#gH: ~L"oԇFL\|6$=B{ӛp[?@McpS0`H \c9tay=B!A-.uF3>Kސ!<5:ɕIBÙzv>6bE1xݲf_js_n`F?2c֧of>޺5O=w^w"#dQI Rrғ#9A7&M̸ *.=0ihӮҦ]$ON 7^ qA8в/5Z0ip<;sR/?w.j͇8#'W?c}vސdKdϮ`ݸ.J"L7 !ŷFE(ċiڗ4wؽG^i &DOL=1Y# `J!h4=B79,]q͝KOZ7;J3{X.ߕ#֠s@RɮViҁl"HҿAۛӯd!^} ^Qxߴ~N5pZ. ?XE i4tck5 MKˋK+$ot:HS<S>`'m{\v-hQC,~0kP̝x{}K8".)6 qnN'qx] ϴUZPiD,)YN`^`Z7WX*~}N1r*IR C9k}/|({j hl^ ARkݨ; i^@h0K1j܅v>mO/D{rWcsHau96QGD.((q+ ^% 51O5!b2RN* x>=3}&-oVө{4@u TOƲB/9/Tg@J "VX#p[eLnKN?{WB q%y҆F23fo.+()hh"bKKL/$6S E묱 x] :]q ٹ`HKuy0fT{:%3kzPbi"\<?vs'~BɲkIM`tR};[hj o-(z%UqNn8?mfK6k#MBf/n)䏼)()c)Dv ȅ>kȴiєtx:l x,c_ӧg$Ї& ,P$}ѧJnc<%q//W`?VY-kP:GЍӘ:idCdX#ewļܹGDدu['Ӧi%/" 1p93AI/#'(m_霌 As3bf\+dˮCa/zu 5DfU"ݖ_™#ShMS'HsDtf;xov+F-V'`::QZWLyDҙ֟~ DŽ 3vZ0pPx3c_X_ .o5 @!?= 09߶K!'*m/ XZHXt Fǚ:qDvȴn oۉ@IIKvЉҧgmf)>31M/D 3B<)Qjv NǤ/o% G&  LkY'=bA+M{]SFvj#^!n"+ WM8Qy":oX"`Fhh4Mı@aD< N~Ex3bF.Rwg@bK[ ~xϘ}Ÿn.O`ڴ   X=bkZ8E'. !/uV$;1 [:#!&XjCE+.pa ztcMFDK3 JDossƎ~ Yj-O&hg X3[ҳ2fgŒ@Lƽ3;7vFs_ȟy=f"~})bhc ;6G6/&h<ҹn Jc4FgҮ}G:t\rX^P֐tOMiXPK'ffcS&Ъ5 L4}dtҳ"i;fIHpY$)UpZJם]F5N>4PD`Yrk.W0h*)đq2dMk_,?ZD)n*FEnԈ+ǕHFGFH>*>$AqNΉD4!oAh5`F|n#Ii ~43Oi4( | H!I=7۬) N|7gtS|&= !#3WRo0Zk} |pt:MO5WLv[y#fzg'^=.ɌWc2M#W(&h!ay#-#1>gMb>d>ޑ|\%xD}o4N/"=k1Xx˼nryw-W'~['&w`ϩ86'lwt̴D**+Gq =uf(&5#<1CH!7F{=.xѿWMmZ,-'I3_$ESz~̫<$LtH:Ktol Z΄@OP-8V֝a hdm\ ]P=nvdRZH$-կ)Os?#Z Xb"`H(!&( 1.>~5>kIv0EG-KÈim*r>1,U76衺 ۤt}å"?#U:SD*T,~E,7m֦S"}8fHDŽOXw-klm3ʇfd_~i06nV6<: $uoba|2v@X!0:rδKbf:f\/Ib_ӧjd1IYŝIaQsaOнF?+xD~ZV9of}zևnUz X8wM V ୦iQX%|b^B<J\0)oS$Lӊ1F=ZU&A 9kr4_ns> BߟV鏋!~DOGFRZb)rݐy ^*a t]zV `;0 !NH²Mp`0zh?F ,]vj~g)pAM+/1z'67cKZ >+iHZBú۠,yiY/t݅Zf;7;jQ.+ fơL0fh_Dh5J̴aHp{չ 7FIiA"lb\GFo}͙~&fҼxTkbŊcʴ۔6mꭔ6Ni0U7vE!frYVQ {C!HڰJ"Iqx橘|ވ.YJ~(AOf8} Upp#o6,0w {3v"˜a[Z%xh\7e<>$.?RvXxqC/"4IE̩6k)o·6*)O/a*c,ԱfgB8!~SzTƒzỮB.)+Ϥ>^y*wQbN%-y305!GISU0;d7d}FB4u6Mdz3 *,Eֳ "=D JbK\0R %6-LvF5y!d^) S!ү&Bƿ]pьWw>*pKLF9^Vz=bu6E,jtRBLi0}3_wMP SF^} x9%<ܐ_|l-$IRFb<ͅԒt=g+O[i:N4 ?ktF,JhpHTdTz\Nj*3rpq.’䊆oUF22B8'86lZf4]JѨHMBBu7~.];(mJGogCT`<}2bČO3 ;J)=xC[8+KNZ<ƼH x(5=:wT0`Ġ޲ߣǁ#%JwY譏˛k-?PHX-fif4[|ڋYB@C}s,;{[9 g;3b)l*ܐ<~Y]rڌ},ˌ;NU|O)[j0gg nr`o$~B>YiTTfWl*m[p;h}؛+MI5U8wf4]&6YB,B[!+O}DMPQUș: nWzLJ[Z%ſ}gPaxs9off|t<- au/s)iB[:0f6`oEAK%'(mJ8">Wܐ_,=4J˩x2f{u1i2V Tڌ'}Gj_R>9DH@suDQ ʷ9 k*͆{p/͏^0^BKެb*Oعu&4 te4!&~qK[@5-,JbHeO ǫ\n&gpa<جKlB. k%z K8}ii,Bk6 UT<]ZĢ/WnpE0mЂf=""v|N =OKf`vvB0;<3$X3ȉ:#VqF+/o[6{K)*S* =Ҕ>~@r`έ:zf]SI,xED(7or7p☘hMP Ǝۯ@j6~mXy5Vo*2DƈQٕ9G*EGb6B{H9p R:'>p2`fB{&~BkP]*Mӹc[d~%҆kv|~6+L!"AZO+DKH}chf&"SBv)׈##7GGᖫ'ẩoM;1g(.P*A +=Vh.%@`TBoF;OCyJDhU }2F 9+k'?X*-hZ:hHzDK݆yg`4bCdyb " 1~.=;=}o6+>7Zex0:׬xzmD.Mÿ ~FE>VڔS8$ (]6( hUt!+K߁w/SEXšUkXWCv)mi8%4r8%F3bpBS.{n8nc+*_!osp=h^ЭS{thv HNC|ё7>|iM}7OŒW㛟N4^0^LڊE EGcdJ;]`-IGf)(NX K%OhOaV1h&76m} +jfXпW\}ɹ?f0twMrBzF\qQ=;vy ;r<)8:qJOcج}GRὕ[ #4 0OK,:*JcqBdǎc_n1_t;2b`nx͏ǒW5O/4YnHz*=.MQxh_P13IB/--?wfGo.5JEcjQ_WRǓamO~ *as h!uF&xaҹqh׶f+._0, HzwK\(m Oe߆.D7 pJϋk%zicEG; Ai3 2 @NKC KetOG8zuFj=gn33볟— Q|&_~M6ӫٖus3Ǣ1y\BBkF̔pm0J{o`Ψ q]7\+&C̸H1^}+luPnjA0Ke~l/%ـCG0n\mSbI#@488)CJhs@XsP%G5*fqs2[.Gb|_6-Xju,3{uCw^*mJ)'~%W(J lҥ=8ֲ5ܲ~^>#s648(rKBL M$0Շd 7tUIU|dPxΫЧ23~Zo|܉Þ8?}*&7"h2a]4FŔknӻ $/8כk͑zv^~ɁjA]m5 ֓$Ĝ%4wbH"!B6#.;^ *w{$.9f` z|9tW[5ŢWEWB3h5=B$iuL"&xU$.] T+mD :׀ h l*"kuKQz1d]0jH;TvnM.vm=;*r 7E5.2:$X@p('[Ȭ3G_:wl;D05G;_aH ZEGWMD֫8$QZ/&xXy(:{:^h7Wkq-󂪊2$&QA!A+kdt$0&Bch \+د tQcEvyj4~mo~=~Z X.OErBP֘jZZ4w 1q0=r`yy7u&,ǩ\i!hYD\\,J* [Po$ qQl ǔ6"[%|l ~3fx 3Pڔlz6 FA#Ұ_E'b7DE EWU)y{0 ^#m ҽ2X6{HjQQ]*>N0Ny[w|ږ*0Kq4EhZ֖gXc+v3}zy6{|qgd=%ǩRۃ ՈH .ǞEٶ.X_{dUuRy3ߕ+wCB\pAa1/^p7s~Z)|^`-I]@v Jp6HB$@hH|:I\{y~a(bq}X{~abkΛw@Õpgxx:&UĽV cRQ^LjeDh1$HeZꂇ zΦ1{Wq-_si7j$'SqQgH7XK} r<5U;'QaEl\@`@иRN:*<:«FIӧbҹ>V^}mSr`zaL9Fy9j0pV{7%41v"mT!a!zS>r_~'jbC8WQR:|fƆ{~߲z¾w#^6}lv] 7|p%ys?k0F CF^} Ow^qn"'X6%TD={w1?$ӫi2:׉HCTZS۷u8wX~&e:f`1¡)5Y%~65JʔKl;S J+0ݯig  hϨZ ݩ[/u> 0ۛͬ]u5)YJ Q]t$$xUu[vfw=8%Wy6WTebxU<ђr^"^^T}ZN;+.j_,W AOАB5OҩKp =8b{ <ͲPZSLQT]R@$A(֐NM1+Uf`4\(ꐀ"њ3*.=r+al^`lDی},~r[1b>8">E|ͯ@Mj[g0Mߌ#o Q+2`6߹c[l~[zV)%yBLTQ%4  8\.vetI`(=f?6{^̩aɒF"ZƀHm;;˜aG1띯uW=+|!R.PR8KTaCQij7W =B".(9#D A \wC?6lGum=S)5= VgiÈk$ 8cl.!& (;{bZj[̹ssZ8Ia]t$_9 mߋ3ԛxӟ/aޟhՙ [qCmJGgŖҳ0pmƨ,-ѣsGMPSď+7+.z *Qn*Lg ϩFXk-C%2u`A&3|yM8<ݔ;#НW<$1~}zv\\H`ӽem'6ÛEH@7{Q` K0+B . _esHq#`VQ@֕{H58TEtNH]#u7unܹc[d~%҆Sfr;_cǾ~o;E zn׾_N$?bهuY x"۝` s0:$'!!.5uFM 8%eXa;&5jt!5+7%sf`acû4|uͼ=Eׁ"q\Wo{K~?5E c1 9ho1,b_d Zj1+"$Q`AvYx=Yq\ u^Dtp?5(C,puӹoª_ghfJzNLƽ$ioq1zuåbR:n0ꈍ%`g˛>USm8-ysfpMGh 1sy0:>[Ji3eٸf~sSu5U]3vlpU[bb2L0"}!Z, 0MB H^0a pb2PbˈG Fg$+ٸ↻1Dyi16 {ܤΜ\_ rrr 7}d\ޯVꉊm)oHϺؔ7go7ޢ^eU qpBI[<D>u0;:|lȋEGb8oJ%0)w>;P$r"y-.{xS0" X/k,@+ :Q&B$!w(:r\J,`u8^$Ebe0F~xmܺ G*DŏDDe3 6dTdŇ\l^Z>-era`! !%MeٴGK6#h9zp/ޟ|kkPYQBc<-ahȘ9Y鏹z]@?KtSq`zGaqWzƭ{CTB",= M&!XZ o]?vF@6IrPһ7ـf~=:M 9V چ}\kzF6Hn1qc@faT_j֠b [ 0ԥgOc'}".THK2b괽DI ԃz4 oe@a"֍~ݲfW)-Y_k|_I^mZ`O3Pla0~ |lҦ<( Ujk!22c(5%~?ͳp9 Cz z2 Mĵ1@DxéS4996 cF 7kn yۖj 3:F2c)mA& ӳ``9OX@{ƃ"b$8{$fX`Z*͢XJ-1Yx{uk4#>mQnVz&3&0g" L8Uԥ$i zdHHmeJ yUP7&GȬo)݉ɑ e=4@;M`G8 f&-RO@Ri pU= l9']z2\jv ;q%IXD<]D CSb2o\\z}Z?AמY 4.ftOpH9[#b @G.?-YU 3_Pu4H(:M Oռt$ZSVWHi0*l^9ky79/DF'v΁$`+` = |V Xx@T?s;6QQ+l)oi^mgPrOư=IMyĸ'%=Vi4”F~Wn_sPP,bfLqN ϫӥg>B V0FxKop k׶x9vk} Rŏ/kMLj[ F22«,)=k&b&:2yKƵS }Nb&:Ƹ@Z.W^2Nb`AxΔ7'ǝJrxbc%S K'GiyW:gm4ddNV !dLL#xKo;K~!zb̪7:8<5bň͂ᩚ¤a4=B<0|ѩA@I1譏˗6wddR{ ;OGwh\>)]i3TTR-/;'ҳ.@@!ƫ# ӯْL?3r|e˛oװFدOZKϞ 9<)T0,E0ʪUgT~F !ǼNc\7{v%iSNkYó&#RЄ**MQL>%tY | 6MɧA ܆ӶkxV^MA*2 D/۪Ҳ_Ѝzl sPgŧaX!TsY>K Fz r +*m.ä8\|OyUTmgL$3J6"0N6/LGEo@G?ͳ e"~C̉4j`}4Fuy 97>Fi@ğ ?8M'r׳6W\x~/@ђ4β:1f5/`͍͂Sz} '$'4^}DIedzg_YNnD` -7g1dd>;mֺ:+>Jp&1/ bo!`B|p`;26lEct9Hi i6C%,EZBbȘ9TrR>4aa' $7=k!#q%zݚ:+>E={), Ή Ќ7YBL8`WT\>Ti3T,<3!#q-Cr8myK "يTXr_!L/ň#Wޑv]VFΈ5>map6y኏53?󣳄%M 1y%hԵbY1ous[#<}z֗'*'ya ,!&"$%8+>(uj}zr$RD}mgd3f` _X uVc啾7"$/ mf㦎Iv#Ҝk,%;Q$@= A;sxnS4 3qտ>ž$@ׅ+خ7g߂3bقnmU%ѵہ^9sF !ub*ӟJX'h9ݶ~n^w ڧF 2t>+>pXTA}ilx g&%$q!fnq&,L2]R Ui]|m&ƖOѥe8|a = ^x.2gH:+ ̈}r$b|ܗF<Ŵn {6C4yV|h&ۓjQy%8@8DKD$zm.Fh5ȾJ?_Ri=J&X°IM1wW̸R@=مw}z^2|PTk!=X!}5hru*3b`“8- gH2@cN:Kd| %V{]u+(0U2ߘyظu/k}oL%\1vKO[:ѐu1 ܱ-b\1'Qnۿgn Dq!=FSޜekq C-]nj>rF:eSB̫hҶ I@fxBSy6`͛>#ĦO> "4$(%|.(#x]L~]@i3TBj |;ՓtY-mv ɸ1Ms'Z2ۄ 3xCL~ ܭSL=)zCJ  )m?hi!16{VPAj{gQim(a~ζҟ;VT9)FÌ.ŒןMW\3oމmKt5֦Q!)Z-1a6',wkMi0x0sOH 1ٛFB<2@lc:^ggVN^c;% 3)!stё~D,ypՓv/ /7u%׶@oKϞ&=L_/eH9uF jr:)A YbSA`Z0wWdEAfҖJҲmrRVi_?sebˑTM<.FIb!-{N=ҩtEi4 qb1#UFi3|/1g]Ь!)pEc11JJνIH$A0.# I !3Ol،|c @QM%&2تVd{9E,At~=o-H;Aۍ y6g[ &b^"u5*^ *''v Z-%@=ۄw:޾`{) ƳLWX1`A:s'4CDzVnK 1#ui!Y!NK:[6["#"wS5x+ qnÝԳ@zߣzHLvQ8\MuwSaȞ( @EP''dFܐNZO˺ G٭OP$[:_~ ڑvY?+m_ qIfHVrg 􅄸H}]M%xp.Ϫڥ=W4m7\)r-ώE o >;>$53n{KomT.XPK\/tm4"ILZxa;|o(8讫J - 1IJ+)ғw:Zhm_TŐ]qw;ѹ[/vDXy@@/tR:O@a>*W7.M0ACtGdDgR:YTBd*X,Ӯ!(B^]/n0֥tQ7i_ۄ확ڴ+n 7z| %l\%I|pt䦎['5ok#H/Wd l@'fNQĪ[6ߴ FxJBZr1׌Lf;v`o˾8YkyJ[e*h0/ޒ{+`@oܶu&͐쨴VU8'6f=s?UF&͹/,y8y'*'pal\ο*ܙ &%8N̛$¬oQPAٵ>%Zfz ?b13k@JP1zzwwNHZɬxŚM7g6ڬst={ 5u7{ /7ie㪜D1MQcպ>F]CE]5߶tX3ΝvPgt5|`(fd^e˝wfY(=~/# E!M%җ`-X Xaf6sqlf.FB"d$ f$P"3F"&!%-@X24*DE*=Gxw` ظ{kan$Zܹlmd>WvDDF&` :(GFgƺ31Mk=|5w(mwQU$IO($JFI( mUW]pWUQwUꂽ ((]l@)$$}Bʔ{ޙuss33=5hٌ Q%氫#`=>*{!?±n3e}N'ǣ/XNfnqpCΚQgj?ɌL/wJ䪪qVtN~4j ǿv tBq`Ha죚N!%>l=~ې.kv`/gC-fXQW_Q^zIm%v9{kX= 3bo&1lm<=-eN;\AkܵG04#Mb}.ihiTn'2jF Byh8ƨ2ɩ{۽;X(0O~~[?|5u:q2̜6^Zg $.V$Yҭ#iFSmؗ`PF&Tf1WpЁ&#`c]W7G*>PDkC*N*罫)Dvn>/hԘC١g->B04'kC$Iãi 4WqR0?G^p~ll/XPrpzGz6R:58eO&ݓ_wˌP3X, [30 {>DR^D='*:'ҏ%3#r& 4SZ/$mU=|,NL IO[`7>Y %ՀtZ9DёJs.3 s~V\vZ04'1.U^f!xslj ˳@x_ifss#+g~'l߼Y [й+:wzAXŒXcoq\.R+c~R|n':a+>BG%eZ$2 2Ȝ 7V5- M5C쾊LIo9nǮQsᶶD2 ]j=3Gż%Z UwZ^Oy&҉-%_j4ֳc014|4î q!( `k :wCN|*" S{ٗ%(:s\رm=mXcGy |\a2!Y{88FħT8!v'hyoe~"k݆fA0)@{8*s$ 9z WKԉY "$CRJ; 2?h:gCdFHI[NHMHe&O"{SLZe/Suc%G`,50c\2kč#"馜]]Sg@Df;" ]"r&VY6/2g“ ǝlnE*x-^F'*NV!Ð~վOzNă[S"+?pj(RvqHu l]-DIQ;GQ\|ǎD)pZ#`FTL,"q q=))GqI3n>mpT+"zIDYqЬ7N F |Gn Oj)@zK eIȣ!Dk /:@ߙ2e? aH?G܅%iiD cCցŽͦlͫ%Yaf^5;s-_'n2'-@|7mX#ՠyѦUW&ѯS"L6-Z$^^jFT8|n0^c&74i1 oBfkW,Tu7˘⭀GșIy.ͪOR PQ!u6F O\?}5g[ԩSais[:BH,󨸤R-2b{ u_CrrcRcRcf Y ÓI |_IC,|I8榫y1sśZ327vS2C]/Yo>RSS蘸dz-6J a9-P{ϴTmCT#,_GkT{+:!Rڪ-NOsT۟ǥQC}o9H(◭`5u{=,F"vf- Ky,#ץ~'jYoa_THc蓕svE9чS[ 1=ܕM1@, D gT|H1 =F}Tkkl"&}H5@S(f- O:'}+GYo/a?xӄزMuc-fbBrD|ջ*VS+N `wS{T쨮Ͻ>Í1bp,*1|.:"x8Q9 ('`3.<Ι#/gӤ%,fX樴8}oYݷg{Ey9*M &|!!F|eị1?/' ŠeX:6ޜ v7Hiԥn*Ƣ]#>Mey} `y5n%;v RڢMb-ƨܫafSѭτdȡT{% ʹ\k@U-v`0#1a2*KX Xs-X Ycz#]'-{uݧ.M~n`ԛ3WvbbUn{ң-1CTm_PlZPz9ɯ{?p֏ˍ$G|'-Ѳ:9e2 -G]Ju8~L/u]jF"41V7Yc*9*e~^`gw)“DNl\ 3NFźtb/@ 4@6Ω3blU9MߝG-(@QWҗ&F"̬9p#(KE|w<"1O>p%6pre*>)Z:U-?b!=u`hrD(l52"Vj/ous`_GoYWޟz0 WRxωiҍ*>%opVO,AT #鹆oؗ-ބpþPeغo=o~FZHKnZxgPmUqtT#*/0>޴sҁ~cλe%DZu3_-m06&$/Cyj**t:*zݽ=o*ϰI2SLA㩤oqCSp Bx5/:Z/$%]u 9JZ0\vݓ9ʵ 6m8x`/֯X?Ժ~pg-O,''Ը˘Ew* b"չ> {9ip=j2p#ʅ9{CM@H5}y6 M-V}5 >>MYTR| >R&avTbs2075(Ɔ1P?߻PMٳubw(Uy݅z$[C@;ƎRB@*+o/bC~<ܨ;I?،ءu~a(-9{1NZWrA>wz:/MWNɉj`q&T/SvNǺ<#?s=eoNQbSӧXx^S*OYZ̳fE=8r4 Mk5FU-1DwTSN%pg@%WյF5HUx~aн cPj>|isˣqQC CO#b(>v;nh=.b bBwD,Q,OG EQ3}MQyfYewyڶg$euA:舾H3W3M ^)M++0Ή1UM@ןia8~?߁IM#b'|U[XJ\kC]ԣ-gM5+'=;}e'֒5JʒӜ 8Α+7\6 ]:z &zҟ}RMY XGkA@$  عjf#3_/a |ƀ=5}i Ge96]o`c/O 򞽌J_KqDF/؊u$'JQY~'pA>GI؍}V?^ L* Fb/0̈~H]r[w?rzu;0+:hk"L5nJ9QV T ƭ ݓ_2s`̌Zu6k 1D!@oз_nTbL/"״~z2 ^jtܱ@[ >Bզf\|@7ul@Dx7"¢R`o~W &}-ł0av9]5էzÕ.8knyޱx%TfM{;\ĒE_*Pm+(bEGZSvڏy=I eg#l>_ / PGpAy=yibNMH[KV*CmLHOa 3ܓi<$(8ÌXv5L(Tflߜ́Cغ-̳/s]+|b^µ֓Zj)*/[n{7m v  ӶخuZ z0IΚ(gswpȉHpXl%;Aa95 "|LoH?Y拡5Ohh)q7]&|8;I<"!W3|l2Xt>K 9>yuzkFE2e;̌4(l%>X{ `ȧvCT Mj3iiy%і[,. y$\)$_ ȭKD,Z9cȈf;/?tXGKZ^-|0*}"|4_}{)ś% |*$g[MN\zxI礞j]YpS,d~KuԊ%YƘrr յOb_r*jް@ݟ^v3}4KĽaf<{Kړz/~ɑt/Ik+b|(:őÎ--GVRߙk3eӪlhːl9_b2hMNԊ& HOnaxO|:s^)`Ͳ/r,`ĄX=@)(6Fľrv/ZYG} LQKI[prx7+=QY[5gE&$iޑAoXNڪncah_ Z,9W΀oXܗ]&bl#NZ=i>EkĊ럢GbڛgL2~Yf|dnox*KfzM֜kN0q9`^uJн5âE㐐q-nuր@& hD֯gG*A%r^:YԴ2:Z>LPOO啧&fz6"wȺ/ W[.}X^9GE 0fT :l͙0?"giחiEy"t7Ln}2a:9i16x+,aVlYQSUK=\ȌKm&aQѱGԺYG>~W+eǮs^'-0ZKuT;g_zd vǓ,.#lkLሜ Z_>.E"Hm4H4,jW$_ Kvg+N [7Bi1wsww:kLgܭУ΅̜bG ;$]HbSdjśxZq,4#V}aݟ7ri&lgD $ Bq ,_P)$_qF"vkfs0C-Lp\XONI K 8՝c"g+ln71.g̴;#U9Ha $x5gw kͷoEZXZڗ-ބUL}w4YǛ?FV|D ؂}{<9%U6a}9ǵtXoZy6 $q@2ln7>_/̜:?nsOXs?Ϻ[v*o`FSj9i|b-uVyp*P|0 {s#?:31??iѭy L@k/e"҅j =@ X6ODF". j;˵X•Z1jNoFy~^x5*d7 zpOl=%wktAa2܉nTGX$j{3nD ɴZ+]Z{Ph(A6"Ge96[ͩ"w=Hñ!o3$~) ̫9hجDu{u m"Z<@%!A CC 3Zkݗг1N*f ڮv\Ae5aVk%{/#sֻYjn ްL\SdF4w8YLei5g܉ukݭ16d@浿P`L<@sE6{kJv'xl6k͙P Yzg`7z|^]m-^U^:k3kΗIF@ZXڗc͙@@5Tp %TS5 .aF0%̢U$U˨-qƜ9'D [s|q3Ҧu̸{#8$cM|aɻj9S^i^MYs&,4! U}X<abH銩 b/K Tbm5V7i!(P  1&Jg<;kǔ$%I'BpT}S+c\t0[2*@ QIKՅDK:!y{23Yƪq-k2+z=RF .A< LF"lZ2Drˣr@Q/'%@#Ͽu-vWebl9jJ(Pp5A0s % B TL(aJL%58ޥK; = :_I6*}yL C# 0SyK5tu+OO;ww:#@$0t аzLc?b7PzKŜ TX#ab8^ Un|x;ZH@@|@̘J z@_W˲|*Wo_{qD"g!JnVs1=<`1;\#toç, /x"k_=8'1Ty/-8}P]:  É=AFp6ELB-:0=ӽs;7?^Om{ʪQ煦qTt Src0Y$0xav)UWDHr<ȝM4  F[N;*y)t?r!:(%وxUm\%b~.?Q=P<#.޳ ?&c1 "~ȚS{$1uC> _w>|qG~A4$oǠl?2-̰>O;R5H\ ֱ-uZĐ4:(IJ%YOM-zVn=ӑ@np.VK8n~fOt(& Re!ZTwcݴJ/$܆>qH&F⋙i|tPB[Atv8&-<"`:.q\{jj4̲z[Dᣯ$W F]x$IbD>(|%Z-  g׶w(x=S0_zwj s߉/NG&ѯ2Sk3dFtǧqb1㵾/Y4Za@(u+s S+ޮ-t3< IuA6>{|pwMky 1<^Wc0{1 C#*⍻"(A #E<&EDHҥC 'a൬Tl5DLDrL7H Õs8ڌeT]ٹ6 =Mrj9 $BtǍg3ۯ֮jyomЖ<@0?,-dgl Z[ʀjMB\&БWhyѾs7#L&3:wC..6*TVV,X #g_߮BߓQQ?}r#JhC6Lp :i};!\}{Эg:-Xbᒫn{wuGٌ>]pan.j {v_ו$/;o}J/[ꟓ}If^@=r:WZ I܊Z83^QgAfL9~gP7"°Kօs6h&c$I•7܁Y~}(L&nͿ;:Kc];ޘrtRYRRLom)ru㱦I钯$e񌟎Jn_*ԩ.I2{!olk-SpDptGttAnvH 1I~cxj\p1ᶫ` A!=r2Ť z)n$n`@(Ɓ2G4zğۗiVp}9GkMq -V [bbqקڡu8ckܬү7{t${W$!Yы\x'|mw뜆=SWYɴ_ DLDkUne%x]X]sΩƍTo L5] q<:Lۅ,/H uN|z+0tEvZ h9LS?O֜'k{Qq%n`}e!K)(+ d6hUW3'OSL]a~-ʄBbqb$7Hp!}g|;`VF]LH@Nѫ3wGnuT82fz֛f#f[@B*J#L-|! Fp'FAb943BdG03jkXL;M]/NOӭW*9Z$p^d>;oNFppN|T5wb9AmkNZI(k& ҉Lg!?7c"[gᬩ0J"`#F$[~ 3ihc(X\P">6=C.ѫ{G8ފ f~dǺiDN(gƛh!Im׍ĥz:6iB633m;(O` 3W43u_($W&CG˰WQYݤu(a Cv6^;=׃Tb@„N[֍*;8 )&:\9_:af͜܆ʰ>! փ<=K"cO F5 Z\}5Ys&.Fl\֡F]y*+NFaETINERR*Ff VA,d"uy[s&V<!5!2`]ɤlq%gg| Hv+xkV<^L4+K8*8|1VQ1h6qHlV qtM@.21q0|"ni^qO2j}-Wȍ OVGC9B&X)\~ +vr l oY0u?φ 4_r _w [קDib[O@Bb$nVmغ djycYHkYP H>bh${S@vg*Iɠ%(FSL˙M[%y ][À~iAn[*l_*vTuZ^|.Bxkp?I WzmR@'2%߮%=c\+< O:1I 4B",kKxϞeAx}qeh3wq%( %7UF(-R LBw͠Pqh;x=X"ox*e6Zf_$qX*f{nA :YFx?D-b|X=}#7}R攆g&Q\n,+&"Zfے,3b3~ q ƸR YVLy`#녎q siN^F20#uqMO*جoJ5.cjzCI"vq:V}:)1d5i"i;!+ExXqY)YZ'l2mR:iQFT(>,OUi5~² '90S7gH/kdHρW^?0Ϊ2lc3hL]4?:ַZu:MfhFG$}u_E6*5%9AV0:oUno9r@>O@}9|xݬtm虤d%+ȟQ93h'Eka44b@*: ;SN&j0_D ԫTuhtIg2VXju?|۵!IeH^I!|{Vt_v6j;dC˳lZǪ$A`pk*'4B[j%ѥ~t|$BWnzhG kz>&ʻC2RMok'dQ!}M],_n3g}3:RIۭ|cQmXW#ow[=+o- -NbLO@ HfMm3"#F:;X-eqqVSK@$I#(d܁H1$xZծSW|q6ISL*bT mѩxg]3^p,ai3d>@7*=ėɘOõGIf %[%[j RkR0gFsm5uh.&i>`oϴ~3BZV#מteqiVY&b>ţ˭Zǣ$Rhyu /j,:~BH^S3@z 8RMlHZ;&qm~:%H'!]3 - D ]PΝ|]uf*QD+5U5Ъ rF*p/(h~I+;}A?BHZ/ZPȳ^VU Ydȡu(v.,M !ѳ'9-t,EhM5x&v#+$ &|uJ(WdmE^0 k%G3 g8o)e= F榅FB`D|=9yl6uKHtpc$%d1dL403 AkZL ־ l=vpeJن*@=B:@fy$AhZTX$FaMGé@P  E'CjY D }h\b oJ#: uw/G|Uf@-4P#$S Jc񄈰bLMwМV 'p2GL0=#4O` #FWI|u,bՁaQMLlYu3Ҹ\` Х d@g /<CD--’P h蕑f^u8a$F "Lc/PuBsZtiߚ1s@$aDs$66# xoZf0$U5#B 4ŨHΌnmBϗg@DvMB?jA`cLM+p:jWHuhN'-C!PM ⴎhɴQ֑ƈXa2+d%Cp>- (t񌋺BbXfa$aP̴HDq:`ᏺ{;y:aKLZ;:=:; c"3JԀԴJyya^ k_5\:$# P6D緑u$RlYeZcXe_-.0^ ZuCVģ ܠuz$Ή:8+L:u0-Egxu7+hA(Ò?!1aXkCMtk6rhAׯFPiFm_w= їL:sa}cuBoQ6Q@$Ft^W*x5/'K 7$-ߘ*H5ͩ3^`K;N1[#@ș^ZP6#䶗KI蝙bbg$a1FoC2Sŵ<Qoۉ0kݓӬ_B?k_Ѕ9? GEDR\b$kO63aL"53E7XB1Դdlߘ!?<Fe:؅FVZǡ&QH;g,uVB9$@X HٺhZ1"%'ZGdF!i 1TKe'WV+}&dU491Hm|9!5bf|cEEZǡGQ EDUM3#Uo>O-~ {0>L}| 08郂"X~X:^cߘnB9H.c-( dz;gf">SĜlxK=y$|}cxMN!#~ ku@F`_9xrnAE|@!0$>H 0F:'+~0v뾺1,h*.g_ I@`%bNo#/gyM[>psĜd3Aa$!#ʹMOV50[ZV3jX=> g,RId Rw9&AN0zbKR4x`eL>hZyA 0q\:`;۸ @ɘv(쌮 Et8NDB}OHOŢԺ߁% lJ'UFM!X&'a3[s6åҜ($>  FXMk$3N_I0A8>V9?\ !-'Q:ĆXE,>A QOQ(,:9=&/p=K>xQ,_Φ_#'ʪi|I5zzehݏ@,r޶!kH32ٻOZwD|.uc֧'$ͪIKy3c1.& ?Zl23T~(15ܒN3;RZTZjsAx_F!|"Q7Kx,|zG. Hz.W(`%?Z)F[n:|u9RD@L*"(0*lQuØ6hvrzm%u#jayH:@eR%EQ\fjoI@;Aޓ3-F& o;{ 64!m%tEXtdate:create2016-04-14T17:13:06+02:00k5%tEXtdate:modify2016-04-14T17:13:06+02:006ItEXtSoftwarewww.inkscape.org<IENDB`././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158377.0 qutebrowser-1.10.1/qutebrowser/img/file.svg0000644000175000017510000005636700000000000022103 0ustar00florianflorian00000000000000 image/svg+xml Generic Text text plaintext regular document Jakub Steiner http://jimmac.musichall.cz ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158377.0 qutebrowser-1.10.1/qutebrowser/img/folder.svg0000644000175000017510000005452500000000000022431 0ustar00florianflorian00000000000000 image/svg+xml Folder Icon Jakub Steiner http://jimmac.musichall.cz folder directory ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581772733.9014227 qutebrowser-1.10.1/qutebrowser/javascript/0000755000175000017510000000000000000000000022014 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/javascript/caret.js0000644000175000017510000014176100000000000023462 0ustar00florianflorian00000000000000/* eslint-disable max-len, max-statements, complexity, default-case */ // Copyright 2014 The Chromium Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** * Copyright 2018-2020 Florian Bruhin (The Compiler) * * This file is part of qutebrowser. * * qutebrowser 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. * * qutebrowser 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 qutebrowser. If not, see . */ /** * Ported chrome-caretbrowsing extension. * https://cs.chromium.org/chromium/src/ui/accessibility/extensions/caretbrowsing/ * * The behavior is based on Mozilla's spec whenever possible: * http://www.mozilla.org/access/keyboard/proposal * * The one exception is that Esc is used to escape out of a form control, * rather than their proposed key (which doesn't seem to work in the * latest Firefox anyway). * * Some details about how Chrome selection works, which will help in * understanding the code: * * The Selection object (window.getSelection()) has four components that * completely describe the state of the caret or selection: * * base and anchor: this is the start of the selection, the fixed point. * extent and focus: this is the end of the selection, the part that * moves when you hold down shift and press the left or right arrows. * * When the selection is a cursor, the base, anchor, extent, and focus are * all the same. * * There's only one time when the base and anchor are not the same, or the * extent and focus are not the same, and that's when the selection is in * an ambiguous state - i.e. it's not clear which edge is the focus and which * is the anchor. As an example, if you double-click to select a word, then * the behavior is dependent on your next action. If you press Shift+Right, * the right edge becomes the focus. But if you press Shift+Left, the left * edge becomes the focus. * * When the selection is in an ambiguous state, the base and extent are set * to the position where the mouse clicked, and the anchor and focus are set * to the boundaries of the selection. * * The only way to set the selection and give it direction is to use * the non-standard Selection.setBaseAndExtent method. If you try to use * Selection.addRange(), the anchor will always be on the left and the focus * will always be on the right, making it impossible to manipulate * selections that move from right to left. * * Finally, Chrome will throw an exception if you try to set an invalid * selection - a selection where the left and right edges are not the same, * but it doesn't span any visible characters. A common example is that * there are often many whitespace characters in the DOM that are not * visible on the page; trying to select them will fail. Another example is * any node that's invisible or not displayed. * * While there are probably many possible methods to determine what is * selectable, this code uses the method of determining if there's a valid * bounding box for the range or not - keep moving the cursor forwards until * the range from the previous position and candidate next position has a * valid bounding box. */ "use strict"; window._qutebrowser.caret = (function() { function isElementInViewport(node) { let i; let boundingRect = (node.getClientRects()[0] || node.getBoundingClientRect()); if (boundingRect.width <= 1 && boundingRect.height <= 1) { const rects = node.getClientRects(); for (i = 0; i < rects.length; i++) { if (rects[i].width > rects[0].height && rects[i].height > rects[0].height) { boundingRect = rects[i]; } } } if (boundingRect === undefined) { return null; } if (boundingRect.top > innerHeight || boundingRect.left > innerWidth) { return null; } if (boundingRect.width <= 1 || boundingRect.height <= 1) { const children = node.children; let visibleChildNode = false; for (i = 0; i < children.length; ++i) { boundingRect = (children[i].getClientRects()[0] || children[i].getBoundingClientRect()); if (boundingRect.width > 1 && boundingRect.height > 1) { visibleChildNode = true; break; } } if (visibleChildNode === false) { return null; } } if (boundingRect.top + boundingRect.height < 10 || boundingRect.left + boundingRect.width < -10) { return null; } const computedStyle = window.getComputedStyle(node, null); if (computedStyle.visibility !== "visible" || computedStyle.display === "none" || node.hasAttribute("disabled") || parseInt(computedStyle.width, 10) === 0 || parseInt(computedStyle.height, 10) === 0) { return null; } return boundingRect.top >= -20; } function positionCaret() { const walker = document.createTreeWalker(document.body, -1); let node; const textNodes = []; let el; while ((node = walker.nextNode())) { if (node.nodeType === 3 && node.nodeValue.trim() !== "") { textNodes.push(node); } } for (let i = 0; i < textNodes.length; i++) { const element = textNodes[i].parentElement; if (isElementInViewport(element)) { el = element; break; } } if (el !== undefined) { /* eslint-disable no-use-before-define */ const start = new Cursor(el, 0, ""); const end = new Cursor(el, 0, ""); const nodesCrossed = []; const result = TraverseUtil.getNextChar( start, end, nodesCrossed, true); if (result === null) { return; } CaretBrowsing.setAndValidateSelection(start, start); /* eslint-enable no-use-before-define */ } } /** * Return whether a node is focusable. This includes nodes whose tabindex * attribute is set to "-1" explicitly - these nodes are not in the tab * order, but they should still be focused if the user navigates to them * using linear or smart DOM navigation. * * Note that when the tabIndex property of an Element is -1, that doesn't * tell us whether the tabIndex attribute is missing or set to "-1" explicitly, * so we have to check the attribute. * * @param {Object} targetNode The node to check if it's focusable. * @return {boolean} True if the node is focusable. */ function isFocusable(targetNode) { if (!targetNode || typeof (targetNode.tabIndex) !== "number") { return false; } if (targetNode.tabIndex >= 0) { return true; } if (targetNode.hasAttribute && targetNode.hasAttribute("tabindex") && targetNode.getAttribute("tabindex") === "-1") { return true; } return false; } const axs = {}; axs.dom = {}; axs.color = {}; axs.utils = {}; axs.dom.parentElement = function(node) { if (!node) { return null; } const composedNode = axs.dom.composedParentNode(node); if (!composedNode) { return null; } switch (composedNode.nodeType) { case Node.ELEMENT_NODE: return composedNode; default: return axs.dom.parentElement(composedNode); } }; axs.dom.shadowHost = function(node) { if ("host" in node) { return node.host; } return null; }; axs.dom.composedParentNode = function(node) { if (!node) { return null; } if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { return axs.dom.shadowHost(node); } const parentNode = node.parentNode; if (!parentNode) { return null; } if (parentNode.nodeType === Node.DOCUMENT_FRAGMENT_NODE) { return axs.dom.shadowHost(parentNode); } if (!parentNode.shadowRoot) { return parentNode; } const points = node.getDestinationInsertionPoints(); if (points.length > 0) { return axs.dom.composedParentNode(points[points.length - 1]); } return null; }; axs.color.Color = function(red, green, blue, alpha) { this.red = red; this.green = green; this.blue = blue; this.alpha = alpha; }; axs.color.parseColor = function(colorText) { if (colorText === "transparent") { return new axs.color.Color(0, 0, 0, 0); } let match = colorText.match(/^rgb\((\d+), (\d+), (\d+)\)$/); if (match) { const blue = parseInt(match[3], 10); const green = parseInt(match[2], 10); const red = parseInt(match[1], 10); return new axs.color.Color(red, green, blue, 1); } match = colorText.match(/^rgba\((\d+), (\d+), (\d+), (\d*(\.\d+)?)\)/); if (match) { const red = parseInt(match[1], 10); const green = parseInt(match[2], 10); const blue = parseInt(match[3], 10); const alpha = parseFloat(match[4]); return new axs.color.Color(red, green, blue, alpha); } return null; }; axs.color.flattenColors = function(color1, color2) { const colorAlpha = color1.alpha; return new axs.color.Color( ((1 - colorAlpha) * color2.red) + (colorAlpha * color1.red), ((1 - colorAlpha) * color2.green) + (colorAlpha * color1.green), ((1 - colorAlpha) * color2.blue) + (colorAlpha * color2.blue), color1.alpha + (color2.alpha * (1 - color1.alpha))); }; axs.utils.getParentBgColor = function(_el) { let el = _el; let el2 = el; let iter = null; el = []; for (iter = null; (el2 = axs.dom.parentElement(el2));) { const style = window.getComputedStyle(el2, null); if (style) { const color = axs.color.parseColor(style.backgroundColor); if (color && (style.opacity < 1 && (color.alpha *= style.opacity), color.alpha !== 0 && (el.push(color), color.alpha === 1))) { iter = !0; break; } } } if (!iter) { el.push(new axs.color.Color(255, 255, 255, 1)); } for (el2 = el.pop(); el.length;) { iter = el.pop(); el2 = axs.color.flattenColors(iter, el2); } return el2; }; axs.utils.getFgColor = function(el, el2, color) { let color2 = axs.color.parseColor(el.color); if (!color2) { return null; } if (color2.alpha < 1) { color2 = axs.color.flattenColors(color2, color); } if (el.opacity < 1) { const el3 = axs.utils.getParentBgColor(el2); color2.alpha *= el.opacity; color2 = axs.color.flattenColors(color2, el3); } return color2; }; axs.utils.getBgColor = function(el, elParent) { let color = axs.color.parseColor(el.backgroundColor); if (!color) { return null; } if (el.opacity < 1) { color.alpha *= el.opacity; } if (color.alpha < 1) { const bgColor = axs.utils.getParentBgColor(elParent); if (bgColor === null) { return null; } color = axs.color.flattenColors(color, bgColor); } return color; }; axs.color.colorChannelToString = function(_color) { const color = Math.round(_color); if (color < 15) { return `0${color.toString(16)}`; } return color.toString(16); }; axs.color.colorToString = function(color) { if (color.alpha === 1) { const red = axs.color.colorChannelToString(color.red); const green = axs.color.colorChannelToString(color.green); const blue = axs.color.colorChannelToString(color.blue); return `#${red}${green}${blue}`; } const arr = [color.red, color.green, color.blue, color.alpha].join(); return `rgba(${arr})`; }; /** * A class to represent a cursor location in the document, * like the start position or end position of a selection range. * * Later this may be extended to support "virtual text" for an object, * like the ALT text for an image. * * Note: we cache the text of a particular node at the time we * traverse into it. Later we should add support for dynamically * reloading it. * @param {Node} node The DOM node. * @param {number} index The index of the character within the node. * @param {string} text The cached text contents of the node. * @constructor */ // eslint-disable-next-line func-style const Cursor = function(node, index, text) { this.node = node; this.index = index; this.text = text; }; /** * @return {Cursor} A new cursor pointing to the same location. */ Cursor.prototype.clone = function() { return new Cursor(this.node, this.index, this.text); }; /** * Modify this cursor to point to the location that another cursor points to. * @param {Cursor} otherCursor The cursor to copy from. */ Cursor.prototype.copyFrom = function(otherCursor) { this.node = otherCursor.node; this.index = otherCursor.index; this.text = otherCursor.text; }; /** * Utility functions for stateless DOM traversal. * @constructor */ const TraverseUtil = {}; /** * Gets the text representation of a node. This allows us to substitute * alt text, names, or titles for html elements that provide them. * @param {Node} node A DOM node. * @return {string} A text string representation of the node. */ TraverseUtil.getNodeText = function(node) { if (node.constructor === Text) { return node.data; } return ""; }; /** * Return true if a node should be treated as a leaf node, because * its children are properties of the object that shouldn't be traversed. * * TODO(dmazzoni): replace this with a predicate that detects nodes with * ARIA roles and other objects that have their own description. * For now we just detect a couple of common cases. * * @param {Node} node A DOM node. * @return {boolean} True if the node should be treated as a leaf node. */ TraverseUtil.treatAsLeafNode = function(node) { return node.childNodes.length === 0 || node.nodeName === "SELECT" || node.nodeName === "OBJECT"; }; /** * Return true only if a single character is whitespace. * From https://developer.mozilla.org/en/Whitespace_in_the_DOM, * whitespace is defined as one of the characters * "\t" TAB \u0009 * "\n" LF \u000A * "\r" CR \u000D * " " SPC \u0020. * * @param {string} c A string containing a single character. * @return {boolean} True if the character is whitespace, otherwise false. */ TraverseUtil.isWhitespace = function(ch) { return (ch === " " || ch === "\n" || ch === "\r" || ch === "\t"); }; /** * Use the computed CSS style to figure out if this DOM node is currently * visible. * @param {Node} node A HTML DOM node. * @return {boolean} Whether or not the html node is visible. */ TraverseUtil.isVisible = function(node) { if (!node.style) { return true; } const style = window.getComputedStyle(node, null); return (Boolean(style) && style.display !== "none" && style.visibility !== "hidden"); }; /** * Use the class name to figure out if this DOM node should be traversed. * @param {Node} node A HTML DOM node. * @return {boolean} Whether or not the html node should be traversed. */ TraverseUtil.isSkipped = function(_node) { let node = _node; if (node.constructor === Text) { node = node.parentElement; } if (node.className === "CaretBrowsing_Caret") { return true; } return false; }; /** * Moves the cursor forwards until it has crossed exactly one character. * @param {Cursor} cursor The cursor location where the search should start. * On exit, the cursor will be immediately to the right of the * character returned. * @param {Array} nodesCrossed Any HTML nodes crossed between the * initial and final cursor position will be pushed onto this array. * @return {?string} The character found, or null if the bottom of the * document has been reached. */ TraverseUtil.forwardsChar = function(cursor, nodesCrossed) { for (;;) { let childNode = null; if (!TraverseUtil.treatAsLeafNode(cursor.node)) { for (let i = cursor.index; i < cursor.node.childNodes.length; i++) { const node = cursor.node.childNodes[i]; if (TraverseUtil.isSkipped(node)) { nodesCrossed.push(node); } else if (TraverseUtil.isVisible(node)) { childNode = node; break; } } } if (childNode) { cursor.node = childNode; cursor.index = 0; cursor.text = TraverseUtil.getNodeText(cursor.node); if (cursor.node.constructor !== Text) { nodesCrossed.push(cursor.node); } } else { // Return the next character from this leaf node. if (cursor.index < cursor.text.length) { return cursor.text[cursor.index++]; } // Move to the next sibling, going up the tree as necessary. while (cursor.node !== null) { // Try to move to the next sibling. let siblingNode = null; for (let node = cursor.node.nextSibling; node !== null; node = node.nextSibling) { if (TraverseUtil.isSkipped(node)) { nodesCrossed.push(node); } else if (TraverseUtil.isVisible(node)) { siblingNode = node; break; } } if (siblingNode) { cursor.node = siblingNode; cursor.text = TraverseUtil.getNodeText(siblingNode); cursor.index = 0; if (cursor.node.constructor !== Text) { nodesCrossed.push(cursor.node); } break; } // Otherwise, move to the parent. const parentNode = cursor.node.parentNode; if (parentNode && parentNode.constructor !== HTMLBodyElement) { cursor.node = cursor.node.parentNode; cursor.text = null; cursor.index = 0; } else { return null; } } } } }; /** * Finds the next character, starting from endCursor. Upon exit, startCursor * and endCursor will surround the next character. If skipWhitespace is * true, will skip until a real character is found. Otherwise, it will * attempt to select all of the whitespace between the initial position * of endCursor and the next non-whitespace character. * @param {Cursor} startCursor On exit, points to the position before * the char. * @param {Cursor} endCursor The position to start searching for the next * char. On exit, will point to the position past the char. * @param {Array} nodesCrossed Any HTML nodes crossed between the * initial and final cursor position will be pushed onto this array. * @param {boolean} skipWhitespace If true, will keep scanning until a * non-whitespace character is found. * @return {?string} The next char, or null if the bottom of the * document has been reached. */ TraverseUtil.getNextChar = function( startCursor, endCursor, nodesCrossed, skipWhitespace) { // Save the starting position and get the first character. startCursor.copyFrom(endCursor); let fChar = TraverseUtil.forwardsChar(endCursor, nodesCrossed); if (fChar === null) { return null; } // Keep track of whether the first character was whitespace. const initialWhitespace = TraverseUtil.isWhitespace(fChar); // Keep scanning until we find a non-whitespace or non-skipped character. while ((TraverseUtil.isWhitespace(fChar)) || (TraverseUtil.isSkipped(endCursor.node))) { fChar = TraverseUtil.forwardsChar(endCursor, nodesCrossed); if (fChar === null) { return null; } } if (skipWhitespace || !initialWhitespace) { // If skipWhitepace is true, or if the first character we encountered // was not whitespace, return that non-whitespace character. startCursor.copyFrom(endCursor); startCursor.index--; return fChar; } for (let i = 0; i < nodesCrossed.length; i++) { if (TraverseUtil.isSkipped(nodesCrossed[i])) { // We need to make sure that startCursor and endCursor aren't // surrounding a skippable node. endCursor.index--; startCursor.copyFrom(endCursor); startCursor.index--; return " "; } } // Otherwise, return all of the whitespace before that last character. endCursor.index--; return " "; }; /** * The class handling the Caret Browsing implementation in the page. * Sets up communication with the background page, and then when caret * browsing is enabled, response to various key events to move the caret * or selection within the text content of the document. * @constructor */ const CaretBrowsing = {}; /** * Is caret browsing enabled? * @type {boolean} */ CaretBrowsing.isEnabled = false; /** * Keep it enabled even when flipped off (for the options page)? * @type {boolean} */ CaretBrowsing.forceEnabled = false; /** * What to do when the caret appears? * @type {string} */ CaretBrowsing.onEnable = undefined; /** * What to do when the caret jumps? * @type {string} */ CaretBrowsing.onJump = undefined; /** * Is this window / iframe focused? We won't show the caret if not, * especially so that carets aren't shown in two iframes of the same * tab. * @type {boolean} */ CaretBrowsing.isWindowFocused = false; /** * Is the caret actually visible? This is true only if isEnabled and * isWindowFocused are both true. * @type {boolean} */ CaretBrowsing.isCaretVisible = false; /** * The actual caret element, an absolute-positioned flashing line. * @type {Element} */ CaretBrowsing.caretElement = undefined; /** * The x-position of the caret, in absolute pixels. * @type {number} */ CaretBrowsing.caretX = 0; /** * The y-position of the caret, in absolute pixels. * @type {number} */ CaretBrowsing.caretY = 0; /** * The width of the caret in pixels. * @type {number} */ CaretBrowsing.caretWidth = 0; /** * The height of the caret in pixels. * @type {number} */ CaretBrowsing.caretHeight = 0; /** * The foregroundc color. * @type {string} */ CaretBrowsing.caretForeground = "#000"; /** * The backgroundc color. * @type {string} */ CaretBrowsing.caretBackground = "#fff"; /** * Is the selection collapsed, i.e. are the start and end locations * the same? If so, our blinking caret image is shown; otherwise * the Chrome selection is shown. * @type {boolean} */ CaretBrowsing.isSelectionCollapsed = false; /** * Whether we're running on Windows. * @type {boolean} */ CaretBrowsing.isWindows = null; /** * Whether we're running on on old Qt 5.7.1. * There, we need to use -webkit-filter. * @type {boolean} */ CaretBrowsing.needsFilterPrefix = null; /** * The id returned by window.setInterval for our stopAnimation function, so * we can cancel it when we call stopAnimation again. * @type {number?} */ CaretBrowsing.animationFunctionId = null; /** * Check if a node is a control that normally allows the user to interact * with it using arrow keys. We won't override the arrow keys when such a * control has focus, the user must press Escape to do caret browsing outside * that control. * @param {Node} node A node to check. * @return {boolean} True if this node is a control that the user can * interact with using arrow keys. */ CaretBrowsing.isControlThatNeedsArrowKeys = function(node) { if (!node) { return false; } if (node === document.body || node !== document.activeElement) { return false; } if (node.constructor === HTMLSelectElement) { return true; } if (node.constructor === HTMLInputElement) { switch (node.type) { case "email": case "number": case "password": case "search": case "text": case "tel": case "url": case "": return true; // All of these are text boxes. case "datetime": case "datetime-local": case "date": case "month": case "radio": case "range": case "week": return true; // These are other input elements that use arrows. } } // Handle focusable ARIA controls. if (node.getAttribute && isFocusable(node)) { const role = node.getAttribute("role"); switch (role) { case "combobox": case "grid": case "gridcell": case "listbox": case "menu": case "menubar": case "menuitem": case "menuitemcheckbox": case "menuitemradio": case "option": case "radiogroup": case "scrollbar": case "slider": case "spinbutton": case "tab": case "tablist": case "textbox": case "tree": case "treegrid": case "treeitem": return true; } } return false; }; CaretBrowsing.injectCaretStyles = function() { const prefix = CaretBrowsing.needsFilterPrefix ? "-webkit-" : ""; const style = ` .CaretBrowsing_Caret { position: absolute; z-index: 2147483647; min-height: 1em; min-width: 0.2em; animation: blink 1s step-end infinite; --inherited-color: inherit; background-color: var(--inherited-color, #000); color: var(--inherited-color, #000); mix-blend-mode: difference; ${prefix}filter: invert(85%); } @keyframes blink { 50% { visibility: hidden; } } `; const node = document.createElement("style"); node.innerHTML = style; document.body.appendChild(node); }; /** * If there's no initial selection, set the cursor just before the * first text character in the document. */ CaretBrowsing.setInitialCursor = function() { const selectionRange = window.getSelection().toString().length; if (selectionRange === 0) { positionCaret(); } CaretBrowsing.injectCaretStyles(); CaretBrowsing.toggle(); CaretBrowsing.initiated = true; CaretBrowsing.selectionEnabled = selectionRange > 0; }; /** * Try to set the window's selection to be between the given start and end * cursors, and return whether or not it was successful. * @param {Cursor} start The start position. * @param {Cursor} end The end position. * @return {boolean} True if the selection was successfully set. */ CaretBrowsing.setAndValidateSelection = function(start, end) { const sel = window.getSelection(); sel.setBaseAndExtent(start.node, start.index, end.node, end.index); if (sel.rangeCount !== 1) { return false; } return (sel.anchorNode === start.node && sel.anchorOffset === start.index && sel.focusNode === end.node && sel.focusOffset === end.index); }; /** * Set focus to a node if it's focusable. If it's an input element, * select the text, otherwise it doesn't appear focused to the user. * Every other control behaves normally if you just call focus() on it. * @param {Node} node The node to focus. * @return {boolean} True if the node was focused. */ CaretBrowsing.setFocusToNode = function(nodeArg) { let node = nodeArg; while (node && node !== document.body) { if (isFocusable(node) && node.constructor !== HTMLIFrameElement) { node.focus(); if (node.constructor === HTMLInputElement && node.select) { node.select(); } return true; } node = node.parentNode; } return false; }; /** * Set the caret element's normal style, i.e. not when animating. */ CaretBrowsing.setCaretElementNormalStyle = function() { const element = CaretBrowsing.caretElement; element.className = "CaretBrowsing_Caret"; if (CaretBrowsing.isSelectionCollapsed) { element.style.opacity = "1.0"; } else { element.style.opacity = "0.0"; } element.style.left = `${CaretBrowsing.caretX}px`; element.style.top = `${CaretBrowsing.caretY}px`; element.style.width = `${CaretBrowsing.caretWidth}px`; element.style.height = `${CaretBrowsing.caretHeight}px`; element.style.color = CaretBrowsing.caretForeground; }; /** * Create the caret element. This assumes that caretX, caretY, * caretWidth, and caretHeight have all been set. The caret is * animated in so the user can find it when it first appears. */ CaretBrowsing.createCaretElement = function() { const element = document.createElement("div"); element.className = "CaretBrowsing_Caret"; document.body.appendChild(element); CaretBrowsing.caretElement = element; CaretBrowsing.setCaretElementNormalStyle(); }; /** * Recreate the caret element, triggering any intro animation. */ CaretBrowsing.recreateCaretElement = function() { if (CaretBrowsing.caretElement) { CaretBrowsing.caretElement.parentElement.removeChild( CaretBrowsing.caretElement); CaretBrowsing.caretElement = null; CaretBrowsing.updateIsCaretVisible(); } }; /** * Get the rectangle for a cursor position. This is tricky because * you can't get the bounding rectangle of an empty range, so this function * computes the rect by trying a range including one character earlier or * later than the cursor position. * @param {Cursor} cursor A single cursor position. * @return {{left: number, top: number, width: number, height: number}} * The bounding rectangle of the cursor. */ CaretBrowsing.getCursorRect = function(cursor) { let node = cursor.node; const index = cursor.index; const rect = { "left": 0, "top": 0, "width": 1, "height": 0, }; if (node.constructor === Text) { let left = index; let right = index; const max = node.data.length; const newRange = document.createRange(); while (left > 0 || right < max) { if (left > 0) { left--; newRange.setStart(node, left); newRange.setEnd(node, index); const rangeRect = newRange.getBoundingClientRect(); if (rangeRect && rangeRect.width && rangeRect.height) { rect.left = rangeRect.right; rect.top = rangeRect.top; rect.height = rangeRect.height; break; } } if (right < max) { right++; newRange.setStart(node, index); newRange.setEnd(node, right); const rangeRect = newRange.getBoundingClientRect(); if (rangeRect && rangeRect.width && rangeRect.height) { rect.left = rangeRect.left; rect.top = rangeRect.top; rect.height = rangeRect.height; break; } } } } else { rect.height = node.offsetHeight; while (node !== null) { rect.left += node.offsetLeft; rect.top += node.offsetTop; node = node.offsetParent; } } rect.left += window.pageXOffset; rect.top += window.pageYOffset; return rect; }; /** * Compute the new location of the caret or selection and update * the element as needed. * @param {boolean} scrollToSelection If true, will also scroll the page * to the caret / selection location. */ CaretBrowsing.updateCaretOrSelection = function(scrollToSelection) { const sel = window.getSelection(); if (sel.rangeCount === 0) { if (CaretBrowsing.caretElement) { CaretBrowsing.isSelectionCollapsed = false; CaretBrowsing.caretElement.style.opacity = "0.0"; } return; } const range = sel.getRangeAt(0); if (!range) { if (CaretBrowsing.caretElement) { CaretBrowsing.isSelectionCollapsed = false; CaretBrowsing.caretElement.style.opacity = "0.0"; } return; } if (CaretBrowsing.isControlThatNeedsArrowKeys( document.activeElement)) { let node = document.activeElement; CaretBrowsing.caretWidth = node.offsetWidth; CaretBrowsing.caretHeight = node.offsetHeight; CaretBrowsing.caretX = 0; CaretBrowsing.caretY = 0; while (node.offsetParent) { CaretBrowsing.caretX += node.offsetLeft; CaretBrowsing.caretY += node.offsetTop; node = node.offsetParent; } CaretBrowsing.isSelectionCollapsed = false; } else if (range.startOffset !== range.endOffset || range.startContainer !== range.endContainer) { const rect = range.getBoundingClientRect(); if (!rect) { return; } CaretBrowsing.caretX = rect.left + window.pageXOffset; CaretBrowsing.caretY = rect.top + window.pageYOffset; CaretBrowsing.caretWidth = rect.width; CaretBrowsing.caretHeight = rect.height; CaretBrowsing.isSelectionCollapsed = false; } else { const rect = CaretBrowsing.getCursorRect( new Cursor(range.startContainer, range.startOffset, TraverseUtil.getNodeText(range.startContainer))); CaretBrowsing.caretX = rect.left; CaretBrowsing.caretY = rect.top; CaretBrowsing.caretWidth = rect.width; CaretBrowsing.caretHeight = rect.height; CaretBrowsing.isSelectionCollapsed = true; } if (CaretBrowsing.caretElement) { const element = CaretBrowsing.caretElement; if (CaretBrowsing.isSelectionCollapsed) { element.style.opacity = "1.0"; element.style.left = `${CaretBrowsing.caretX}px`; element.style.top = `${CaretBrowsing.caretY}px`; element.style.width = `${CaretBrowsing.caretWidth}px`; element.style.height = `${CaretBrowsing.caretHeight}px`; } else { element.style.opacity = "0.0"; } } else { CaretBrowsing.createCaretElement(); } let elem = range.startContainer; if (elem.constructor === Text) { elem = elem.parentElement; } const style = window.getComputedStyle(elem); const bg = axs.utils.getBgColor(style, elem); const fg = axs.utils.getFgColor(style, elem, bg); CaretBrowsing.caretBackground = axs.color.colorToString(bg); CaretBrowsing.caretForeground = axs.color.colorToString(fg); if (scrollToSelection) { // Scroll just to the "focus" position of the selection, // the part the user is manipulating. const rect = CaretBrowsing.getCursorRect( new Cursor(sel.focusNode, sel.focusOffset, TraverseUtil.getNodeText(sel.focusNode))); const yscroll = window.pageYOffset; const pageHeight = window.innerHeight; const caretY = rect.top; const caretHeight = Math.min(rect.height, 30); if (yscroll + pageHeight < caretY + caretHeight) { window.scroll(0, (caretY + caretHeight - pageHeight + 100)); } else if (caretY < yscroll) { window.scroll(0, (caretY - 100)); } } }; CaretBrowsing.move = function(direction, granularity, count = 1) { let action = "move"; if (CaretBrowsing.selectionEnabled) { action = "extend"; } for (let i = 0; i < count; i++) { window. getSelection(). modify(action, direction, granularity); } if (CaretBrowsing.isWindows && (direction === "forward" || direction === "right") && granularity === "word") { CaretBrowsing.move("left", "character"); } }; CaretBrowsing.finishMove = function() { window.setTimeout(() => { CaretBrowsing.updateCaretOrSelection(true); }, 0); CaretBrowsing.stopAnimation(); }; CaretBrowsing.moveToBlock = function(paragraph, boundary, count = 1) { let action = "move"; if (CaretBrowsing.selectionEnabled) { action = "extend"; } for (let i = 0; i < count; i++) { window. getSelection(). modify(action, paragraph, "paragraph"); window. getSelection(). modify(action, boundary, "paragraphboundary"); } }; CaretBrowsing.toggle = function(value) { if (CaretBrowsing.forceEnabled) { CaretBrowsing.recreateCaretElement(); return; } if (value === undefined) { CaretBrowsing.isEnabled = !CaretBrowsing.isEnabled; } else { CaretBrowsing.isEnabled = value; } CaretBrowsing.updateIsCaretVisible(); }; /** * Event handler, called when the mouse is clicked. Chrome already * sets the selection when the mouse is clicked, all we need to do is * update our cursor. * @param {Event} evt The DOM event. * @return {boolean} True if the default action should be performed. */ CaretBrowsing.onClick = function() { if (!CaretBrowsing.isEnabled) { return true; } window.setTimeout(() => { CaretBrowsing.updateCaretOrSelection(false); }, 0); return true; }; /** * Update whether or not the caret is visible, based on whether caret browsing * is enabled and whether this window / iframe has focus. */ CaretBrowsing.updateIsCaretVisible = function() { CaretBrowsing.isCaretVisible = (CaretBrowsing.isEnabled && CaretBrowsing.isWindowFocused); if (CaretBrowsing.isCaretVisible && !CaretBrowsing.caretElement) { CaretBrowsing.setInitialCursor(); CaretBrowsing.updateCaretOrSelection(true); } else if (!CaretBrowsing.isCaretVisible && CaretBrowsing.caretElement) { if (CaretBrowsing.caretElement) { CaretBrowsing.isSelectionCollapsed = false; CaretBrowsing.caretElement.parentElement.removeChild( CaretBrowsing.caretElement); CaretBrowsing.caretElement = null; } } }; CaretBrowsing.onWindowFocus = function() { CaretBrowsing.isWindowFocused = true; CaretBrowsing.updateIsCaretVisible(); }; CaretBrowsing.onWindowBlur = function() { CaretBrowsing.isWindowFocused = false; CaretBrowsing.updateIsCaretVisible(); }; CaretBrowsing.startAnimation = function() { if (CaretBrowsing.caretElement) { CaretBrowsing.caretElement.style.animationIterationCount = "infinite"; } }; CaretBrowsing.stopAnimation = function() { if (CaretBrowsing.caretElement) { CaretBrowsing.caretElement.style.animationIterationCount = 0; window.clearTimeout(CaretBrowsing.animationFunctionId); CaretBrowsing.animationFunctionId = window.setTimeout(() => { CaretBrowsing.startAnimation(); }, 1000); } }; CaretBrowsing.init = function() { CaretBrowsing.isWindowFocused = document.hasFocus(); document.addEventListener("click", CaretBrowsing.onClick, false); window.addEventListener("focus", CaretBrowsing.onWindowFocus, false); window.addEventListener("blur", CaretBrowsing.onWindowBlur, false); }; window.setTimeout(() => { if (!window.caretBrowsingLoaded) { window.caretBrowsingLoaded = true; CaretBrowsing.init(); if (document.body && document.body.getAttribute("caretbrowsing") === "on") { CaretBrowsing.forceEnabled = true; CaretBrowsing.isEnabled = true; CaretBrowsing.updateIsCaretVisible(); } } }, 0); const funcs = {}; funcs.setInitialCursor = () => { if (!CaretBrowsing.initiated) { CaretBrowsing.setInitialCursor(); return CaretBrowsing.selectionEnabled; } if (window.getSelection().toString().length === 0) { positionCaret(); } CaretBrowsing.toggle(); return CaretBrowsing.selectionEnabled; }; funcs.setFlags = (flags) => { CaretBrowsing.isWindows = flags.includes("windows"); CaretBrowsing.needsFilterPrefix = flags.includes("filter-prefix"); }; funcs.disableCaret = () => { CaretBrowsing.toggle(false); }; funcs.toggle = () => { CaretBrowsing.toggle(); }; funcs.moveRight = (count = 1) => { CaretBrowsing.move("right", "character", count); CaretBrowsing.finishMove(); }; funcs.moveLeft = (count = 1) => { CaretBrowsing.move("left", "character", count); CaretBrowsing.finishMove(); }; funcs.moveDown = (count = 1) => { CaretBrowsing.move("forward", "line", count); CaretBrowsing.finishMove(); }; funcs.moveUp = (count = 1) => { CaretBrowsing.move("backward", "line", count); CaretBrowsing.finishMove(); }; funcs.moveToEndOfWord = (count = 1) => { CaretBrowsing.move("forward", "word", count); CaretBrowsing.finishMove(); }; funcs.moveToNextWord = (count = 1) => { CaretBrowsing.move("forward", "word", count); CaretBrowsing.move("right", "character"); CaretBrowsing.finishMove(); }; funcs.moveToPreviousWord = (count = 1) => { CaretBrowsing.move("backward", "word", count); CaretBrowsing.finishMove(); }; funcs.moveToStartOfLine = () => { CaretBrowsing.move("left", "lineboundary"); CaretBrowsing.finishMove(); }; funcs.moveToEndOfLine = () => { CaretBrowsing.move("right", "lineboundary"); CaretBrowsing.finishMove(); }; funcs.moveToStartOfNextBlock = (count = 1) => { CaretBrowsing.moveToBlock("forward", "backward", count); CaretBrowsing.finishMove(); }; funcs.moveToStartOfPrevBlock = (count = 1) => { CaretBrowsing.moveToBlock("backward", "backward", count); CaretBrowsing.finishMove(); }; funcs.moveToEndOfNextBlock = (count = 1) => { CaretBrowsing.moveToBlock("forward", "forward", count); CaretBrowsing.finishMove(); }; funcs.moveToEndOfPrevBlock = (count = 1) => { CaretBrowsing.moveToBlock("backward", "forward", count); CaretBrowsing.finishMove(); }; funcs.moveToStartOfDocument = () => { CaretBrowsing.move("backward", "documentboundary"); CaretBrowsing.finishMove(); }; funcs.moveToEndOfDocument = () => { CaretBrowsing.move("forward", "documentboundary"); CaretBrowsing.finishMove(); }; funcs.dropSelection = () => { window.getSelection().removeAllRanges(); }; funcs.getSelection = () => window.getSelection().toString(); funcs.toggleSelection = () => { CaretBrowsing.selectionEnabled = !CaretBrowsing.selectionEnabled; return CaretBrowsing.selectionEnabled; }; funcs.reverseSelection = () => { const sel = window.getSelection(); sel.setBaseAndExtent( sel.extentNode, sel.extentOffset, sel.baseNode, sel.baseOffset ); }; return funcs; })(); ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1579082658.0 qutebrowser-1.10.1/qutebrowser/javascript/global_wrapper.js0000644000175000017510000000044400000000000025354 0ustar00florianflorian00000000000000(function() { "use strict"; if (!window.hasOwnProperty("_qutebrowser")) { window._qutebrowser = {"initialized": {}}; } if (window._qutebrowser.initialized["{{name}}"]) { return; } {{code}} window._qutebrowser.initialized["{{name}}"] = true; })(); ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1579082658.0 qutebrowser-1.10.1/qutebrowser/javascript/greasemonkey_wrapper.js0000644000175000017510000001546700000000000026620 0ustar00florianflorian00000000000000(function() { const _qute_script_id = "__gm_{{ scriptName }}"; function GM_log(text) { console.log(text); } const GM_info = { 'script': {{ scriptInfo }}, 'scriptMetaStr': "{{ scriptMeta }}", 'scriptWillUpdate': false, 'version': "0.0.1", // so scripts don't expect exportFunction 'scriptHandler': 'Tampermonkey', }; function checkKey(key, funcName) { if (typeof key !== "string") { throw new Error(`${funcName} requires the first parameter to be of type string, not '${typeof key}'`); } } function GM_setValue(key, value) { checkKey(key, "GM_setValue"); if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") { throw new Error(`GM_setValue requires the second parameter to be of type string, number or boolean, not '${typeof value}'`); } localStorage.setItem(_qute_script_id + key, value); } function GM_getValue(key, default_) { checkKey(key, "GM_getValue"); return localStorage.getItem(_qute_script_id + key) || default_; } function GM_deleteValue(key) { checkKey(key, "GM_deleteValue"); localStorage.removeItem(_qute_script_id + key); } function GM_listValues() { const keys = []; for (let i = 0; i < localStorage.length; i++) { if (localStorage.key(i).startsWith(_qute_script_id)) { keys.push(localStorage.key(i).slice(_qute_script_id.length)); } } return keys; } function GM_openInTab(url) { window.open(url); } // Almost verbatim copy from Eric function GM_xmlhttpRequest(/* object */ details) { details.method = details.method ? details.method.toUpperCase() : "GET"; if (!details.url) { throw new Error("GM_xmlhttpRequest requires a URL."); } // build XMLHttpRequest object const oXhr = new XMLHttpRequest(); // run it if ("onreadystatechange" in details) { oXhr.onreadystatechange = function() { details.onreadystatechange(oXhr); }; } if ("onload" in details) { oXhr.onload = function() { details.onload(oXhr); }; } if ("onerror" in details) { oXhr.onerror = function () { details.onerror(oXhr); }; } oXhr.open(details.method, details.url, true); if ("headers" in details) { for (const header in details.headers) { oXhr.setRequestHeader(header, details.headers[header]); } } if ("data" in details) { oXhr.send(details.data); } else { oXhr.send(); } } function GM_addStyle(/* String */ styles) { const oStyle = document.createElement("style"); oStyle.setAttribute("type", "text/css"); oStyle.appendChild(document.createTextNode(styles)); const head = document.getElementsByTagName("head")[0]; if (head === undefined) { // no head yet, stick it wherever document.documentElement.appendChild(oStyle); } else { head.appendChild(oStyle); } } // Stub these two so that the gm4 polyfill script doesn't try to // create broken versions as attributes of window. function GM_getResourceText(caption, commandFunc, accessKey) { console.error(`${GM_info.script.name} called unimplemented GM_getResourceText`); } function GM_registerMenuCommand(caption, commandFunc, accessKey) { console.error(`${GM_info.script.name} called unimplemented GM_registerMenuCommand`); } // Mock the greasemonkey 4.0 async API. const GM = {}; GM.info = GM_info; const entries = { 'log': GM_log, 'addStyle': GM_addStyle, 'deleteValue': GM_deleteValue, 'getValue': GM_getValue, 'listValues': GM_listValues, 'openInTab': GM_openInTab, 'setValue': GM_setValue, 'xmlHttpRequest': GM_xmlhttpRequest, } for (newKey in entries) { let old = entries[newKey]; if (old && (typeof GM[newKey] == 'undefined')) { GM[newKey] = function(...args) { return new Promise((resolve, reject) => { try { resolve(old(...args)); } catch (e) { reject(e); } }); }; } }; {% if use_proxy %} /* * Try to give userscripts an environment that they expect. Which * seems to be that the global window object should look the same as * the page's one and that if a script writes to an attribute of * window it should be able to access that variable in the global * scope. * Use a Proxy to stop scripts from actually changing the global * window (that's what unsafeWindow is for). * Use the "with" statement to make the proxy provide what looks * like global scope. * * There are other Proxy functions that we may need to override. * set, get and has are definitely required. */ const unsafeWindow = window; const qute_gm_window_shadow = {}; // stores local changes to window const qute_gm_windowProxyHandler = { get: function(target, prop) { if (prop in qute_gm_window_shadow) return qute_gm_window_shadow[prop]; if (prop in target) { if (typeof target[prop] === 'function' && typeof target[prop].prototype == 'undefined') // Getting TypeError: Illegal Execution when callers try to execute // eg addEventListener from here because they were returned // unbound return target[prop].bind(target); return target[prop]; } }, set: function(target, prop, val) { return qute_gm_window_shadow[prop] = val; }, has: function(target, key) { return key in qute_gm_window_shadow || key in target; } }; const qute_gm_window_proxy = new Proxy( unsafeWindow, qute_gm_windowProxyHandler); with (qute_gm_window_proxy) { // We can't return `this` or `qute_gm_window_proxy` from // `qute_gm_window_proxy.get('window')` because the Proxy implementation // does typechecking on read-only things. So we have to shadow `window` // more conventionally here. const window = qute_gm_window_proxy; // ====== The actual user script source ====== // {{ scriptSource }} // ====== End User Script ====== // }; {% else %} // ====== The actual user script source ====== // {{ scriptSource }} // ====== End User Script ====== // {% endif %} })(); ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/javascript/history.js0000644000175000017510000001541300000000000024057 0ustar00florianflorian00000000000000/** * Copyright 2017-2020 Florian Bruhin (The-Compiler) * Copyright 2017 Imran Sobir * * This file is part of qutebrowser. * * qutebrowser 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. * * qutebrowser 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 qutebrowser. If not, see . */ "use strict"; window.loadHistory = (function() { // Date of last seen item. let lastItemDate = null; // Each request for new items includes the time of the last item and an // offset. The offset is equal to the number of items from the previous // request that had time=nextTime, and causes the next request to skip // those items to avoid duplicates. let nextTime = null; let nextOffset = 0; // The URL to fetch data from. const DATA_URL = "qute://history/data"; // Various fixed elements const EOF_MESSAGE = document.getElementById("eof"); const LOAD_LINK = document.getElementById("load"); const HIST_CONTAINER = document.getElementById("hist-container"); /** * Finds or creates the session table>tbody to which item with given date * should be added. * * @param {Date} date - the date of the item being added. * @returns {Element} the element to which new rows should be added. */ function getSessionNode(date) { // Find/create table const tableId = ["hist", date.getDate(), date.getMonth(), date.getYear()].join("-"); let table = document.getElementById(tableId); if (table === null) { table = document.createElement("table"); table.id = tableId; // Caption contains human-readable date const caption = document.createElement("caption"); caption.className = "date"; const options = { "weekday": "long", "year": "numeric", "month": "long", "day": "numeric", }; caption.innerHTML = date.toLocaleDateString("en-US", options); table.appendChild(caption); // Add table to page HIST_CONTAINER.appendChild(table); } // Find/create tbody let tbody = table.lastChild; if (tbody.tagName !== "TBODY") { tbody = document.createElement("tbody"); table.appendChild(tbody); } // Create session-separator and new tbody if necessary if (tbody.lastChild !== null && lastItemDate !== null && window.GAP_INTERVAL > 0) { const interval = lastItemDate.getTime() - date.getTime(); if (interval > window.GAP_INTERVAL) { // Add session-separator const sessionSeparator = document.createElement("td"); sessionSeparator.className = "session-separator"; sessionSeparator.colSpan = 2; sessionSeparator.innerHTML = "§"; table.appendChild(document.createElement("tr")); table.lastChild.appendChild(sessionSeparator); // Create new tbody tbody = document.createElement("tbody"); table.appendChild(tbody); } } return tbody; } /** * Given a history item, create and return for it. * * @param {string} itemUrl - The url for this item. * @param {string} itemTitle - The title for this item. * @param {string} itemTime - The formatted time for this item. * @returns {Element} the completed tr. */ function makeHistoryRow(itemUrl, itemTitle, itemTime) { const row = document.createElement("tr"); const title = document.createElement("td"); title.className = "title"; const link = document.createElement("a"); link.href = itemUrl; link.innerHTML = itemTitle; // Properly escaped in qutescheme.py const host = document.createElement("span"); host.className = "hostname"; host.innerHTML = link.hostname; title.appendChild(link); title.appendChild(host); const time = document.createElement("td"); time.className = "time"; time.innerHTML = itemTime; row.appendChild(title); row.appendChild(time); return row; } /** * Get JSON from given URL. * * @param {string} url - the url to fetch data from. * @param {function} callback - the function to callback with data. * @returns {void} */ function getJSON(url, callback) { const xhr = new XMLHttpRequest(); xhr.open("GET", url, true); xhr.responseType = "json"; xhr.onload = () => { const status = xhr.status; callback(status, xhr.response); }; xhr.send(); } /** * Receive history data from qute://history/data. * * @param {Number} status - The status of the query. * @param {Array} history - History data. * @returns {void} */ function receiveHistory(status, history) { if (history === null) { return; } if (history.length === 0) { // Reached end of history window.onscroll = null; EOF_MESSAGE.style.display = "block"; LOAD_LINK.style.display = "none"; return; } nextTime = history[history.length - 1].time; nextOffset = 0; for (let i = 0, len = history.length; i < len; i++) { const item = history[i]; // python's time.time returns seconds, but js Date expects ms const currentItemDate = new Date(item.time * 1000); getSessionNode(currentItemDate).appendChild(makeHistoryRow( item.url, item.title, currentItemDate.toLocaleTimeString() )); lastItemDate = currentItemDate; if (item.time === nextTime) { nextOffset++; } } } /** * Load new history. * @return {void} */ function loadHistory() { let url = DATA_URL.concat("?offset=", nextOffset.toString()); if (nextTime === null) { getJSON(url, receiveHistory); } else { url = url.concat("&start_time=", nextTime.toString()); getJSON(url, receiveHistory); } } return loadHistory; })(); ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158375.0 qutebrowser-1.10.1/qutebrowser/javascript/pac_utils.js0000644000175000017510000001745600000000000024352 0ustar00florianflorian00000000000000/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Akhil Arora * Tomi Leppikangas * Darin Fisher * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* Script for Proxy Auto Config in the new world order. - Gagan Saksena 04/24/00 */ function dnsDomainIs(host, domain) { return (host.length >= domain.length && host.substring(host.length - domain.length) == domain); } function dnsDomainLevels(host) { return host.split('.').length-1; } function convert_addr(ipchars) { var bytes = ipchars.split('.'); var result = ((bytes[0] & 0xff) << 24) | ((bytes[1] & 0xff) << 16) | ((bytes[2] & 0xff) << 8) | (bytes[3] & 0xff); return result; } function isInNet(ipaddr, pattern, maskstr) { var test = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/ .exec(ipaddr); if (test == null) { ipaddr = dnsResolve(ipaddr); if (ipaddr == null) return false; } else if (test[1] > 255 || test[2] > 255 || test[3] > 255 || test[4] > 255) { return false; // not an IP address } var host = convert_addr(ipaddr); var pat = convert_addr(pattern); var mask = convert_addr(maskstr); return ((host & mask) == (pat & mask)); } function isPlainHostName(host) { return (host.search('\\.') == -1); } function isResolvable(host) { var ip = dnsResolve(host); return (ip != null); } function localHostOrDomainIs(host, hostdom) { return (host == hostdom) || (hostdom.lastIndexOf(host + '.', 0) == 0); } function shExpMatch(url, pattern) { pattern = pattern.replace(/\./g, '\\.'); pattern = pattern.replace(/\*/g, '.*'); pattern = pattern.replace(/\?/g, '.'); var newRe = new RegExp('^'+pattern+'$'); return newRe.test(url); } var wdays = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6}; var months = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11}; function weekdayRange() { function getDay(weekday) { if (weekday in wdays) { return wdays[weekday]; } return -1; } var date = new Date(); var argc = arguments.length; var wday; if (argc < 1) return false; if (arguments[argc - 1] == 'GMT') { argc--; wday = date.getUTCDay(); } else { wday = date.getDay(); } var wd1 = getDay(arguments[0]); var wd2 = (argc == 2) ? getDay(arguments[1]) : wd1; return (wd1 == -1 || wd2 == -1) ? false : (wd1 <= wday && wday <= wd2); } function dateRange() { function getMonth(name) { if (name in months) { return months[name]; } return -1; } var date = new Date(); var argc = arguments.length; if (argc < 1) { return false; } var isGMT = (arguments[argc - 1] == 'GMT'); if (isGMT) { argc--; } // function will work even without explict handling of this case if (argc == 1) { var tmp = parseInt(arguments[0]); if (isNaN(tmp)) { return ((isGMT ? date.getUTCMonth() : date.getMonth()) == getMonth(arguments[0])); } else if (tmp < 32) { return ((isGMT ? date.getUTCDate() : date.getDate()) == tmp); } else { return ((isGMT ? date.getUTCFullYear() : date.getFullYear()) == tmp); } } var year = date.getFullYear(); var date1, date2; date1 = new Date(year, 0, 1, 0, 0, 0); date2 = new Date(year, 11, 31, 23, 59, 59); var adjustMonth = false; for (var i = 0; i < (argc >> 1); i++) { var tmp = parseInt(arguments[i]); if (isNaN(tmp)) { var mon = getMonth(arguments[i]); date1.setMonth(mon); } else if (tmp < 32) { adjustMonth = (argc <= 2); date1.setDate(tmp); } else { date1.setFullYear(tmp); } } for (var i = (argc >> 1); i < argc; i++) { var tmp = parseInt(arguments[i]); if (isNaN(tmp)) { var mon = getMonth(arguments[i]); date2.setMonth(mon); } else if (tmp < 32) { date2.setDate(tmp); } else { date2.setFullYear(tmp); } } if (adjustMonth) { date1.setMonth(date.getMonth()); date2.setMonth(date.getMonth()); } if (isGMT) { var tmp = date; tmp.setFullYear(date.getUTCFullYear()); tmp.setMonth(date.getUTCMonth()); tmp.setDate(date.getUTCDate()); tmp.setHours(date.getUTCHours()); tmp.setMinutes(date.getUTCMinutes()); tmp.setSeconds(date.getUTCSeconds()); date = tmp; } return ((date1 <= date) && (date <= date2)); } function timeRange() { var argc = arguments.length; var date = new Date(); var isGMT= false; if (argc < 1) { return false; } if (arguments[argc - 1] == 'GMT') { isGMT = true; argc--; } var hour = isGMT ? date.getUTCHours() : date.getHours(); var date1, date2; date1 = new Date(); date2 = new Date(); if (argc == 1) { return (hour == arguments[0]); } else if (argc == 2) { return ((arguments[0] <= hour) && (hour <= arguments[1])); } else { switch (argc) { case 6: date1.setSeconds(arguments[2]); date2.setSeconds(arguments[5]); case 4: var middle = argc >> 1; date1.setHours(arguments[0]); date1.setMinutes(arguments[1]); date2.setHours(arguments[middle]); date2.setMinutes(arguments[middle + 1]); if (middle == 2) { date2.setSeconds(59); } break; default: throw 'timeRange: bad number of arguments' } } if (isGMT) { date.setFullYear(date.getUTCFullYear()); date.setMonth(date.getUTCMonth()); date.setDate(date.getUTCDate()); date.setHours(date.getUTCHours()); date.setMinutes(date.getUTCMinutes()); date.setSeconds(date.getUTCSeconds()); } return ((date1 <= date) && (date <= date2)); } ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/javascript/position_caret.js0000644000175000017510000000773500000000000025410 0ustar00florianflorian00000000000000/** * Copyright 2015-2020 Florian Bruhin (The Compiler) * Copyright 2015 Artur Shaik * * This file is part of qutebrowser. * * qutebrowser 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. * * qutebrowser 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 qutebrowser. If not, see . */ /** * Snippet to position caret at top of the page when caret mode is enabled. * Some code was borrowed from: * * https://github.com/1995eaton/chromium-vim/blob/master/content_scripts/dom.js * https://github.com/1995eaton/chromium-vim/blob/master/content_scripts/visual.js */ "use strict"; (function() { // FIXME:qtwebengine integrate this with other window._qutebrowser code? function isElementInViewport(node) { // eslint-disable-line complexity let i; let boundingRect = (node.getClientRects()[0] || node.getBoundingClientRect()); if (boundingRect.width <= 1 && boundingRect.height <= 1) { const rects = node.getClientRects(); for (i = 0; i < rects.length; i++) { if (rects[i].width > rects[0].height && rects[i].height > rects[0].height) { boundingRect = rects[i]; } } } if (boundingRect === undefined) { return null; } if (boundingRect.top > innerHeight || boundingRect.left > innerWidth) { return null; } if (boundingRect.width <= 1 || boundingRect.height <= 1) { const children = node.children; let visibleChildNode = false; for (i = 0; i < children.length; ++i) { boundingRect = (children[i].getClientRects()[0] || children[i].getBoundingClientRect()); if (boundingRect.width > 1 && boundingRect.height > 1) { visibleChildNode = true; break; } } if (visibleChildNode === false) { return null; } } if (boundingRect.top + boundingRect.height < 10 || boundingRect.left + boundingRect.width < -10) { return null; } const computedStyle = window.getComputedStyle(node, null); if (computedStyle.visibility !== "visible" || computedStyle.display === "none" || node.hasAttribute("disabled") || parseInt(computedStyle.width, 10) === 0 || parseInt(computedStyle.height, 10) === 0) { return null; } return boundingRect.top >= -20; } function positionCaret() { const walker = document.createTreeWalker(document.body, 4, null); let node; const textNodes = []; let el; while ((node = walker.nextNode())) { if (node.nodeType === 3 && node.data.trim() !== "") { textNodes.push(node); } } for (let i = 0; i < textNodes.length; i++) { const element = textNodes[i].parentElement; if (isElementInViewport(element.parentElement)) { el = element; break; } } if (el !== undefined) { const range = document.createRange(); range.setStart(el, 0); range.setEnd(el, 0); const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } } positionCaret(); })(); ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/javascript/print.js0000644000175000017510000000207200000000000023507 0ustar00florianflorian00000000000000/** * Copyright 2018-2020 Florian Bruhin (The Compiler) * * This file is part of qutebrowser. * * qutebrowser 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. * * qutebrowser 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 qutebrowser. If not, see . */ /* * this is a hack based on the QupZilla solution, https://github.com/QupZilla/qupzilla/commit/d3f0d766fb052dc504de2426d42f235d96b5eb60 * * We go to a qute://print which triggers the print, then we cancel the request. */ "use strict"; window.print = function() { window.location = "qute://print"; }; ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/javascript/scroll.js0000644000175000017510000000444000000000000023652 0ustar00florianflorian00000000000000/** * Copyright 2016-2020 Florian Bruhin (The Compiler) * * This file is part of qutebrowser. * * qutebrowser 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. * * qutebrowser 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 qutebrowser. If not, see . */ "use strict"; window._qutebrowser.scroll = (function() { const funcs = {}; funcs.to_perc = (x, y) => { let x_px = window.scrollX; let y_px = window.scrollY; const width = Math.max( document.body.scrollWidth, document.body.offsetWidth, document.documentElement.scrollWidth, document.documentElement.offsetWidth ); const height = Math.max( document.body.scrollHeight, document.body.offsetHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight ); if (x !== undefined) { x_px = (width - window.innerWidth) / 100 * x; } if (y !== undefined) { y_px = (height - window.innerHeight) / 100 * y; } /* console.log(JSON.stringify({ "x": x, "window.scrollX": window.scrollX, "window.innerWidth": window.innerWidth, "elem.scrollWidth": document.documentElement.scrollWidth, "x_px": x_px, "y": y, "window.scrollY": window.scrollY, "window.innerHeight": window.innerHeight, "elem.scrollHeight": document.documentElement.scrollHeight, "y_px": y_px, })); */ window.scroll(x_px, y_px); }; funcs.delta_page = (x, y) => { const dx = window.innerWidth * x; const dy = window.innerHeight * y; window.scrollBy(dx, dy); }; return funcs; })(); ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/javascript/stylesheet.js0000644000175000017510000001233100000000000024543 0ustar00florianflorian00000000000000/** * Copyright 2017-2020 Florian Bruhin (The Compiler) * Copyright 2017 Ulrik de Muelenaere * * This file is part of qutebrowser. * * qutebrowser 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. * * qutebrowser 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 qutebrowser. If not, see . */ "use strict"; window._qutebrowser.stylesheet = (function() { if (window._qutebrowser.stylesheet) { return window._qutebrowser.stylesheet; } const funcs = {}; const xhtml_ns = "http://www.w3.org/1999/xhtml"; const svg_ns = "http://www.w3.org/2000/svg"; let root_elem; let style_elem; let css_content = ""; let root_observer; let initialized = false; // Watch for rewrites of the root element and changes to its children, // then move the stylesheet to the end. Partially inspired by Stylus: // https://github.com/openstyles/stylus/blob/1.1.4.2/content/apply.js#L235-L355 function watch_root() { if (!document.documentElement) { root_observer.observe(document, {"childList": true}); return; } if (root_elem !== document.documentElement) { root_elem = document.documentElement; root_observer.disconnect(); root_observer.observe(document, {"childList": true}); root_observer.observe(root_elem, {"childList": true}); } if (style_elem !== root_elem.lastChild) { root_elem.appendChild(style_elem); } } function create_style() { let ns = xhtml_ns; if (document.documentElement && document.documentElement.namespaceURI === svg_ns) { ns = svg_ns; } style_elem = document.createElementNS(ns, "style"); style_elem.textContent = css_content; root_observer = new MutationObserver(watch_root); watch_root(); } // We should only inject the stylesheet if the document already has style // information associated with it. Otherwise we wait until the browser // rewrites it to an XHTML document showing the document tree. As a // starting point for exploring the relevant code in Chromium, see // https://github.com/qt/qtwebengine-chromium/blob/cfe8c60/chromium/third_party/WebKit/Source/core/xml/parser/XMLDocumentParser.cpp#L1539-L1540 function check_style(node) { const stylesheet = node.nodeType === Node.PROCESSING_INSTRUCTION_NODE && node.target === "xml-stylesheet" && node.parentNode === document; const known_ns = node.nodeType === Node.ELEMENT_NODE && (node.namespaceURI === xhtml_ns || node.namespaceURI === svg_ns); return stylesheet || known_ns; } function init() { initialized = true; // Chromium will not rewrite a document inside a frame, so add the // stylesheet even if the document is unstyled. if (window !== window.top) { create_style(); return; } const iter = document.createNodeIterator(document, NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_ELEMENT); let node; while ((node = iter.nextNode())) { if (check_style(node)) { create_style(); return; } } const style_observer = new MutationObserver((mutations) => { for (const mutation of mutations) { const nodes = mutation.addedNodes; for (let i = 0; i < nodes.length; ++i) { if (check_style(nodes[i])) { create_style(); style_observer.disconnect(); return; } } } }); style_observer.observe(document, {"childList": true, "subtree": true}); } funcs.set_css = function(css) { if (!initialized) { init(); } if (style_elem) { style_elem.textContent = css; // The browser seems to rewrite the document in same-origin frames // without notifying the mutation observer. Ensure that the // stylesheet is in the current document. watch_root(); } else { css_content = css; } // Propagate the new CSS to all child frames. // FIXME:qtwebengine This does not work for cross-origin frames. for (let i = 0; i < window.frames.length; ++i) { const frame = window.frames[i]; if (frame._qutebrowser && frame._qutebrowser.stylesheet) { frame._qutebrowser.stylesheet.set_css(css); } } }; return funcs; })(); ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/javascript/webelem.js0000644000175000017510000003247600000000000024006 0ustar00florianflorian00000000000000/** * Copyright 2016-2020 Florian Bruhin (The Compiler) * * This file is part of qutebrowser. * * qutebrowser 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. * * qutebrowser 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 qutebrowser. If not, see . */ /** * The connection for web elements between Python and Javascript works like * this: * * - Python calls into Javascript and invokes a function to find elements (one * of the find_* functions). * - Javascript gets the requested element, and calls serialize_elem on it. * - serialize_elem saves the javascript element object in "elements", gets some * attributes from the element, and assigns an ID (index into 'elements') to * it. * - Python gets this information and constructs a Python wrapper object with * the information it got right away, and the ID. * - When Python wants to modify an element, it calls javascript again with the * element ID. * - Javascript gets the element from the elements array, and modifies it. */ "use strict"; window._qutebrowser.webelem = (function() { const funcs = {}; const elements = []; function get_frame_offset(frame) { if (frame === null) { // Dummy object with zero offset return { "top": 0, "right": 0, "bottom": 0, "left": 0, "height": 0, "width": 0, }; } return frame.frameElement.getBoundingClientRect(); } // Add an offset rect to a base rect, for use with frames function add_offset_rect(base, offset) { return { "top": base.top + offset.top, "left": base.left + offset.left, "bottom": base.bottom + offset.top, "right": base.right + offset.left, "height": base.height, "width": base.width, }; } function get_caret_position(elem, frame) { // With older Chromium versions (and QtWebKit), InvalidStateError will // be thrown if elem doesn't have selectionStart. // With newer Chromium versions (>= Qt 5.10), we get null. try { return elem.selectionStart; } catch (err) { if ((err instanceof DOMException || (frame && err instanceof frame.DOMException)) && err.name === "InvalidStateError") { // nothing to do, caret_position is already null } else { // not the droid we're looking for throw err; } } return null; } function serialize_elem(elem, frame = null) { if (!elem) { return null; } const id = elements.length; elements[id] = elem; const caret_position = get_caret_position(elem, frame); const out = { "id": id, "rects": [], // Gets filled up later "caret_position": caret_position, }; // Deal with various fun things which can happen in form elements // https://github.com/qutebrowser/qutebrowser/issues/2569 // https://github.com/qutebrowser/qutebrowser/issues/2877 // https://stackoverflow.com/q/22942689/2085149 if (typeof elem.tagName === "string") { out.tag_name = elem.tagName; } else if (typeof elem.nodeName === "string") { out.tag_name = elem.nodeName; } else { out.tag_name = ""; } if (typeof elem.className === "string") { out.class_name = elem.className; } else { // e.g. SVG elements out.class_name = ""; } if (typeof elem.value === "string" || typeof elem.value === "number") { out.value = elem.value; } else { out.value = ""; } if (typeof elem.outerHTML === "string") { out.outer_xml = elem.outerHTML; } else { out.outer_xml = ""; } if (typeof elem.textContent === "string") { out.text = elem.textContent; } else if (typeof elem.text === "string") { out.text = elem.text; } // else: don't add the text at all const attributes = {}; for (let i = 0; i < elem.attributes.length; ++i) { const attr = elem.attributes[i]; attributes[attr.name] = attr.value; } out.attributes = attributes; const client_rects = elem.getClientRects(); const frame_offset_rect = get_frame_offset(frame); for (let k = 0; k < client_rects.length; ++k) { const rect = client_rects[k]; out.rects.push( add_offset_rect(rect, frame_offset_rect) ); } // console.log(JSON.stringify(out)); return out; } function is_visible(elem, frame = null) { // Adopted from vimperator: // https://github.com/vimperator/vimperator-labs/blob/vimperator-3.14.0/common/content/hints.js#L259-L285 // FIXME:qtwebengine we might need something more sophisticated like // the cVim implementation here? // https://github.com/1995eaton/chromium-vim/blob/1.2.85/content_scripts/dom.js#L74-L134 const win = elem.ownerDocument.defaultView; const offset_rect = get_frame_offset(frame); let rect = add_offset_rect(elem.getBoundingClientRect(), offset_rect); if (!rect || rect.top > window.innerHeight || rect.bottom < 0 || rect.left > window.innerWidth || rect.right < 0) { return false; } rect = elem.getClientRects()[0]; if (!rect) { return false; } const style = win.getComputedStyle(elem, null); if (style.getPropertyValue("visibility") !== "visible" || style.getPropertyValue("display") === "none" || style.getPropertyValue("opacity") === "0") { // FIXME:qtwebengine do we need this handling? // visibility and display style are misleading for area tags and // they get "display: none" by default. // See https://github.com/vimperator/vimperator-labs/issues/236 if (elem.nodeName.toLowerCase() !== "area" && !elem.classList.contains("ace_text-input")) { return false; } } return true; } // Returns true if the iframe is accessible without // cross domain errors, else false. function iframe_same_domain(frame) { try { frame.document; // eslint-disable-line no-unused-expressions return true; } catch (err) { return false; } } funcs.find_css = (selector, only_visible) => { let elems; try { elems = document.querySelectorAll(selector); } catch (ex) { return {"success": false, "error": ex.toString()}; } const subelem_frames = window.frames; const out = []; for (let i = 0; i < elems.length; ++i) { if (!only_visible || is_visible(elems[i])) { out.push(serialize_elem(elems[i])); } } // Recurse into frames and add them for (let i = 0; i < subelem_frames.length; i++) { if (iframe_same_domain(subelem_frames[i])) { const frame = subelem_frames[i]; const subelems = frame.document. querySelectorAll(selector); for (let elem_num = 0; elem_num < subelems.length; ++elem_num) { if (!only_visible || is_visible(subelems[elem_num], frame)) { out.push(serialize_elem(subelems[elem_num], frame)); } } } } return {"success": true, "result": out}; }; // Runs a function in a frame until the result is not null, then return // If no frame succeeds, return null function run_frames(func) { for (let i = 0; i < window.frames.length; ++i) { const frame = window.frames[i]; if (iframe_same_domain(frame)) { const result = func(frame); if (result) { return result; } } } return null; } funcs.find_id = (id) => { const elem = document.getElementById(id); if (elem) { return serialize_elem(elem); } const serialized_elem = run_frames((frame) => { const element = frame.window.document.getElementById(id); return serialize_elem(element, frame); }); if (serialized_elem) { return serialized_elem; } return null; }; // Check if elem is an iframe, and if so, return the result of func on it. // If no iframes match, return null function call_if_frame(elem, func) { // Check if elem is a frame, and if so, call func on the window if ("contentWindow" in elem) { const frame = elem.contentWindow; if (iframe_same_domain(frame) && "frameElement" in elem.contentWindow) { return func(frame); } } return null; } funcs.find_focused = () => { const elem = document.activeElement; if (!elem || elem === document.body) { // "When there is no selection, the active element is the page's // or null." return null; } // Check if we got an iframe, and if so, recurse inside of it const frame_elem = call_if_frame(elem, (frame) => serialize_elem(frame.document.activeElement, frame)); if (frame_elem !== null) { return frame_elem; } return serialize_elem(elem); }; funcs.find_at_pos = (x, y) => { const elem = document.elementFromPoint(x, y); // Check if we got an iframe, and if so, recurse inside of it const frame_elem = call_if_frame(elem, (frame) => { // Subtract offsets due to being in an iframe const frame_offset_rect = frame.frameElement.getBoundingClientRect(); return serialize_elem(frame.document. elementFromPoint(x - frame_offset_rect.left, y - frame_offset_rect.top), frame); }); if (frame_elem !== null) { return frame_elem; } return serialize_elem(elem); }; // Function for returning a selection or focus to python (so we can click // it). If nothing is selected but there is something focused, returns // "focused" funcs.find_selected_focused_link = () => { const elem = window.getSelection().anchorNode; if (elem) { return serialize_elem(elem.parentNode); } const serialized_frame_elem = run_frames((frame) => { const node = frame.window.getSelection().anchorNode; if (node) { return serialize_elem(node.parentNode, frame); } return null; }); if (serialized_frame_elem) { return serialized_frame_elem; } return funcs.find_focused() && "focused"; }; funcs.set_value = (id, value) => { elements[id].value = value; }; funcs.insert_text = (id, text) => { const elem = elements[id]; elem.focus(); document.execCommand("insertText", false, text); }; funcs.dispatch_event = (id, event, bubbles = false, cancelable = false, composed = false) => { const elem = elements[id]; elem.dispatchEvent( new Event(event, {"bubbles": bubbles, "cancelable": cancelable, "composed": composed})); }; funcs.set_attribute = (id, name, value) => { elements[id].setAttribute(name, value); }; funcs.remove_blank_target = (id) => { let elem = elements[id]; while (elem !== null) { const tag = elem.tagName.toLowerCase(); if (tag === "a" || tag === "area") { if (elem.getAttribute("target") === "_blank") { elem.setAttribute("target", "_top"); } break; } elem = elem.parentElement; } }; funcs.click = (id) => { const elem = elements[id]; elem.click(); }; funcs.focus = (id) => { const elem = elements[id]; elem.focus(); }; funcs.move_cursor_to_end = (id) => { const elem = elements[id]; elem.selectionStart = elem.value.length; elem.selectionEnd = elem.value.length; }; funcs.delete = (id) => { const elem = elements[id]; elem.remove(); }; return funcs; })(); ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1581772733.904756 qutebrowser-1.10.1/qutebrowser/keyinput/0000755000175000017510000000000000000000000021516 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/keyinput/__init__.py0000644000175000017510000000153200000000000023630 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Modules related to keyboard input and mode handling.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/keyinput/basekeyparser.py0000644000175000017510000003175400000000000024742 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Base class for vim-like key sequence parser.""" import string import types import typing import attr from PyQt5.QtCore import pyqtSignal, QObject, Qt from PyQt5.QtGui import QKeySequence, QKeyEvent from PyQt5.QtWidgets import QWidget from qutebrowser.config import config from qutebrowser.utils import usertypes, log, utils from qutebrowser.keyinput import keyutils @attr.s(frozen=True) class MatchResult: """The result of matching a keybinding.""" match_type = attr.ib() # type: QKeySequence.SequenceMatch command = attr.ib() # type: typing.Optional[str] sequence = attr.ib() # type: keyutils.KeySequence def __attrs_post_init__(self) -> None: if self.match_type == QKeySequence.ExactMatch: assert self.command is not None else: assert self.command is None class BindingTrie: """Helper class for key parser. Represents a set of bindings. Every BindingTree will either contain children or a command (for leaf nodes). The only exception is the root BindingNode, if there are no bindings at all. From the outside, this class works similar to a mapping of keyutils.KeySequence to str. Doing trie[sequence] = 'command' adds a binding, and so does calling .update() with a mapping. Additionally, a "matches" method can be used to do partial matching. However, some mapping methods are not (yet) implemented: - __getitem__ (use matches() instead) - __len__ - __iter__ - __delitem__ Attributes: children: A mapping from KeyInfo to children BindingTries. command: Command associated with this trie node. """ __slots__ = 'children', 'command' def __init__(self) -> None: self.children = { } # type: typing.MutableMapping[keyutils.KeyInfo, BindingTrie] self.command = None # type: typing.Optional[str] def __setitem__(self, sequence: keyutils.KeySequence, command: str) -> None: node = self for key in sequence: if key not in node.children: node.children[key] = BindingTrie() node = node.children[key] node.command = command def __contains__(self, sequence: keyutils.KeySequence) -> bool: return self.matches(sequence).match_type == QKeySequence.ExactMatch def __repr__(self) -> str: return utils.get_repr(self, children=self.children, command=self.command) def update(self, mapping: typing.Mapping) -> None: """Add data from the given mapping to the trie.""" for key in mapping: self[key] = mapping[key] def matches(self, sequence: keyutils.KeySequence) -> MatchResult: """Try to match a given keystring with any bound keychain. Args: sequence: The key sequence to match. Return: A MatchResult object. """ node = self for key in sequence: try: node = node.children[key] except KeyError: return MatchResult(match_type=QKeySequence.NoMatch, command=None, sequence=sequence) if node.command is not None: return MatchResult(match_type=QKeySequence.ExactMatch, command=node.command, sequence=sequence) elif node.children: return MatchResult(match_type=QKeySequence.PartialMatch, command=None, sequence=sequence) else: # This can only happen when there are no bindings at all. return MatchResult(match_type=QKeySequence.NoMatch, command=None, sequence=sequence) class BaseKeyParser(QObject): """Parser for vim-like key sequences and shortcuts. Not intended to be instantiated directly. Subclasses have to override execute() to do whatever they want to. Class Attributes: Match: types of a match between a binding and the keystring. partial: No keychain matched yet, but it's still possible in the future. definitive: Keychain matches exactly. none: No more matches possible. do_log: Whether to log keypresses or not. passthrough: Whether unbound keys should be passed through with this handler. supports_count: Whether count is supported. Attributes: bindings: Bound key bindings _win_id: The window ID this keyparser is associated with. _sequence: The currently entered key sequence _modename: The name of the input mode associated with this keyparser. Signals: keystring_updated: Emitted when the keystring is updated. arg: New keystring. request_leave: Emitted to request leaving a mode. arg 0: Mode to leave. arg 1: Reason for leaving. arg 2: Ignore the request if we're not in that mode """ keystring_updated = pyqtSignal(str) request_leave = pyqtSignal(usertypes.KeyMode, str, bool) do_log = True passthrough = False supports_count = True def __init__(self, win_id: int, parent: QWidget = None) -> None: super().__init__(parent) self._win_id = win_id self._modename = None self._sequence = keyutils.KeySequence() self._count = '' self.bindings = BindingTrie() config.instance.changed.connect(self._on_config_changed) def __repr__(self) -> str: return utils.get_repr(self) def _debug_log(self, message: str) -> None: """Log a message to the debug log if logging is active. Args: message: The message to log. """ if self.do_log: log.keyboard.debug(message) def _match_key(self, sequence: keyutils.KeySequence) -> MatchResult: """Try to match a given keystring with any bound keychain. Args: sequence: The command string to find. Return: A tuple (matchtype, binding). matchtype: Match.definitive, Match.partial or Match.none. binding: - None with Match.partial/Match.none. - The found binding with Match.definitive. """ assert sequence assert not isinstance(sequence, str) return self.bindings.matches(sequence) def _match_without_modifiers( self, sequence: keyutils.KeySequence) -> MatchResult: """Try to match a key with optional modifiers stripped.""" self._debug_log("Trying match without modifiers") sequence = sequence.strip_modifiers() return self._match_key(sequence) def _match_key_mapping( self, sequence: keyutils.KeySequence) -> MatchResult: """Try to match a key in bindings.key_mappings.""" self._debug_log("Trying match with key_mappings") mapped = sequence.with_mappings( types.MappingProxyType(config.cache['bindings.key_mappings'])) if sequence != mapped: self._debug_log("Mapped {} -> {}".format( sequence, mapped)) return self._match_key(mapped) return MatchResult(match_type=QKeySequence.NoMatch, command=None, sequence=sequence) def _match_count(self, sequence: keyutils.KeySequence, dry_run: bool) -> bool: """Try to match a key as count.""" txt = str(sequence[-1]) # To account for sequences changed above. if (txt in string.digits and self.supports_count and not (not self._count and txt == '0')): self._debug_log("Trying match as count") assert len(txt) == 1, txt if not dry_run: self._count += txt self.keystring_updated.emit(self._count + str(self._sequence)) return True return False def handle(self, e: QKeyEvent, *, dry_run: bool = False) -> QKeySequence.SequenceMatch: """Handle a new keypress. Separate the keypress into count/command, then check if it matches any possible command, and either run the command, ignore it, or display an error. Args: e: the KeyPressEvent from Qt. dry_run: Don't actually execute anything, only check whether there would be a match. Return: A QKeySequence match. """ key = Qt.Key(e.key()) txt = str(keyutils.KeyInfo.from_event(e)) self._debug_log("Got key: 0x{:x} / modifiers: 0x{:x} / text: '{}' / " "dry_run {}".format(key, int(e.modifiers()), txt, dry_run)) if keyutils.is_modifier_key(key): self._debug_log("Ignoring, only modifier") return QKeySequence.NoMatch try: sequence = self._sequence.append_event(e) except keyutils.KeyParseError as ex: self._debug_log("{} Aborting keychain.".format(ex)) self.clear_keystring() return QKeySequence.NoMatch result = self._match_key(sequence) del sequence # Enforce code below to use the modified result.sequence if result.match_type == QKeySequence.NoMatch: result = self._match_without_modifiers(result.sequence) if result.match_type == QKeySequence.NoMatch: result = self._match_key_mapping(result.sequence) if result.match_type == QKeySequence.NoMatch: was_count = self._match_count(result.sequence, dry_run) if was_count: return QKeySequence.ExactMatch if dry_run: return result.match_type self._sequence = result.sequence if result.match_type == QKeySequence.ExactMatch: assert result.command is not None self._debug_log("Definitive match for '{}'.".format( result.sequence)) count = int(self._count) if self._count else None self.clear_keystring() self.execute(result.command, count) elif result.match_type == QKeySequence.PartialMatch: self._debug_log("No match for '{}' (added {})".format( result.sequence, txt)) self.keystring_updated.emit(self._count + str(result.sequence)) elif result.match_type == QKeySequence.NoMatch: self._debug_log("Giving up with '{}', no matches".format( result.sequence)) self.clear_keystring() else: raise utils.Unreachable("Invalid match value {!r}".format( result.match_type)) return result.match_type @config.change_filter('bindings') def _on_config_changed(self) -> None: self._read_config() def _read_config(self, modename: str = None) -> None: """Read the configuration. Config format: key = command, e.g.: = quit Args: modename: Name of the mode to use. """ if modename is None: if self._modename is None: raise ValueError("read_config called with no mode given, but " "None defined so far!") modename = self._modename else: self._modename = modename self.bindings = BindingTrie() for key, cmd in config.key_instance.get_bindings_for(modename).items(): assert not isinstance(key, str), key assert cmd self.bindings[key] = cmd def execute(self, cmdstr: str, count: int = None) -> None: """Handle a completed keychain. Args: cmdstr: The command to execute as a string. count: The count if given. """ raise NotImplementedError def clear_keystring(self) -> None: """Clear the currently entered key sequence.""" if self._sequence: self._debug_log("Clearing keystring (was: {}).".format( self._sequence)) self._sequence = keyutils.KeySequence() self._count = '' self.keystring_updated.emit('') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/keyinput/eventfilter.py0000644000175000017510000000752500000000000024430 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Global Qt event filter which dispatches key events.""" from PyQt5.QtCore import pyqtSlot, QObject, QEvent from PyQt5.QtGui import QKeyEvent, QWindow from PyQt5.QtWidgets import QApplication from qutebrowser.keyinput import modeman from qutebrowser.misc import quitter from qutebrowser.utils import objreg class EventFilter(QObject): """Global Qt event filter. Attributes: _activated: Whether the EventFilter is currently active. _handlers; A {QEvent.Type: callable} dict with the handlers for an event. """ def __init__(self, parent: QObject = None) -> None: super().__init__(parent) self._activated = True self._handlers = { QEvent.KeyPress: self._handle_key_event, QEvent.KeyRelease: self._handle_key_event, QEvent.ShortcutOverride: self._handle_key_event, } def install(self) -> None: QApplication.instance().installEventFilter(self) @pyqtSlot() def shutdown(self) -> None: QApplication.instance().removeEventFilter(self) def _handle_key_event(self, event: QKeyEvent) -> bool: """Handle a key press/release event. Args: event: The QEvent which is about to be delivered. Return: True if the event should be filtered, False if it's passed through. """ active_window = QApplication.instance().activeWindow() if active_window not in objreg.window_registry.values(): # Some other window (print dialog, etc.) is focused so we pass the # event through. return False try: man = modeman.instance('current') return man.handle_event(event) except objreg.RegistryUnavailableError: # No window available yet, or not a MainWindow return False def eventFilter(self, obj: QObject, event: QEvent) -> bool: """Handle an event. Args: obj: The object which will get the event. event: The QEvent which is about to be delivered. Return: True if the event should be filtered, False if it's passed through. """ try: if not self._activated: return False if not isinstance(obj, QWindow): # We already handled this same event at some point earlier, so # we're not interested in it anymore. return False try: handler = self._handlers[event.type()] except KeyError: return False else: return handler(event) except: # If there is an exception in here and we leave the eventfilter # activated, we'll get an infinite loop and a stack overflow. self._activated = False raise def init() -> None: """Initialize the global EventFilter instance.""" event_filter = EventFilter(parent=QApplication.instance()) event_filter.install() quitter.instance.shutting_down.connect(event_filter.shutdown) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/keyinput/keyutils.py0000644000175000017510000005556700000000000023763 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Our own QKeySequence-like class and related utilities. Note that Qt's type safety (or rather, lack thereof) is somewhat scary when it comes to keys/modifiers. Many places (such as QKeyEvent::key()) don't actually return a Qt::Key, they return an int. To make things worse, when talking about a "key", sometimes Qt means a Qt::Key member. However, sometimes it means a Qt::Key member ORed with Qt.KeyboardModifiers... Because of that, _assert_plain_key() and _assert_plain_modifier() make sure we handle what we actually think we do. """ import itertools import typing import attr from PyQt5.QtCore import Qt, QEvent from PyQt5.QtGui import QKeySequence, QKeyEvent from qutebrowser.utils import utils # Map Qt::Key values to their Qt::KeyboardModifier value. _MODIFIER_MAP = { Qt.Key_Shift: Qt.ShiftModifier, Qt.Key_Control: Qt.ControlModifier, Qt.Key_Alt: Qt.AltModifier, Qt.Key_Meta: Qt.MetaModifier, Qt.Key_AltGr: Qt.GroupSwitchModifier, Qt.Key_Mode_switch: Qt.GroupSwitchModifier, } _NIL_KEY = Qt.Key(0) def _build_special_names() -> typing.Mapping[Qt.Key, str]: """Build _SPECIAL_NAMES dict from the special_names_str mapping below. The reason we don't do this directly is that certain Qt versions don't have all the keys, so we want to ignore AttributeErrors. """ special_names_str = { # Some keys handled in a weird way by QKeySequence::toString. # See https://bugreports.qt.io/browse/QTBUG-40030 # Most are unlikely to be ever needed, but you never know ;) # For dead/combining keys, we return the corresponding non-combining # key, as that's easier to add to the config. 'Super_L': 'Super L', 'Super_R': 'Super R', 'Hyper_L': 'Hyper L', 'Hyper_R': 'Hyper R', 'Direction_L': 'Direction L', 'Direction_R': 'Direction R', 'Shift': 'Shift', 'Control': 'Control', 'Meta': 'Meta', 'Alt': 'Alt', 'AltGr': 'AltGr', 'Multi_key': 'Multi key', 'SingleCandidate': 'Single Candidate', 'Mode_switch': 'Mode switch', 'Dead_Grave': '`', 'Dead_Acute': '´', 'Dead_Circumflex': '^', 'Dead_Tilde': '~', 'Dead_Macron': '¯', 'Dead_Breve': '˘', 'Dead_Abovedot': '˙', 'Dead_Diaeresis': '¨', 'Dead_Abovering': '˚', 'Dead_Doubleacute': '˝', 'Dead_Caron': 'ˇ', 'Dead_Cedilla': '¸', 'Dead_Ogonek': '˛', 'Dead_Iota': 'Iota', 'Dead_Voiced_Sound': 'Voiced Sound', 'Dead_Semivoiced_Sound': 'Semivoiced Sound', 'Dead_Belowdot': 'Belowdot', 'Dead_Hook': 'Hook', 'Dead_Horn': 'Horn', 'Dead_Stroke': '\u0335', # '̵' 'Dead_Abovecomma': '\u0313', # '̓' 'Dead_Abovereversedcomma': '\u0314', # '̔' 'Dead_Doublegrave': '\u030f', # '̏' 'Dead_Belowring': '\u0325', # '̥' 'Dead_Belowmacron': '\u0331', # '̱' 'Dead_Belowcircumflex': '\u032d', # '̭' 'Dead_Belowtilde': '\u0330', # '̰' 'Dead_Belowbreve': '\u032e', # '̮' 'Dead_Belowdiaeresis': '\u0324', # '̤' 'Dead_Invertedbreve': '\u0311', # '̑' 'Dead_Belowcomma': '\u0326', # '̦' 'Dead_Currency': '¤', 'Dead_a': 'a', 'Dead_A': 'A', 'Dead_e': 'e', 'Dead_E': 'E', 'Dead_i': 'i', 'Dead_I': 'I', 'Dead_o': 'o', 'Dead_O': 'O', 'Dead_u': 'u', 'Dead_U': 'U', 'Dead_Small_Schwa': 'ə', 'Dead_Capital_Schwa': 'Ə', 'Dead_Greek': 'Greek', 'Dead_Lowline': '\u0332', # '̲' 'Dead_Aboveverticalline': '\u030d', # '̍' 'Dead_Belowverticalline': '\u0329', 'Dead_Longsolidusoverlay': '\u0338', # '̸' 'Memo': 'Memo', 'ToDoList': 'To Do List', 'Calendar': 'Calendar', 'ContrastAdjust': 'Contrast Adjust', 'LaunchG': 'Launch (G)', 'LaunchH': 'Launch (H)', 'MediaLast': 'Media Last', 'unknown': 'Unknown', # For some keys, we just want a different name 'Escape': 'Escape', } special_names = {_NIL_KEY: 'nil'} for k, v in special_names_str.items(): try: special_names[getattr(Qt, 'Key_' + k)] = v except AttributeError: # pragma: no cover pass return special_names _SPECIAL_NAMES = _build_special_names() def _assert_plain_key(key: Qt.Key) -> None: """Make sure this is a key without KeyboardModifiers mixed in.""" assert not key & Qt.KeyboardModifierMask, hex(key) def _assert_plain_modifier(key: Qt.KeyboardModifier) -> None: """Make sure this is a modifier without a key mixed in.""" assert not key & ~Qt.KeyboardModifierMask, hex(key) def _is_printable(key: Qt.Key) -> bool: _assert_plain_key(key) return key <= 0xff and key not in [Qt.Key_Space, _NIL_KEY] def is_special_hint_mode(key: Qt.Key, modifiers: Qt.KeyboardModifier) -> bool: """Check whether this key should clear the keychain in hint mode. When we press "s", we don't want to be handled as part of a key chain in hint mode. """ _assert_plain_key(key) _assert_plain_modifier(modifiers) if is_modifier_key(key): return False return not (_is_printable(key) and modifiers in [Qt.ShiftModifier, Qt.NoModifier, Qt.KeypadModifier]) def is_special(key: Qt.Key, modifiers: Qt.KeyboardModifier) -> bool: """Check whether this key requires special key syntax.""" _assert_plain_key(key) _assert_plain_modifier(modifiers) return not (_is_printable(key) and modifiers in [Qt.ShiftModifier, Qt.NoModifier]) def is_modifier_key(key: Qt.Key) -> bool: """Test whether the given key is a modifier. This only considers keys which are part of Qt::KeyboardModifiers, i.e. which would interrupt a key chain like "yY" when handled. """ _assert_plain_key(key) return key in _MODIFIER_MAP def _is_surrogate(key: Qt.Key) -> bool: """Check if a codepoint is a UTF-16 surrogate. UTF-16 surrogates are a reserved range of Unicode from 0xd800 to 0xd8ff, used to encode Unicode codepoints above the BMP (Base Multilingual Plane). """ _assert_plain_key(key) return 0xd800 <= key <= 0xdfff def _remap_unicode(key: Qt.Key, text: str) -> Qt.Key: """Work around QtKeyEvent's bad values for high codepoints. QKeyEvent handles higher unicode codepoints poorly. It uses UTF-16 to handle key events, and for higher codepoints that require UTF-16 surrogates (e.g. emoji and some CJK characters), it sets the keycode to just the upper half of the surrogate, which renders it useless, and breaks UTF-8 encoding, causing crashes. So we detect this case, and reassign the key code to be the full Unicode codepoint, which we can recover from the text() property, which has the full character. This is a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-72776. """ _assert_plain_key(key) if _is_surrogate(key): if len(text) != 1: raise KeyParseError(text, "Expected 1 character for surrogate, " "but got {}!".format(len(text))) return Qt.Key(ord(text[0])) return key def _check_valid_utf8(s: str, data: typing.Union[Qt.Key, Qt.KeyboardModifier]) -> None: """Make sure the given string is valid UTF-8. Makes sure there are no chars where Qt did fall back to weird UTF-16 surrogates. """ try: s.encode('utf-8') except UnicodeEncodeError as e: # pragma: no cover raise ValueError("Invalid encoding in 0x{:x} -> {}: {}" .format(data, s, e)) def _key_to_string(key: Qt.Key) -> str: """Convert a Qt::Key member to a meaningful name. Args: key: A Qt::Key member. Return: A name of the key as a string. """ _assert_plain_key(key) if key in _SPECIAL_NAMES: return _SPECIAL_NAMES[key] result = QKeySequence(key).toString() _check_valid_utf8(result, key) return result def _modifiers_to_string(modifiers: Qt.KeyboardModifier) -> str: """Convert the given Qt::KeyboardModifiers to a string. Handles Qt.GroupSwitchModifier because Qt doesn't handle that as a modifier. """ _assert_plain_modifier(modifiers) if modifiers & Qt.GroupSwitchModifier: modifiers &= ~Qt.GroupSwitchModifier # type: ignore result = 'AltGr+' else: result = '' result += QKeySequence(modifiers).toString() _check_valid_utf8(result, modifiers) return result class KeyParseError(Exception): """Raised by _parse_single_key/parse_keystring on parse errors.""" def __init__(self, keystr: typing.Optional[str], error: str) -> None: if keystr is None: msg = "Could not parse keystring: {}".format(error) else: msg = "Could not parse {!r}: {}".format(keystr, error) super().__init__(msg) def _parse_keystring(keystr: str) -> typing.Iterator[str]: key = '' special = False for c in keystr: if c == '>': if special: yield _parse_special_key(key) key = '' special = False else: yield '>' assert not key, key elif c == '<': special = True elif special: key += c else: yield _parse_single_key(c) if special: yield '<' for c in key: yield _parse_single_key(c) def _parse_special_key(keystr: str) -> str: """Normalize a keystring like Ctrl-Q to a keystring like Ctrl+Q. Args: keystr: The key combination as a string. Return: The normalized keystring. """ keystr = keystr.lower() replacements = ( ('control', 'ctrl'), ('windows', 'meta'), ('mod4', 'meta'), ('command', 'meta'), ('cmd', 'meta'), ('mod1', 'alt'), ('less', '<'), ('greater', '>'), ) for (orig, repl) in replacements: keystr = keystr.replace(orig, repl) for mod in ['ctrl', 'meta', 'alt', 'shift', 'num']: keystr = keystr.replace(mod + '-', mod + '+') return keystr def _parse_single_key(keystr: str) -> str: """Get a keystring for QKeySequence for a single key.""" return 'Shift+' + keystr if keystr.isupper() else keystr @attr.s(frozen=True, hash=False) class KeyInfo: """A key with optional modifiers. Attributes: key: A Qt::Key member. modifiers: A Qt::KeyboardModifiers enum value. """ key = attr.ib() # type: Qt.Key modifiers = attr.ib() # type: Qt.KeyboardModifier @classmethod def from_event(cls, e: QKeyEvent) -> 'KeyInfo': """Get a KeyInfo object from a QKeyEvent. This makes sure that key/modifiers are never mixed and also remaps UTF-16 surrogates to work around QTBUG-72776. """ key = _remap_unicode(Qt.Key(e.key()), e.text()) modifiers = e.modifiers() _assert_plain_key(key) _assert_plain_modifier(modifiers) return cls(key, modifiers) def __hash__(self) -> int: """Convert KeyInfo to int before hashing. This is needed as a WORKAROUND because enum members aren't hashable with PyQt 5.7. """ return hash(self.to_int()) def __str__(self) -> str: """Convert this KeyInfo to a meaningful name. Return: A name of the key (combination) as a string. """ key_string = _key_to_string(self.key) modifiers = int(self.modifiers) if self.key in _MODIFIER_MAP: # Don't return e.g. modifiers &= ~_MODIFIER_MAP[self.key] elif _is_printable(self.key): # "normal" binding if not key_string: # pragma: no cover raise ValueError("Got empty string for key 0x{:x}!" .format(self.key)) assert len(key_string) == 1, key_string if self.modifiers == Qt.ShiftModifier: assert not is_special(self.key, self.modifiers) return key_string.upper() elif self.modifiers == Qt.NoModifier: assert not is_special(self.key, self.modifiers) return key_string.lower() else: # Use special binding syntax, but instead of key_string = key_string.lower() modifiers = Qt.KeyboardModifier(modifiers) # "special" binding assert is_special(self.key, self.modifiers) modifier_string = _modifiers_to_string(modifiers) return '<{}{}>'.format(modifier_string, key_string) def text(self) -> str: """Get the text which would be displayed when pressing this key.""" control = { Qt.Key_Space: ' ', Qt.Key_Tab: '\t', Qt.Key_Backspace: '\b', Qt.Key_Return: '\r', Qt.Key_Enter: '\r', Qt.Key_Escape: '\x1b', } if self.key in control: return control[self.key] elif not _is_printable(self.key): return '' text = QKeySequence(self.key).toString() if not self.modifiers & Qt.ShiftModifier: text = text.lower() return text def to_event(self, typ: QEvent.Type = QEvent.KeyPress) -> QKeyEvent: """Get a QKeyEvent from this KeyInfo.""" return QKeyEvent(typ, self.key, self.modifiers, self.text()) def to_int(self) -> int: """Get the key as an integer (with key/modifiers).""" return int(self.key) | int(self.modifiers) class KeySequence: """A sequence of key presses. This internally uses chained QKeySequence objects and exposes a nicer interface over it. NOTE: While private members of this class are in theory mutable, they must not be mutated in order to ensure consistent hashing. Attributes: _sequences: A list of QKeySequence Class attributes: _MAX_LEN: The maximum amount of keys in a QKeySequence. """ _MAX_LEN = 4 def __init__(self, *keys: int) -> None: self._sequences = [] # type: typing.List[QKeySequence] for sub in utils.chunk(keys, self._MAX_LEN): args = [self._convert_key(key) for key in sub] sequence = QKeySequence(*args) self._sequences.append(sequence) if keys: assert self self._validate() def _convert_key(self, key: Qt.Key) -> int: """Convert a single key for QKeySequence.""" assert isinstance(key, (int, Qt.KeyboardModifiers)), key return int(key) def __str__(self) -> str: parts = [] for info in self: parts.append(str(info)) return ''.join(parts) def __iter__(self) -> typing.Iterator[KeyInfo]: """Iterate over KeyInfo objects.""" for key_and_modifiers in self._iter_keys(): key = Qt.Key(int(key_and_modifiers) & ~Qt.KeyboardModifierMask) modifiers = Qt.KeyboardModifiers( # type: ignore int(key_and_modifiers) & Qt.KeyboardModifierMask) yield KeyInfo(key=key, modifiers=modifiers) def __repr__(self) -> str: return utils.get_repr(self, keys=str(self)) def __lt__(self, other: 'KeySequence') -> bool: return self._sequences < other._sequences def __gt__(self, other: 'KeySequence') -> bool: return self._sequences > other._sequences def __le__(self, other: 'KeySequence') -> bool: return self._sequences <= other._sequences def __ge__(self, other: 'KeySequence') -> bool: return self._sequences >= other._sequences def __eq__(self, other: object) -> bool: if not isinstance(other, KeySequence): return NotImplemented return self._sequences == other._sequences def __ne__(self, other: object) -> bool: if not isinstance(other, KeySequence): return NotImplemented return self._sequences != other._sequences def __hash__(self) -> int: return hash(tuple(self._sequences)) def __len__(self) -> int: return sum(len(seq) for seq in self._sequences) def __bool__(self) -> bool: return bool(self._sequences) @typing.overload def __getitem__(self, item: int) -> KeyInfo: ... @typing.overload def __getitem__(self, item: slice) -> 'KeySequence': ... def __getitem__( self, item: typing.Union[int, slice] ) -> typing.Union[KeyInfo, 'KeySequence']: if isinstance(item, slice): keys = list(self._iter_keys()) return self.__class__(*keys[item]) else: infos = list(self) return infos[item] def _iter_keys(self) -> typing.Iterator[int]: return itertools.chain.from_iterable(self._sequences) def _validate(self, keystr: str = None) -> None: for info in self: if info.key < Qt.Key_Space or info.key >= Qt.Key_unknown: raise KeyParseError(keystr, "Got invalid key!") for seq in self._sequences: if not seq: raise KeyParseError(keystr, "Got invalid key!") def matches(self, other: 'KeySequence') -> QKeySequence.SequenceMatch: """Check whether the given KeySequence matches with this one. We store multiple QKeySequences with <= 4 keys each, so we need to match those pair-wise, and account for an unequal amount of sequences as well. """ # pylint: disable=protected-access if len(self._sequences) > len(other._sequences): # If we entered more sequences than there are in the config, # there's no way there can be a match. return QKeySequence.NoMatch for entered, configured in zip(self._sequences, other._sequences): # If we get NoMatch/PartialMatch in a sequence, we can abort there. match = entered.matches(configured) if match != QKeySequence.ExactMatch: return match # We checked all common sequences and they had an ExactMatch. # # If there's still more sequences configured than entered, that's a # PartialMatch, as more keypresses can still follow and new sequences # will appear which we didn't check above. # # If there's the same amount of sequences configured and entered, # that's an EqualMatch. if len(self._sequences) == len(other._sequences): return QKeySequence.ExactMatch elif len(self._sequences) < len(other._sequences): return QKeySequence.PartialMatch else: raise utils.Unreachable("self={!r} other={!r}".format(self, other)) def append_event(self, ev: QKeyEvent) -> 'KeySequence': """Create a new KeySequence object with the given QKeyEvent added.""" key = Qt.Key(ev.key()) _assert_plain_key(key) _assert_plain_modifier(ev.modifiers()) key = _remap_unicode(key, ev.text()) modifiers = int(ev.modifiers()) if key == _NIL_KEY: raise KeyParseError(None, "Got nil key!") # We always remove Qt.GroupSwitchModifier because QKeySequence has no # way to mention that in a binding anyways... modifiers &= ~Qt.GroupSwitchModifier # We change Qt.Key_Backtab to Key_Tab here because nobody would # configure "Shift-Backtab" in their config. if modifiers & Qt.ShiftModifier and key == Qt.Key_Backtab: key = Qt.Key_Tab # We don't care about a shift modifier with symbols (Shift-: should # match a : binding even though we typed it with a shift on an # US-keyboard) # # However, we *do* care about Shift being involved if we got an # upper-case letter, as Shift-A should match a Shift-A binding, but not # an "a" binding. # # In addition, Shift also *is* relevant when other modifiers are # involved. Shift-Ctrl-X should not be equivalent to Ctrl-X. if (modifiers == Qt.ShiftModifier and _is_printable(key) and not ev.text().isupper()): modifiers = Qt.KeyboardModifiers() # On macOS, swap Ctrl and Meta back # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-51293 if utils.is_mac: if modifiers & Qt.ControlModifier and modifiers & Qt.MetaModifier: pass elif modifiers & Qt.ControlModifier: modifiers &= ~Qt.ControlModifier modifiers |= Qt.MetaModifier elif modifiers & Qt.MetaModifier: modifiers &= ~Qt.MetaModifier modifiers |= Qt.ControlModifier keys = list(self._iter_keys()) keys.append(key | int(modifiers)) return self.__class__(*keys) def strip_modifiers(self) -> 'KeySequence': """Strip optional modifiers from keys.""" modifiers = Qt.KeypadModifier keys = [key & ~modifiers for key in self._iter_keys()] return self.__class__(*keys) def with_mappings( self, mappings: typing.Mapping['KeySequence', 'KeySequence'] ) -> 'KeySequence': """Get a new KeySequence with the given mappings applied.""" keys = [] for key in self._iter_keys(): key_seq = KeySequence(key) if key_seq in mappings: new_seq = mappings[key_seq] assert len(new_seq) == 1 key = new_seq[0].to_int() keys.append(key) return self.__class__(*keys) @classmethod def parse(cls, keystr: str) -> 'KeySequence': """Parse a keystring like or xyz and return a KeySequence.""" # pylint: disable=protected-access new = cls() strings = list(_parse_keystring(keystr)) for sub in utils.chunk(strings, cls._MAX_LEN): sequence = QKeySequence(', '.join(sub)) new._sequences.append(sequence) if keystr: assert new, keystr # pylint: disable=protected-access new._validate(keystr) return new ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/keyinput/macros.py0000644000175000017510000001142600000000000023360 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2016-2020 Florian Bruhin (The Compiler) # Copyright 2016-2018 Jan Verbeek (blyxxyz) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Keyboard macro system.""" import typing from qutebrowser.commands import runners from qutebrowser.api import cmdutils from qutebrowser.keyinput import modeman from qutebrowser.utils import message, objreg, usertypes _CommandType = typing.Tuple[str, int] # command, type macro_recorder = typing.cast('MacroRecorder', None) class MacroRecorder: """An object for recording and running keyboard macros. Attributes: _macros: A list of commands for each macro register. _recording_macro: The register to which a macro is being recorded. _macro_count: The count passed to run_macro_command for each window. Stored for use by run_macro, which may be called from keyinput/modeparsers.py after a key input. _last_register: The macro which did run last. """ def __init__(self) -> None: self._macros = {} # type: typing.Dict[str, typing.List[_CommandType]] self._recording_macro = None # type: typing.Optional[str] self._macro_count = {} # type: typing.Dict[int, int] self._last_register = None # type: typing.Optional[str] @cmdutils.register(instance='macro-recorder', name='record-macro') @cmdutils.argument('win_id', value=cmdutils.Value.win_id) def record_macro_command(self, win_id: int, register: str = None) -> None: """Start or stop recording a macro. Args: register: Which register to store the macro in. """ if self._recording_macro is None: if register is None: mode_manager = modeman.instance(win_id) mode_manager.enter(usertypes.KeyMode.record_macro, 'record_macro') else: self.record_macro(register) else: message.info("Macro '{}' recorded.".format(self._recording_macro)) self._recording_macro = None def record_macro(self, register: str) -> None: """Start recording a macro.""" message.info("Recording macro '{}'...".format(register)) self._macros[register] = [] self._recording_macro = register @cmdutils.register(instance='macro-recorder', name='run-macro') @cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('count', value=cmdutils.Value.count) def run_macro_command(self, win_id: int, count: int = 1, register: str = None) -> None: """Run a recorded macro. Args: count: How many times to run the macro. register: Which macro to run. """ self._macro_count[win_id] = count if register is None: mode_manager = modeman.instance(win_id) mode_manager.enter(usertypes.KeyMode.run_macro, 'run_macro') else: self.run_macro(win_id, register) def run_macro(self, win_id: int, register: str) -> None: """Run a recorded macro.""" if register == '@': if self._last_register is None: raise cmdutils.CommandError("No previous macro") register = self._last_register self._last_register = register if register not in self._macros: raise cmdutils.CommandError( "No macro recorded in '{}'!".format(register)) commandrunner = runners.CommandRunner(win_id) for _ in range(self._macro_count[win_id]): for cmd in self._macros[register]: commandrunner.run_safely(*cmd) def record_command(self, text: str, count: int) -> None: """Record a command if a macro is being recorded.""" if self._recording_macro is not None: self._macros[self._recording_macro].append((text, count)) def init() -> None: """Initialize the MacroRecorder.""" global macro_recorder macro_recorder = MacroRecorder() objreg.register('macro-recorder', macro_recorder, command_only=True) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/keyinput/modeman.py0000644000175000017510000003636400000000000023524 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Mode manager singleton which handles the current keyboard mode.""" import functools import typing import attr from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QObject, QEvent from PyQt5.QtGui import QKeyEvent from PyQt5.QtWidgets import QApplication from qutebrowser.commands import runners from qutebrowser.keyinput import modeparsers, basekeyparser from qutebrowser.config import config from qutebrowser.api import cmdutils from qutebrowser.utils import usertypes, log, objreg, utils from qutebrowser.browser import hints INPUT_MODES = [usertypes.KeyMode.insert, usertypes.KeyMode.passthrough] PROMPT_MODES = [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno] ParserDictType = typing.MutableMapping[ usertypes.KeyMode, basekeyparser.BaseKeyParser] @attr.s(frozen=True) class KeyEvent: """A small wrapper over a QKeyEvent storing its data. This is needed because Qt apparently mutates existing events with new data. It doesn't store the modifiers because they can be different for a key press/release. Attributes: key: A Qt.Key member (QKeyEvent::key). text: A string (QKeyEvent::text). """ key = attr.ib() # type: Qt.Key text = attr.ib() # type: str @classmethod def from_event(cls, event: QKeyEvent) -> 'KeyEvent': """Initialize a KeyEvent from a QKeyEvent.""" return cls(Qt.Key(event.key()), event.text()) class NotInModeError(Exception): """Exception raised when we want to leave a mode we're not in.""" def init(win_id: int, parent: QObject) -> 'ModeManager': """Initialize the mode manager and the keyparsers for the given win_id.""" modeman = ModeManager(win_id, parent) objreg.register('mode-manager', modeman, scope='window', window=win_id) commandrunner = runners.CommandRunner(win_id) hintmanager = hints.HintManager(win_id, parent=parent) objreg.register('hintmanager', hintmanager, scope='window', window=win_id, command_only=True) keyparsers = { usertypes.KeyMode.normal: modeparsers.NormalKeyParser( win_id=win_id, commandrunner=commandrunner, parent=modeman), usertypes.KeyMode.hint: modeparsers.HintKeyParser( win_id=win_id, commandrunner=commandrunner, hintmanager=hintmanager, parent=modeman), usertypes.KeyMode.insert: modeparsers.PassthroughKeyParser( win_id=win_id, mode=usertypes.KeyMode.insert, commandrunner=commandrunner, parent=modeman), usertypes.KeyMode.passthrough: modeparsers.PassthroughKeyParser( win_id=win_id, mode=usertypes.KeyMode.passthrough, commandrunner=commandrunner, parent=modeman), usertypes.KeyMode.command: modeparsers.PassthroughKeyParser( win_id=win_id, mode=usertypes.KeyMode.command, commandrunner=commandrunner, parent=modeman), usertypes.KeyMode.prompt: modeparsers.PassthroughKeyParser( win_id=win_id, mode=usertypes.KeyMode.prompt, commandrunner=commandrunner, parent=modeman), usertypes.KeyMode.yesno: modeparsers.PromptKeyParser( win_id=win_id, commandrunner=commandrunner, parent=modeman), usertypes.KeyMode.caret: modeparsers.CaretKeyParser( win_id=win_id, commandrunner=commandrunner, parent=modeman), usertypes.KeyMode.set_mark: modeparsers.RegisterKeyParser( win_id=win_id, mode=usertypes.KeyMode.set_mark, commandrunner=commandrunner, parent=modeman), usertypes.KeyMode.jump_mark: modeparsers.RegisterKeyParser( win_id=win_id, mode=usertypes.KeyMode.jump_mark, commandrunner=commandrunner, parent=modeman), usertypes.KeyMode.record_macro: modeparsers.RegisterKeyParser( win_id=win_id, mode=usertypes.KeyMode.record_macro, commandrunner=commandrunner, parent=modeman), usertypes.KeyMode.run_macro: modeparsers.RegisterKeyParser( win_id=win_id, mode=usertypes.KeyMode.run_macro, commandrunner=commandrunner, parent=modeman), } # type: ParserDictType for mode, parser in keyparsers.items(): modeman.register(mode, parser) return modeman def instance(win_id: typing.Union[int, str]) -> 'ModeManager': """Get a modemanager object.""" return objreg.get('mode-manager', scope='window', window=win_id) def enter(win_id: int, mode: usertypes.KeyMode, reason: str = None, only_if_normal: bool = False) -> None: """Enter the mode 'mode'.""" instance(win_id).enter(mode, reason, only_if_normal) def leave(win_id: int, mode: usertypes.KeyMode, reason: str = None, *, maybe: bool = False) -> None: """Leave the mode 'mode'.""" instance(win_id).leave(mode, reason, maybe=maybe) class ModeManager(QObject): """Manager for keyboard modes. Attributes: mode: The mode we're currently in. _win_id: The window ID of this ModeManager _prev_mode: Mode before a prompt popped up parsers: A dictionary of modes and their keyparsers. _forward_unbound_keys: If we should forward unbound keys. _releaseevents_to_pass: A set of KeyEvents where the keyPressEvent was passed through, so the release event should as well. Signals: entered: Emitted when a mode is entered. arg1: The mode which has been entered. arg2: The window ID of this mode manager. left: Emitted when a mode is left. arg1: The mode which has been left. arg2: The new current mode. arg3: The window ID of this mode manager. """ entered = pyqtSignal(usertypes.KeyMode, int) left = pyqtSignal(usertypes.KeyMode, usertypes.KeyMode, int) def __init__(self, win_id: int, parent: QObject = None) -> None: super().__init__(parent) self._win_id = win_id self.parsers = {} # type: ParserDictType self._prev_mode = usertypes.KeyMode.normal self.mode = usertypes.KeyMode.normal self._releaseevents_to_pass = set() # type: typing.Set[KeyEvent] def __repr__(self) -> str: return utils.get_repr(self, mode=self.mode) def _handle_keypress(self, event: QKeyEvent, *, dry_run: bool = False) -> bool: """Handle filtering of KeyPress events. Args: event: The KeyPress to examine. dry_run: Don't actually handle the key, only filter it. Return: True if event should be filtered, False otherwise. """ curmode = self.mode parser = self.parsers[curmode] if curmode != usertypes.KeyMode.insert: log.modes.debug("got keypress in mode {} - delegating to " "{}".format(curmode, utils.qualname(parser))) match = parser.handle(event, dry_run=dry_run) is_non_alnum = ( event.modifiers() not in [Qt.NoModifier, Qt.ShiftModifier] or not event.text().strip()) forward_unbound_keys = config.cache['input.forward_unbound_keys'] if match: filter_this = True elif (parser.passthrough or forward_unbound_keys == 'all' or (forward_unbound_keys == 'auto' and is_non_alnum)): filter_this = False else: filter_this = True if not filter_this and not dry_run: self._releaseevents_to_pass.add(KeyEvent.from_event(event)) if curmode != usertypes.KeyMode.insert: focus_widget = QApplication.instance().focusWidget() log.modes.debug("match: {}, forward_unbound_keys: {}, " "passthrough: {}, is_non_alnum: {}, dry_run: {} " "--> filter: {} (focused: {!r})".format( match, forward_unbound_keys, parser.passthrough, is_non_alnum, dry_run, filter_this, focus_widget)) return filter_this def _handle_keyrelease(self, event: QKeyEvent) -> bool: """Handle filtering of KeyRelease events. Args: event: The KeyPress to examine. Return: True if event should be filtered, False otherwise. """ # handle like matching KeyPress keyevent = KeyEvent.from_event(event) if keyevent in self._releaseevents_to_pass: self._releaseevents_to_pass.remove(keyevent) filter_this = False else: filter_this = True if self.mode != usertypes.KeyMode.insert: log.modes.debug("filter: {}".format(filter_this)) return filter_this def register(self, mode: usertypes.KeyMode, parser: basekeyparser.BaseKeyParser) -> None: """Register a new mode.""" assert parser is not None self.parsers[mode] = parser parser.request_leave.connect(self.leave) def enter(self, mode: usertypes.KeyMode, reason: str = None, only_if_normal: bool = False) -> None: """Enter a new mode. Args: mode: The mode to enter as a KeyMode member. reason: Why the mode was entered. only_if_normal: Only enter the new mode if we're in normal mode. """ if mode == usertypes.KeyMode.normal: self.leave(self.mode, reason='enter normal: {}'.format(reason)) return log.modes.debug("Entering mode {}{}".format( mode, '' if reason is None else ' (reason: {})'.format(reason))) if mode not in self.parsers: raise ValueError("No keyparser for mode {}".format(mode)) if self.mode == mode or (self.mode in PROMPT_MODES and mode in PROMPT_MODES): log.modes.debug("Ignoring request as we're in mode {} " "already.".format(self.mode)) return if self.mode != usertypes.KeyMode.normal: if only_if_normal: log.modes.debug("Ignoring request as we're in mode {} " "and only_if_normal is set..".format( self.mode)) return log.modes.debug("Overriding mode {}.".format(self.mode)) self.left.emit(self.mode, mode, self._win_id) if mode in PROMPT_MODES and self.mode in INPUT_MODES: self._prev_mode = self.mode else: self._prev_mode = usertypes.KeyMode.normal self.mode = mode self.entered.emit(mode, self._win_id) @cmdutils.register(instance='mode-manager', scope='window') def enter_mode(self, mode: str) -> None: """Enter a key mode. Args: mode: The mode to enter. See `:help bindings.commands` for the available modes, but note that hint/command/yesno/prompt mode can't be entered manually. """ try: m = usertypes.KeyMode[mode] except KeyError: raise cmdutils.CommandError("Mode {} does not exist!".format(mode)) if m in [usertypes.KeyMode.hint, usertypes.KeyMode.command, usertypes.KeyMode.yesno, usertypes.KeyMode.prompt]: raise cmdutils.CommandError( "Mode {} can't be entered manually!".format(mode)) self.enter(m, 'command') @pyqtSlot(usertypes.KeyMode, str, bool) def leave(self, mode: usertypes.KeyMode, reason: str = None, maybe: bool = False) -> None: """Leave a key mode. Args: mode: The mode to leave as a usertypes.KeyMode member. reason: Why the mode was left. maybe: If set, ignore the request if we're not in that mode. """ if self.mode != mode: if maybe: log.modes.debug("Ignoring leave request for {} (reason {}) as " "we're in mode {}".format( mode, reason, self.mode)) return else: raise NotInModeError("Not in mode {}!".format(mode)) log.modes.debug("Leaving mode {}{}".format( mode, '' if reason is None else ' (reason: {})'.format(reason))) # leaving a mode implies clearing keychain, see # https://github.com/qutebrowser/qutebrowser/issues/1805 self.clear_keychain() self.mode = usertypes.KeyMode.normal self.left.emit(mode, self.mode, self._win_id) if mode in PROMPT_MODES: self.enter(self._prev_mode, reason='restore mode before {}'.format(mode.name)) @cmdutils.register(instance='mode-manager', name='leave-mode', not_modes=[usertypes.KeyMode.normal], scope='window') def leave_current_mode(self) -> None: """Leave the mode we're currently in.""" if self.mode == usertypes.KeyMode.normal: raise ValueError("Can't leave normal mode!") self.leave(self.mode, 'leave current') def handle_event(self, event: QEvent) -> bool: """Filter all events based on the currently set mode. Also calls the real keypress handler. Args: event: The KeyPress to examine. Return: True if event should be filtered, False otherwise. """ handlers = { QEvent.KeyPress: self._handle_keypress, QEvent.KeyRelease: self._handle_keyrelease, QEvent.ShortcutOverride: functools.partial(self._handle_keypress, dry_run=True), } # type: typing.Mapping[QEvent.Type, typing.Callable[[QEvent], bool]] handler = handlers[event.type()] return handler(event) @cmdutils.register(instance='mode-manager', scope='window') def clear_keychain(self) -> None: """Clear the currently entered key chain.""" self.parsers[self.mode].clear_keystring() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/keyinput/modeparsers.py0000644000175000017510000003023500000000000024417 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """KeyChainParser for "hint" and "normal" modes. Module attributes: STARTCHARS: Possible chars for starting a commandline input. """ import typing import traceback import enum from PyQt5.QtCore import pyqtSlot, Qt, QObject from PyQt5.QtGui import QKeySequence, QKeyEvent from qutebrowser.browser import hints from qutebrowser.commands import cmdexc from qutebrowser.config import config from qutebrowser.keyinput import basekeyparser, keyutils, macros from qutebrowser.utils import usertypes, log, message, objreg, utils if typing.TYPE_CHECKING: from qutebrowser.commands import runners STARTCHARS = ":/?" LastPress = enum.Enum('LastPress', ['none', 'filtertext', 'keystring']) class CommandKeyParser(basekeyparser.BaseKeyParser): """KeyChainParser for command bindings. Attributes: _commandrunner: CommandRunner instance. """ def __init__(self, win_id: int, commandrunner: 'runners.CommandRunner', parent: QObject = None) -> None: super().__init__(win_id, parent) self._commandrunner = commandrunner def execute(self, cmdstr: str, count: int = None) -> None: try: self._commandrunner.run(cmdstr, count) except cmdexc.Error as e: message.error(str(e), stack=traceback.format_exc()) class NormalKeyParser(CommandKeyParser): """KeyParser for normal mode with added STARTCHARS detection and more. Attributes: _partial_timer: Timer to clear partial keypresses. """ def __init__(self, win_id: int, commandrunner: 'runners.CommandRunner', parent: QObject = None) -> None: super().__init__(win_id, commandrunner, parent) self._read_config('normal') self._partial_timer = usertypes.Timer(self, 'partial-match') self._partial_timer.setSingleShot(True) self._partial_timer.timeout.connect(self._clear_partial_match) self._inhibited = False self._inhibited_timer = usertypes.Timer(self, 'normal-inhibited') self._inhibited_timer.setSingleShot(True) def __repr__(self) -> str: return utils.get_repr(self) def handle(self, e: QKeyEvent, *, dry_run: bool = False) -> QKeySequence.SequenceMatch: """Override to abort if the key is a startchar.""" txt = e.text().strip() if self._inhibited: self._debug_log("Ignoring key '{}', because the normal mode is " "currently inhibited.".format(txt)) return QKeySequence.NoMatch match = super().handle(e, dry_run=dry_run) if match == QKeySequence.PartialMatch and not dry_run: timeout = config.val.input.partial_timeout if timeout != 0: self._partial_timer.setInterval(timeout) self._partial_timer.start() return match def set_inhibited_timeout(self, timeout: int) -> None: """Ignore keypresses for the given duration.""" if timeout != 0: self._debug_log("Inhibiting the normal mode for {}ms.".format( timeout)) self._inhibited = True self._inhibited_timer.setInterval(timeout) self._inhibited_timer.timeout.connect(self._clear_inhibited) self._inhibited_timer.start() @pyqtSlot() def _clear_partial_match(self) -> None: """Clear a partial keystring after a timeout.""" self._debug_log("Clearing partial keystring {}".format( self._sequence)) self._sequence = keyutils.KeySequence() self.keystring_updated.emit(str(self._sequence)) @pyqtSlot() def _clear_inhibited(self) -> None: """Reset inhibition state after a timeout.""" self._debug_log("Releasing inhibition state of normal mode.") self._inhibited = False class PassthroughKeyParser(CommandKeyParser): """KeyChainParser which passes through normal keys. Used for insert/passthrough modes. Attributes: _mode: The mode this keyparser is for. """ do_log = False passthrough = True supports_count = False def __init__(self, win_id: int, mode: usertypes.KeyMode, commandrunner: 'runners.CommandRunner', parent: QObject = None) -> None: """Constructor. Args: mode: The mode this keyparser is for. parent: Qt parent. warn: Whether to warn if an ignored key was bound. """ super().__init__(win_id, commandrunner, parent) self._read_config(mode.name) self._mode = mode def __repr__(self) -> str: return utils.get_repr(self, mode=self._mode) class PromptKeyParser(CommandKeyParser): """KeyParser for yes/no prompts.""" supports_count = False def __init__(self, win_id: int, commandrunner: 'runners.CommandRunner', parent: QObject = None) -> None: super().__init__(win_id, commandrunner, parent) self._read_config('yesno') def __repr__(self) -> str: return utils.get_repr(self) class HintKeyParser(CommandKeyParser): """KeyChainParser for hints. Attributes: _filtertext: The text to filter with. _hintmanager: The HintManager to use. _last_press: The nature of the last keypress, a LastPress member. """ supports_count = False def __init__(self, win_id: int, commandrunner: 'runners.CommandRunner', hintmanager: hints.HintManager, parent: QObject = None) -> None: super().__init__(win_id, commandrunner, parent) self._hintmanager = hintmanager self._filtertext = '' self._last_press = LastPress.none self._read_config('hint') self.keystring_updated.connect(self._hintmanager.handle_partial_key) def _handle_filter_key(self, e: QKeyEvent) -> QKeySequence.SequenceMatch: """Handle keys for string filtering.""" log.keyboard.debug("Got filter key 0x{:x} text {}".format( e.key(), e.text())) if e.key() == Qt.Key_Backspace: log.keyboard.debug("Got backspace, mode {}, filtertext '{}', " "sequence '{}'".format(self._last_press, self._filtertext, self._sequence)) if self._last_press != LastPress.keystring and self._filtertext: self._filtertext = self._filtertext[:-1] self._hintmanager.filter_hints(self._filtertext) return QKeySequence.ExactMatch elif self._last_press == LastPress.keystring and self._sequence: self._sequence = self._sequence[:-1] self.keystring_updated.emit(str(self._sequence)) if not self._sequence and self._filtertext: # Switch back to hint filtering mode (this can happen only # in numeric mode after the number has been deleted). self._hintmanager.filter_hints(self._filtertext) self._last_press = LastPress.filtertext return QKeySequence.ExactMatch else: return QKeySequence.NoMatch elif self._hintmanager.current_mode() != 'number': return QKeySequence.NoMatch elif not e.text(): return QKeySequence.NoMatch else: self._filtertext += e.text() self._hintmanager.filter_hints(self._filtertext) self._last_press = LastPress.filtertext return QKeySequence.ExactMatch def handle(self, e: QKeyEvent, *, dry_run: bool = False) -> QKeySequence.SequenceMatch: """Handle a new keypress and call the respective handlers.""" if dry_run: return super().handle(e, dry_run=True) if keyutils.is_special_hint_mode(Qt.Key(e.key()), e.modifiers()): log.keyboard.debug("Got special key, clearing keychain") self.clear_keystring() assert not dry_run match = super().handle(e) if match == QKeySequence.PartialMatch: self._last_press = LastPress.keystring elif match == QKeySequence.ExactMatch: self._last_press = LastPress.none elif match == QKeySequence.NoMatch: # We couldn't find a keychain so we check if it's a special key. return self._handle_filter_key(e) else: raise ValueError("Got invalid match type {}!".format(match)) return match def update_bindings(self, strings: typing.Sequence[str], preserve_filter: bool = False) -> None: """Update bindings when the hint strings changed. Args: strings: A list of hint strings. preserve_filter: Whether to keep the current value of `self._filtertext`. """ self._read_config() self.bindings.update({keyutils.KeySequence.parse(s): 'follow-hint -s ' + s for s in strings}) if not preserve_filter: self._filtertext = '' class CaretKeyParser(CommandKeyParser): """KeyParser for caret mode.""" passthrough = True def __init__(self, win_id: int, commandrunner: 'runners.CommandRunner', parent: QObject = None) -> None: super().__init__(win_id, commandrunner, parent) self._read_config('caret') class RegisterKeyParser(CommandKeyParser): """KeyParser for modes that record a register key. Attributes: _mode: One of KeyMode.set_mark, KeyMode.jump_mark, KeyMode.record_macro and KeyMode.run_macro. """ supports_count = False def __init__(self, win_id: int, mode: usertypes.KeyMode, commandrunner: 'runners.CommandRunner', parent: QObject = None) -> None: super().__init__(win_id, commandrunner, parent) self._mode = mode self._read_config('register') def handle(self, e: QKeyEvent, *, dry_run: bool = False) -> QKeySequence.SequenceMatch: """Override to always match the next key and use the register.""" match = super().handle(e, dry_run=dry_run) if match or dry_run: return match if keyutils.is_special(Qt.Key(e.key()), e.modifiers()): # this is not a proper register key, let it pass and keep going return QKeySequence.NoMatch key = e.text() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) try: if self._mode == usertypes.KeyMode.set_mark: tabbed_browser.set_mark(key) elif self._mode == usertypes.KeyMode.jump_mark: tabbed_browser.jump_mark(key) elif self._mode == usertypes.KeyMode.record_macro: macros.macro_recorder.record_macro(key) elif self._mode == usertypes.KeyMode.run_macro: macros.macro_recorder.run_macro(self._win_id, key) else: raise ValueError( "{} is not a valid register mode".format(self._mode)) except cmdexc.Error as err: message.error(str(err), stack=traceback.format_exc()) self.request_leave.emit(self._mode, "valid register key", True) return QKeySequence.ExactMatch ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1581772733.904756 qutebrowser-1.10.1/qutebrowser/mainwindow/0000755000175000017510000000000000000000000022022 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/__init__.py0000644000175000017510000000151100000000000024131 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Widgets needed for the main window.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/mainwindow.py0000644000175000017510000006356200000000000024564 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """The main window of qutebrowser.""" import binascii import base64 import itertools import functools import typing from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QRect, QPoint, QTimer, Qt, QCoreApplication, QEventLoop) from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy from qutebrowser.commands import runners from qutebrowser.api import cmdutils from qutebrowser.config import config, configfiles, stylesheet from qutebrowser.utils import (message, log, usertypes, qtutils, objreg, utils, jinja, debug) from qutebrowser.mainwindow import messageview, prompt from qutebrowser.completion import completionwidget, completer from qutebrowser.keyinput import modeman from qutebrowser.browser import commands, downloadview, hints, downloads from qutebrowser.misc import crashsignal, keyhintwidget, sessions win_id_gen = itertools.count(0) def get_window(via_ipc, force_window=False, force_tab=False, force_target=None, no_raise=False): """Helper function for app.py to get a window id. Args: via_ipc: Whether the request was made via IPC. force_window: Whether to force opening in a window. force_tab: Whether to force opening in a tab. force_target: Override the new_instance_open_target config no_raise: suppress target window raising Return: ID of a window that was used to open URL """ if force_window and force_tab: raise ValueError("force_window and force_tab are mutually exclusive!") if not via_ipc: # Initial main window return 0 open_target = config.val.new_instance_open_target # Apply any target overrides, ordered by precedence if force_target is not None: open_target = force_target if force_window: open_target = 'window' if force_tab and open_target == 'window': # Command sent via IPC open_target = 'tab-silent' window = None should_raise = False # Try to find the existing tab target if opening in a tab if open_target != 'window': window = get_target_window() should_raise = open_target not in ['tab-silent', 'tab-bg-silent'] # Otherwise, or if no window was found, create a new one if window is None: window = MainWindow(private=None) window.show() should_raise = True if should_raise and not no_raise: raise_window(window) return window.win_id def raise_window(window, alert=True): """Raise the given MainWindow object.""" window.setWindowState(window.windowState() & ~Qt.WindowMinimized) window.setWindowState(window.windowState() | Qt.WindowActive) window.raise_() # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-69568 QCoreApplication.processEvents( # type: ignore QEventLoop.ExcludeUserInputEvents | QEventLoop.ExcludeSocketNotifiers) window.activateWindow() if alert: QApplication.instance().alert(window) def get_target_window(): """Get the target window for new tabs, or None if none exist.""" try: win_mode = config.val.new_instance_open_target_window if win_mode == 'last-focused': return objreg.last_focused_window() elif win_mode == 'first-opened': return objreg.window_by_index(0) elif win_mode == 'last-opened': return objreg.window_by_index(-1) elif win_mode == 'last-visible': return objreg.last_visible_window() else: raise ValueError("Invalid win_mode {}".format(win_mode)) except objreg.NoWindow: return None _OverlayInfoType = typing.Tuple[QWidget, pyqtSignal, bool, str] class MainWindow(QWidget): """The main window of qutebrowser. Adds all needed components to a vbox, initializes sub-widgets and connects signals. Attributes: status: The StatusBar widget. tabbed_browser: The TabbedBrowser widget. state_before_fullscreen: window state before activation of fullscreen. _downloadview: The DownloadView widget. _download_model: The DownloadModel instance. _vbox: The main QVBoxLayout. _commandrunner: The main CommandRunner instance. _overlays: Widgets shown as overlay for the current webpage. _private: Whether the window is in private browsing mode. """ # Application wide stylesheets STYLESHEET = """ HintLabel { background-color: {{ conf.colors.hints.bg }}; color: {{ conf.colors.hints.fg }}; font: {{ conf.fonts.hints }}; border: {{ conf.hints.border }}; padding-left: 3px; padding-right: 3px; } QMenu { {% if conf.fonts.contextmenu %} font: {{ conf.fonts.contextmenu }}; {% endif %} {% if conf.colors.contextmenu.menu.bg %} background-color: {{ conf.colors.contextmenu.menu.bg }}; {% endif %} {% if conf.colors.contextmenu.menu.fg %} color: {{ conf.colors.contextmenu.menu.fg }}; {% endif %} } QMenu::item:selected { {% if conf.colors.contextmenu.selected.bg %} background-color: {{ conf.colors.contextmenu.selected.bg }}; {% endif %} {% if conf.colors.contextmenu.selected.fg %} color: {{ conf.colors.contextmenu.selected.fg }}; {% endif %} } """ def __init__(self, *, private, geometry=None, parent=None): """Create a new main window. Args: geometry: The geometry to load, as a bytes-object (or None). private: Whether the window is in private browsing mode. parent: The parent the window should get. """ super().__init__(parent) # Late import to avoid a circular dependency # - browsertab -> hints -> webelem -> mainwindow -> bar -> browsertab from qutebrowser.mainwindow import tabbedbrowser from qutebrowser.mainwindow.statusbar import bar self.setAttribute(Qt.WA_DeleteOnClose) self._overlays = [] # type: typing.MutableSequence[_OverlayInfoType] self.win_id = next(win_id_gen) self.registry = objreg.ObjectRegistry() objreg.window_registry[self.win_id] = self objreg.register('main-window', self, scope='window', window=self.win_id) tab_registry = objreg.ObjectRegistry() objreg.register('tab-registry', tab_registry, scope='window', window=self.win_id) message_bridge = message.MessageBridge(self) objreg.register('message-bridge', message_bridge, scope='window', window=self.win_id) self.setWindowTitle('qutebrowser') self._vbox = QVBoxLayout(self) self._vbox.setContentsMargins(0, 0, 0, 0) self._vbox.setSpacing(0) self._init_downloadmanager() self._downloadview = downloadview.DownloadView( model=self._download_model) if config.val.content.private_browsing: # This setting always trumps what's passed in. private = True else: private = bool(private) self._private = private self.tabbed_browser = tabbedbrowser.TabbedBrowser(win_id=self.win_id, private=private, parent=self) objreg.register('tabbed-browser', self.tabbed_browser, scope='window', window=self.win_id) self._init_command_dispatcher() # We need to set an explicit parent for StatusBar because it does some # show/hide magic immediately which would mean it'd show up as a # window. self.status = bar.StatusBar(win_id=self.win_id, private=private, parent=self) self._add_widgets() self._downloadview.show() self._init_completion() log.init.debug("Initializing modes...") modeman.init(win_id=self.win_id, parent=self) self._commandrunner = runners.CommandRunner(self.win_id, partial_match=True) self._keyhint = keyhintwidget.KeyHintView(self.win_id, self) self._add_overlay(self._keyhint, self._keyhint.update_geometry) self._prompt_container = prompt.PromptContainer(self.win_id, self) self._add_overlay(self._prompt_container, self._prompt_container.update_geometry, centered=True, padding=10) objreg.register('prompt-container', self._prompt_container, scope='window', window=self.win_id, command_only=True) self._prompt_container.hide() self._messageview = messageview.MessageView(parent=self) self._add_overlay(self._messageview, self._messageview.update_geometry) self._init_geometry(geometry) self._connect_signals() # When we're here the statusbar might not even really exist yet, so # resizing will fail. Therefore, we use singleShot QTimers to make sure # we defer this until everything else is initialized. QTimer.singleShot(0, self._connect_overlay_signals) config.instance.changed.connect(self._on_config_changed) QApplication.instance().new_window.emit(self) self._set_decoration(config.val.window.hide_decoration) self.state_before_fullscreen = self.windowState() stylesheet.set_register(self) def _init_geometry(self, geometry): """Initialize the window geometry or load it from disk.""" if geometry is not None: self._load_geometry(geometry) elif self.win_id == 0: self._load_state_geometry() else: self._set_default_geometry() log.init.debug("Initial main window geometry: {}".format( self.geometry())) def _add_overlay(self, widget, signal, *, centered=False, padding=0): self._overlays.append((widget, signal, centered, padding)) def _update_overlay_geometries(self): """Update the size/position of all overlays.""" for w, _signal, centered, padding in self._overlays: self._update_overlay_geometry(w, centered, padding) def _update_overlay_geometry(self, widget, centered, padding): """Reposition/resize the given overlay.""" if not widget.isVisible(): return size_hint = widget.sizeHint() if widget.sizePolicy().horizontalPolicy() == QSizePolicy.Expanding: width = self.width() - 2 * padding left = padding else: width = min(size_hint.width(), self.width() - 2 * padding) left = (self.width() - width) // 2 if centered else 0 height_padding = 20 status_position = config.val.statusbar.position if status_position == 'bottom': if self.status.isVisible(): status_height = self.status.height() bottom = self.status.geometry().top() else: status_height = 0 bottom = self.height() top = self.height() - status_height - size_hint.height() top = qtutils.check_overflow(top, 'int', fatal=False) topleft = QPoint(left, max(height_padding, top)) bottomright = QPoint(left + width, bottom) elif status_position == 'top': if self.status.isVisible(): status_height = self.status.height() top = self.status.geometry().bottom() else: status_height = 0 top = 0 topleft = QPoint(left, top) bottom = status_height + size_hint.height() bottom = qtutils.check_overflow(bottom, 'int', fatal=False) bottomright = QPoint(left + width, min(self.height() - height_padding, bottom)) else: raise ValueError("Invalid position {}!".format(status_position)) rect = QRect(topleft, bottomright) log.misc.debug('new geometry for {!r}: {}'.format(widget, rect)) if rect.isValid(): widget.setGeometry(rect) def _init_downloadmanager(self): log.init.debug("Initializing downloads...") qtnetwork_download_manager = objreg.get('qtnetwork-download-manager') try: webengine_download_manager = objreg.get( 'webengine-download-manager') except KeyError: webengine_download_manager = None self._download_model = downloads.DownloadModel( qtnetwork_download_manager, webengine_download_manager) objreg.register('download-model', self._download_model, scope='window', window=self.win_id, command_only=True) def _init_completion(self): self._completion = completionwidget.CompletionView(cmd=self.status.cmd, win_id=self.win_id, parent=self) completer_obj = completer.Completer(cmd=self.status.cmd, win_id=self.win_id, parent=self._completion) self._completion.selection_changed.connect( completer_obj.on_selection_changed) objreg.register('completion', self._completion, scope='window', window=self.win_id, command_only=True) self._add_overlay(self._completion, self._completion.update_geometry) def _init_command_dispatcher(self): self._command_dispatcher = commands.CommandDispatcher( self.win_id, self.tabbed_browser) objreg.register('command-dispatcher', self._command_dispatcher, command_only=True, scope='window', window=self.win_id) self.tabbed_browser.widget.destroyed.connect( # type: ignore functools.partial(objreg.delete, 'command-dispatcher', scope='window', window=self.win_id)) def __repr__(self): return utils.get_repr(self) @pyqtSlot(str) def _on_config_changed(self, option): """Resize the completion if related config options changed.""" if option == 'statusbar.padding': self._update_overlay_geometries() elif option == 'downloads.position': self._add_widgets() elif option == 'statusbar.position': self._add_widgets() self._update_overlay_geometries() elif option == 'window.hide_decoration': self._set_decoration(config.val.window.hide_decoration) def _add_widgets(self): """Add or readd all widgets to the VBox.""" self._vbox.removeWidget(self.tabbed_browser.widget) self._vbox.removeWidget(self._downloadview) self._vbox.removeWidget(self.status) widgets = [self.tabbed_browser.widget] downloads_position = config.val.downloads.position if downloads_position == 'top': widgets.insert(0, self._downloadview) elif downloads_position == 'bottom': widgets.append(self._downloadview) else: raise ValueError("Invalid position {}!".format(downloads_position)) status_position = config.val.statusbar.position if status_position == 'top': widgets.insert(0, self.status) elif status_position == 'bottom': widgets.append(self.status) else: raise ValueError("Invalid position {}!".format(status_position)) for widget in widgets: self._vbox.addWidget(widget) def _load_state_geometry(self): """Load the geometry from the state file.""" try: data = configfiles.state['geometry']['mainwindow'] geom = base64.b64decode(data, validate=True) except KeyError: # First start self._set_default_geometry() except binascii.Error: log.init.exception("Error while reading geometry") self._set_default_geometry() else: self._load_geometry(geom) def _save_geometry(self): """Save the window geometry to the state config.""" data = bytes(self.saveGeometry()) geom = base64.b64encode(data).decode('ASCII') configfiles.state['geometry']['mainwindow'] = geom def _load_geometry(self, geom): """Load geometry from a bytes object. If loading fails, loads default geometry. """ log.init.debug("Loading mainwindow from {!r}".format(geom)) ok = self.restoreGeometry(geom) if not ok: log.init.warning("Error while loading geometry.") self._set_default_geometry() def _connect_overlay_signals(self): """Connect the resize signal and resize everything once.""" for widget, signal, centered, padding in self._overlays: signal.connect( functools.partial(self._update_overlay_geometry, widget, centered, padding)) self._update_overlay_geometry(widget, centered, padding) def _set_default_geometry(self): """Set some sensible default geometry.""" self.setGeometry(QRect(50, 50, 800, 600)) def _get_object(self, name): """Get an object for this window in the object registry.""" return objreg.get(name, scope='window', window=self.win_id) def _connect_signals(self): """Connect all mainwindow signals.""" message_bridge = self._get_object('message-bridge') mode_manager = modeman.instance(self.win_id) # misc self.tabbed_browser.close_window.connect(self.close) mode_manager.entered.connect(hints.on_mode_entered) # status bar mode_manager.entered.connect(self.status.on_mode_entered) mode_manager.left.connect(self.status.on_mode_left) mode_manager.left.connect(self.status.cmd.on_mode_left) mode_manager.left.connect( message.global_bridge.mode_left) # type: ignore # commands normal_parser = mode_manager.parsers[usertypes.KeyMode.normal] normal_parser.keystring_updated.connect( self.status.keystring.setText) self.status.cmd.got_cmd[str].connect( # type: ignore self._commandrunner.run_safely) self.status.cmd.got_cmd[str, int].connect( # type: ignore self._commandrunner.run_safely) self.status.cmd.returnPressed.connect( self.tabbed_browser.on_cmd_return_pressed) self.status.cmd.got_search.connect( self._command_dispatcher.search) # key hint popup for mode, parser in mode_manager.parsers.items(): parser.keystring_updated.connect(functools.partial( self._keyhint.update_keyhint, mode.name)) # messages message.global_bridge.show_message.connect( self._messageview.show_message) message.global_bridge.flush() message.global_bridge.clear_messages.connect( self._messageview.clear_messages) message_bridge.s_set_text.connect(self.status.set_text) message_bridge.s_maybe_reset_text.connect( self.status.txt.maybe_reset_text) # statusbar self.tabbed_browser.current_tab_changed.connect( self.status.on_tab_changed) self.tabbed_browser.cur_progress.connect( self.status.prog.on_load_progress) self.tabbed_browser.cur_load_started.connect( self.status.prog.on_load_started) self.tabbed_browser.cur_scroll_perc_changed.connect( self.status.percentage.set_perc) self.tabbed_browser.widget.tab_index_changed.connect( self.status.tabindex.on_tab_index_changed) self.tabbed_browser.cur_url_changed.connect( self.status.url.set_url) self.tabbed_browser.cur_url_changed.connect(functools.partial( self.status.backforward.on_tab_cur_url_changed, tabs=self.tabbed_browser)) self.tabbed_browser.cur_link_hovered.connect( self.status.url.set_hover_url) self.tabbed_browser.cur_load_status_changed.connect( self.status.url.on_load_status_changed) self.tabbed_browser.cur_caret_selection_toggled.connect( self.status.on_caret_selection_toggled) self.tabbed_browser.cur_fullscreen_requested.connect( self._on_fullscreen_requested) self.tabbed_browser.cur_fullscreen_requested.connect( self.status.maybe_hide) # downloadview self.tabbed_browser.cur_fullscreen_requested.connect( self._downloadview.on_fullscreen_requested) # command input / completion mode_manager.entered.connect( self.tabbed_browser.on_mode_entered) mode_manager.left.connect( self.tabbed_browser.on_mode_left) self.status.cmd.clear_completion_selection.connect( self._completion.on_clear_completion_selection) self.status.cmd.hide_completion.connect( self._completion.hide) def _set_decoration(self, hidden): """Set the visibility of the window decoration via Qt.""" window_flags = Qt.Window # type: int refresh_window = self.isVisible() if hidden: window_flags |= Qt.CustomizeWindowHint | Qt.NoDropShadowWindowHint self.setWindowFlags(window_flags) if refresh_window: self.show() @pyqtSlot(bool) def _on_fullscreen_requested(self, on): if not config.val.content.windowed_fullscreen: if on: self.state_before_fullscreen = self.windowState() self.setWindowState(Qt.WindowFullScreen | # type: ignore self.state_before_fullscreen) elif self.isFullScreen(): self.setWindowState(self.state_before_fullscreen) log.misc.debug('on: {}, state before fullscreen: {}'.format( on, debug.qflags_key(Qt, self.state_before_fullscreen))) @cmdutils.register(instance='main-window', scope='window') @pyqtSlot() def close(self): """Close the current window. // Extend close() so we can register it as a command. """ super().close() def resizeEvent(self, e): """Extend resizewindow's resizeEvent to adjust completion. Args: e: The QResizeEvent """ super().resizeEvent(e) self._update_overlay_geometries() self._downloadview.updateGeometry() self.tabbed_browser.widget.tabBar().refresh() def showEvent(self, e): """Extend showEvent to register us as the last-visible-main-window. Args: e: The QShowEvent """ super().showEvent(e) objreg.register('last-visible-main-window', self, update=True) def _confirm_quit(self): """Confirm that this window should be closed. Return: True if closing is okay, False if a closeEvent should be ignored. """ tab_count = self.tabbed_browser.widget.count() download_count = self._download_model.running_downloads() quit_texts = [] # Ask if multiple-tabs are open if 'multiple-tabs' in config.val.confirm_quit and tab_count > 1: quit_texts.append("{} tabs are open.".format(tab_count)) # Ask if multiple downloads running if 'downloads' in config.val.confirm_quit and download_count > 0: quit_texts.append("{} {} running.".format( download_count, "download is" if download_count == 1 else "downloads are")) # Process all quit messages that user must confirm if quit_texts or 'always' in config.val.confirm_quit: msg = jinja.environment.from_string("""

    {% for text in quit_texts %}
  • {{text}}
  • {% endfor %}
""".strip()).render(quit_texts=quit_texts) confirmed = message.ask('Really quit?', msg, mode=usertypes.PromptMode.yesno, default=True) # Stop asking if the user cancels if not confirmed: log.destroy.debug("Cancelling closing of window {}".format( self.win_id)) return False return True def closeEvent(self, e): """Override closeEvent to display a confirmation if needed.""" if crashsignal.crash_handler.is_crashing: e.accept() return if not self._confirm_quit(): e.ignore() return e.accept() try: last_visible = objreg.get('last-visible-main-window') if self is last_visible: objreg.delete('last-visible-main-window') except KeyError: pass sessions.session_manager.save_last_window_session() self._save_geometry() log.destroy.debug("Closing window {}".format(self.win_id)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/messageview.py0000644000175000017510000001251200000000000024714 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2016-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Showing messages above the statusbar.""" import typing from PyQt5.QtCore import pyqtSlot, pyqtSignal, QTimer, Qt, QSize from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QSizePolicy from qutebrowser.config import config, stylesheet from qutebrowser.utils import usertypes class Message(QLabel): """A single error/warning/info message.""" def __init__(self, level, text, replace, parent=None): super().__init__(text, parent) self.replace = replace self.setAttribute(Qt.WA_StyledBackground, True) qss = """ padding-top: 2px; padding-bottom: 2px; """ if level == usertypes.MessageLevel.error: qss += """ background-color: {{ conf.colors.messages.error.bg }}; color: {{ conf.colors.messages.error.fg }}; font: {{ conf.fonts.messages.error }}; border-bottom: 1px solid {{ conf.colors.messages.error.border }}; """ elif level == usertypes.MessageLevel.warning: qss += """ background-color: {{ conf.colors.messages.warning.bg }}; color: {{ conf.colors.messages.warning.fg }}; font: {{ conf.fonts.messages.warning }}; border-bottom: 1px solid {{ conf.colors.messages.warning.border }}; """ elif level == usertypes.MessageLevel.info: qss += """ background-color: {{ conf.colors.messages.info.bg }}; color: {{ conf.colors.messages.info.fg }}; font: {{ conf.fonts.messages.info }}; border-bottom: 1px solid {{ conf.colors.messages.info.border }} """ else: # pragma: no cover raise ValueError("Invalid level {!r}".format(level)) # We don't bother with set_register_stylesheet here as it's short-lived # anyways. stylesheet.set_register(self, qss, update=False) class MessageView(QWidget): """Widget which stacks error/warning/info messages.""" update_geometry = pyqtSignal() def __init__(self, parent=None): super().__init__(parent) self._messages = [] # type: typing.MutableSequence[Message] self._vbox = QVBoxLayout(self) self._vbox.setContentsMargins(0, 0, 0, 0) self._vbox.setSpacing(0) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self._clear_timer = QTimer() self._clear_timer.timeout.connect(self.clear_messages) config.instance.changed.connect(self._set_clear_timer_interval) self._last_text = None def sizeHint(self): """Get the proposed height for the view.""" height = sum(label.sizeHint().height() for label in self._messages) # The width isn't really relevant as we're expanding anyways. return QSize(-1, height) @config.change_filter('messages.timeout') def _set_clear_timer_interval(self): """Configure self._clear_timer according to the config.""" interval = config.val.messages.timeout if interval > 0: interval *= min(5, len(self._messages)) self._clear_timer.setInterval(interval) def _remove_message(self, widget): """Fully remove and destroy widget from this object.""" self._vbox.removeWidget(widget) widget.hide() widget.deleteLater() @pyqtSlot() def clear_messages(self): """Hide and delete all messages.""" for widget in self._messages: self._remove_message(widget) self._messages = [] self._last_text = None self.hide() self._clear_timer.stop() @pyqtSlot(usertypes.MessageLevel, str, bool) def show_message(self, level, text, replace=False): """Show the given message with the given MessageLevel.""" if text == self._last_text: return if replace and self._messages and self._messages[-1].replace: self._remove_message(self._messages.pop()) widget = Message(level, text, replace=replace, parent=self) self._vbox.addWidget(widget) widget.show() self._messages.append(widget) self._last_text = text self.show() self.update_geometry.emit() if config.val.messages.timeout != 0: self._set_clear_timer_interval() self._clear_timer.start() def mousePressEvent(self, e): """Clear messages when they are clicked on.""" if e.button() in [Qt.LeftButton, Qt.MiddleButton, Qt.RightButton]: self.clear_messages() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/prompt.py0000644000175000017510000010263600000000000023725 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2016-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Showing prompts above the statusbar.""" import os.path import html import collections import functools import typing import attr from PyQt5.QtCore import (pyqtSlot, pyqtSignal, Qt, QTimer, QDir, QModelIndex, QItemSelectionModel, QObject, QEventLoop) from PyQt5.QtWidgets import (QWidget, QGridLayout, QVBoxLayout, QLineEdit, QLabel, QFileSystemModel, QTreeView, QSizePolicy, QSpacerItem) from qutebrowser.browser import downloads from qutebrowser.config import config, configtypes, configexc, stylesheet from qutebrowser.utils import usertypes, log, utils, qtutils, objreg, message from qutebrowser.keyinput import modeman from qutebrowser.api import cmdutils from qutebrowser.utils import urlmatch prompt_queue = typing.cast('PromptQueue', None) @attr.s class AuthInfo: """Authentication info returned by a prompt.""" user = attr.ib() password = attr.ib() class Error(Exception): """Base class for errors in this module.""" class UnsupportedOperationError(Error): """Raised when the prompt class doesn't support the requested operation.""" class PromptQueue(QObject): """Global manager and queue for upcoming prompts. The way in which multiple questions are handled deserves some explanation. If a question is blocking, we *need* to ask it immediately, and can't wait for previous questions to finish. We could theoretically ask a blocking question inside of another blocking one, so in ask_question we simply save the current question on the stack, let the user answer the *most recent* question, and then restore the previous state. With a non-blocking question, things are a bit easier. We simply add it to self._queue if we're still busy handling another question, since it can be answered at any time. In either case, as soon as we finished handling a question, we call _pop_later() which schedules a _pop to ask the next question in _queue. We schedule it rather than doing it immediately because then the order of how things happen is clear, e.g. on_mode_left can't happen after we already set up the *new* question. Attributes: _shutting_down: Whether we're currently shutting down the prompter and should ignore future questions to avoid segfaults. _loops: A list of local EventLoops to spin in when blocking. _queue: A deque of waiting questions. _question: The current Question object if we're handling a question. Signals: show_prompts: Emitted with a Question object when prompts should be shown. """ show_prompts = pyqtSignal(usertypes.Question) def __init__(self, parent=None): super().__init__(parent) self._question = None self._shutting_down = False self._loops = [] # type: typing.MutableSequence[qtutils.EventLoop] self._queue = collections.deque( ) # type: typing.Deque[usertypes.Question] message.global_bridge.mode_left.connect(self._on_mode_left) def __repr__(self): return utils.get_repr(self, loops=len(self._loops), queue=len(self._queue), question=self._question) def _pop_later(self): """Helper to call self._pop as soon as everything else is done.""" QTimer.singleShot(0, self._pop) def _pop(self): """Pop a question from the queue and ask it, if there are any.""" log.prompt.debug("Popping from queue {}".format(self._queue)) if self._queue: question = self._queue.popleft() if not question.is_aborted: # the question could already be aborted, e.g. by a cancelled # download. See # https://github.com/qutebrowser/qutebrowser/issues/415 and # https://github.com/qutebrowser/qutebrowser/issues/1249 self.ask_question(question, blocking=False) def shutdown(self): """Cancel all blocking questions. Quits and removes all running event loops. Return: True if loops needed to be aborted, False otherwise. """ log.prompt.debug("Shutting down with loops {}".format(self._loops)) self._shutting_down = True if self._loops: for loop in self._loops: loop.quit() loop.deleteLater() return True else: return False @pyqtSlot(usertypes.Question, bool) def ask_question(self, question, blocking): """Display a prompt for a given question. Args: question: The Question object to ask. blocking: If True, this function blocks and returns the result. Return: The answer of the user when blocking=True. None if blocking=False. """ log.prompt.debug("Asking question {}, blocking {}, loops {}, queue " "{}".format(question, blocking, self._loops, self._queue)) if self._shutting_down: # If we're currently shutting down we have to ignore this question # to avoid segfaults - see # https://github.com/qutebrowser/qutebrowser/issues/95 log.prompt.debug("Ignoring question because we're shutting down.") question.abort() return None if self._question is not None and not blocking: # We got an async question, but we're already busy with one, so we # just queue it up for later. log.prompt.debug("Adding {} to queue.".format(question)) self._queue.append(question) return None if blocking: # If we're blocking we save the old question on the stack, so we # can restore it after exec, if exec gets called multiple times. log.prompt.debug("New question is blocking, saving {}".format( self._question)) old_question = self._question if old_question is not None: old_question.interrupted = True self._question = question self.show_prompts.emit(question) if blocking: loop = qtutils.EventLoop() self._loops.append(loop) loop.destroyed.connect( # type: ignore lambda: self._loops.remove(loop)) question.completed.connect(loop.quit) question.completed.connect(loop.deleteLater) log.prompt.debug("Starting loop.exec_() for {}".format(question)) loop.exec_(QEventLoop.ExcludeSocketNotifiers) log.prompt.debug("Ending loop.exec_() for {}".format(question)) log.prompt.debug("Restoring old question {}".format(old_question)) self._question = old_question self.show_prompts.emit(old_question) if old_question is None: # Nothing left to restore, so we can go back to popping async # questions. if self._queue: self._pop_later() return question.answer else: question.completed.connect(self._pop_later) return None @pyqtSlot(usertypes.KeyMode) def _on_mode_left(self, mode): """Abort question when a prompt mode was left.""" if mode not in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]: return if self._question is None: return log.prompt.debug("Left mode {}, hiding {}".format( mode, self._question)) self.show_prompts.emit(None) if self._question.answer is None and not self._question.is_aborted: log.prompt.debug("Cancelling {} because {} was left".format( self._question, mode)) self._question.cancel() self._question = None class PromptContainer(QWidget): """Container for prompts to be shown above the statusbar. This is a per-window object, however each window shows the same prompt. Attributes: _layout: The layout used to show prompts in. _win_id: The window ID this object is associated with. Signals: update_geometry: Emitted when the geometry should be updated. """ STYLESHEET = """ QWidget#PromptContainer { {% if conf.statusbar.position == 'top' %} border-bottom-left-radius: {{ conf.prompt.radius }}px; border-bottom-right-radius: {{ conf.prompt.radius }}px; {% else %} border-top-left-radius: {{ conf.prompt.radius }}px; border-top-right-radius: {{ conf.prompt.radius }}px; {% endif %} } QWidget { font: {{ conf.fonts.prompts }}; color: {{ conf.colors.prompts.fg }}; background-color: {{ conf.colors.prompts.bg }}; } QLineEdit { border: {{ conf.colors.prompts.border }}; } QTreeView { selection-background-color: {{ conf.colors.prompts.selected.bg }}; border: {{ conf.colors.prompts.border }}; } QTreeView::branch { background-color: {{ conf.colors.prompts.bg }}; } QTreeView::item:selected, QTreeView::item:selected:hover, QTreeView::branch:selected { background-color: {{ conf.colors.prompts.selected.bg }}; } """ update_geometry = pyqtSignal() def __init__(self, win_id, parent=None): super().__init__(parent) self._layout = QVBoxLayout(self) self._layout.setContentsMargins(10, 10, 10, 10) self._win_id = win_id self._prompt = None # type: typing.Optional[_BasePrompt] self.setObjectName('PromptContainer') self.setAttribute(Qt.WA_StyledBackground, True) stylesheet.set_register(self) message.global_bridge.prompt_done.connect(self._on_prompt_done) prompt_queue.show_prompts.connect(self._on_show_prompts) message.global_bridge.mode_left.connect(self._on_global_mode_left) def __repr__(self): return utils.get_repr(self, win_id=self._win_id) @pyqtSlot(usertypes.Question) def _on_show_prompts(self, question): """Show a prompt for the given question. Args: question: A Question object or None. """ item = self._layout.takeAt(0) if item is not None: widget = item.widget() log.prompt.debug("Deleting old prompt {}".format(widget)) widget.hide() widget.deleteLater() if question is None: log.prompt.debug("No prompts left, hiding prompt container.") self._prompt = None self.hide() return classes = { usertypes.PromptMode.yesno: YesNoPrompt, usertypes.PromptMode.text: LineEditPrompt, usertypes.PromptMode.user_pwd: AuthenticationPrompt, usertypes.PromptMode.download: DownloadFilenamePrompt, usertypes.PromptMode.alert: AlertPrompt, } klass = classes[question.mode] prompt = typing.cast(_BasePrompt, klass(question)) log.prompt.debug("Displaying prompt {}".format(prompt)) self._prompt = prompt # If this question was interrupted, we already connected the signal if not question.interrupted: question.aborted.connect( functools.partial(self._on_aborted, prompt.KEY_MODE)) modeman.enter(self._win_id, prompt.KEY_MODE, 'question asked') self.setSizePolicy(prompt.sizePolicy()) self._layout.addWidget(prompt) prompt.show() self.show() prompt.setFocus() self.update_geometry.emit() @pyqtSlot() def _on_aborted(self, key_mode): """Leave KEY_MODE whenever a prompt is aborted.""" try: modeman.leave(self._win_id, key_mode, 'aborted', maybe=True) except objreg.RegistryUnavailableError: # window was deleted: ignore pass @pyqtSlot(usertypes.KeyMode) def _on_prompt_done(self, key_mode): """Leave the prompt mode in this window if a question was answered.""" modeman.leave(self._win_id, key_mode, ':prompt-accept', maybe=True) @pyqtSlot(usertypes.KeyMode) def _on_global_mode_left(self, mode): """Leave prompt/yesno mode in this window if it was left elsewhere. This ensures no matter where a prompt was answered, we leave the prompt mode and dispose of the prompt object in every window. """ if mode not in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]: return modeman.leave(self._win_id, mode, 'left in other window', maybe=True) item = self._layout.takeAt(0) if item is not None: widget = item.widget() log.prompt.debug("Deleting prompt {}".format(widget)) widget.hide() widget.deleteLater() @cmdutils.register(instance='prompt-container', scope='window', modes=[usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]) def prompt_accept(self, value=None, *, save=False): """Accept the current prompt. // This executes the next action depending on the question mode, e.g. asks for the password or leaves the mode. Args: value: If given, uses this value instead of the entered one. For boolean prompts, "yes"/"no" are accepted as value. save: Save the value to the config. """ assert self._prompt is not None question = self._prompt.question try: done = self._prompt.accept(value, save=save) except Error as e: raise cmdutils.CommandError(str(e)) if done: message.global_bridge.prompt_done.emit(self._prompt.KEY_MODE) question.done() @cmdutils.register(instance='prompt-container', scope='window', modes=[usertypes.KeyMode.prompt], maxsplit=0) def prompt_open_download(self, cmdline: str = None, pdfjs: bool = False) -> None: """Immediately open a download. If no specific command is given, this will use the system's default application to open the file. Args: cmdline: The command which should be used to open the file. A `{}` is expanded to the temporary file name. If no `{}` is present, the filename is automatically appended to the cmdline. pdfjs: Open the download via PDF.js. """ assert self._prompt is not None try: self._prompt.download_open(cmdline, pdfjs=pdfjs) except UnsupportedOperationError: pass @cmdutils.register(instance='prompt-container', scope='window', modes=[usertypes.KeyMode.prompt]) @cmdutils.argument('which', choices=['next', 'prev']) def prompt_item_focus(self, which): """Shift the focus of the prompt file completion menu to another item. Args: which: 'next', 'prev' """ assert self._prompt is not None try: self._prompt.item_focus(which) except UnsupportedOperationError: pass @cmdutils.register( instance='prompt-container', scope='window', modes=[usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]) def prompt_yank(self, sel=False): """Yank URL to clipboard or primary selection. Args: sel: Use the primary selection instead of the clipboard. """ assert self._prompt is not None question = self._prompt.question if question.url is None: message.error('No URL found.') return if sel and utils.supports_selection(): target = 'primary selection' else: sel = False target = 'clipboard' utils.set_clipboard(question.url, sel) message.info("Yanked to {}: {}".format(target, question.url)) class LineEdit(QLineEdit): """A line edit used in prompts.""" def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet(""" QLineEdit { background-color: transparent; } """) self.setAttribute(Qt.WA_MacShowFocusRect, False) def keyPressEvent(self, e): """Override keyPressEvent to paste primary selection on Shift + Ins.""" if e.key() == Qt.Key_Insert and e.modifiers() == Qt.ShiftModifier: try: text = utils.get_clipboard(selection=True, fallback=True) except utils.ClipboardError: # pragma: no cover e.ignore() else: e.accept() self.insert(text) return super().keyPressEvent(e) def __repr__(self): return utils.get_repr(self) class _BasePrompt(QWidget): """Base class for all prompts.""" KEY_MODE = usertypes.KeyMode.prompt def __init__(self, question, parent=None): super().__init__(parent) self.question = question self._vbox = QVBoxLayout(self) self._vbox.setSpacing(15) self._key_grid = None def __repr__(self): return utils.get_repr(self, question=self.question, constructor=True) def _init_texts(self, question): assert question.title is not None, question title = '{}'.format( html.escape(question.title)) title_label = QLabel(title, self) self._vbox.addWidget(title_label) if question.text is not None: # Not doing any HTML escaping here as the text can be formatted text_label = QLabel(question.text) text_label.setTextInteractionFlags(Qt.TextSelectableByMouse) self._vbox.addWidget(text_label) def _init_key_label(self): assert self._key_grid is None, self._key_grid self._key_grid = QGridLayout() self._key_grid.setVerticalSpacing(0) all_bindings = config.key_instance.get_reverse_bindings_for( self.KEY_MODE.name) labels = [] for cmd, text in self._allowed_commands(): bindings = all_bindings.get(cmd, []) if bindings: binding = None preferred = ['', ''] for pref in preferred: if pref in bindings: binding = pref if binding is None: binding = bindings[0] key_label = QLabel('{}'.format(html.escape(binding))) text_label = QLabel(text) labels.append((key_label, text_label)) for i, (key_label, text_label) in enumerate(labels): self._key_grid.addWidget(key_label, i, 0) self._key_grid.addWidget(text_label, i, 1) spacer = QSpacerItem(0, 0, QSizePolicy.Expanding) self._key_grid.addItem(spacer, 0, 2) self._vbox.addLayout(self._key_grid) def _check_save_support(self, save): if save: raise UnsupportedOperationError("Saving answers is only possible " "with yes/no prompts.") def accept(self, value=None, save=False): raise NotImplementedError def download_open(self, cmdline, pdfjs): """Open the download directly if this is a download prompt.""" utils.unused(cmdline) utils.unused(pdfjs) raise UnsupportedOperationError def item_focus(self, _which): """Switch to next file item if this is a filename prompt..""" raise UnsupportedOperationError def _allowed_commands(self): """Get the commands we could run as response to this message.""" raise NotImplementedError class LineEditPrompt(_BasePrompt): """A prompt for a single text value.""" def __init__(self, question, parent=None): super().__init__(question, parent) self._lineedit = LineEdit(self) self._init_texts(question) self._vbox.addWidget(self._lineedit) if question.default: self._lineedit.setText(question.default) self.setFocusProxy(self._lineedit) self._init_key_label() def accept(self, value=None, save=False): self._check_save_support(save) text = value if value is not None else self._lineedit.text() self.question.answer = text return True def _allowed_commands(self): return [('prompt-accept', 'Accept'), ('leave-mode', 'Abort')] class FilenamePrompt(_BasePrompt): """A prompt for a filename.""" def __init__(self, question, parent=None): super().__init__(question, parent) self._init_texts(question) self._init_key_label() self._lineedit = LineEdit(self) if question.default: self._lineedit.setText(question.default) self._lineedit.textEdited.connect(self._set_fileview_root) self._vbox.addWidget(self._lineedit) self.setFocusProxy(self._lineedit) self._init_fileview() self._set_fileview_root(question.default) if config.val.prompt.filebrowser: self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) self._to_complete = '' @pyqtSlot(str) def _set_fileview_root(self, path, *, tabbed=False): """Set the root path for the file display.""" separators = os.sep if os.altsep is not None: separators += os.altsep dirname = os.path.dirname(path) basename = os.path.basename(path) if not tabbed: self._to_complete = '' try: if not path: pass elif path in separators and os.path.isdir(path): # Input "/" -> don't strip anything pass elif path[-1] in separators and os.path.isdir(path): # Input like /foo/bar/ -> show /foo/bar/ contents path = path.rstrip(separators) elif os.path.isdir(dirname) and not tabbed: # Input like /foo/ba -> show /foo contents path = dirname self._to_complete = basename else: return except OSError: log.prompt.exception("Failed to get directory information") return root = self._file_model.setRootPath(path) self._file_view.setRootIndex(root) @pyqtSlot(QModelIndex) def _insert_path(self, index, *, clicked=True): """Handle an element selection. Args: index: The QModelIndex of the selected element. clicked: Whether the element was clicked. """ if index == QModelIndex(): path = os.path.join(self._file_model.rootPath(), self._to_complete) else: path = os.path.normpath(self._file_model.filePath(index)) if clicked: path += os.sep else: # On Windows, when we have C:\foo and tab over .., we get C:\ path = path.rstrip(os.sep) log.prompt.debug('Inserting path {}'.format(path)) self._lineedit.setText(path) self._lineedit.setFocus() self._set_fileview_root(path, tabbed=True) if clicked: # Avoid having a ..-subtree highlighted self._file_view.setCurrentIndex(QModelIndex()) def _init_fileview(self): self._file_view = QTreeView(self) self._file_model = QFileSystemModel(self) self._file_view.setModel(self._file_model) self._file_view.clicked.connect(self._insert_path) if config.val.prompt.filebrowser: self._vbox.addWidget(self._file_view) else: self._file_view.hide() # Only show name self._file_view.setHeaderHidden(True) for col in range(1, 4): self._file_view.setColumnHidden(col, True) # Nothing selected initially self._file_view.setCurrentIndex(QModelIndex()) # The model needs to be sorted so we get the correct first/last index self._file_model.directoryLoaded.connect( # type: ignore lambda: self._file_model.sort(0)) def accept(self, value=None, save=False): self._check_save_support(save) text = value if value is not None else self._lineedit.text() text = downloads.transform_path(text) if text is None: message.error("Invalid filename") return False self.question.answer = text return True def item_focus(self, which): # This duplicates some completion code, but I don't see a nicer way... assert which in ['prev', 'next'], which selmodel = self._file_view.selectionModel() parent = self._file_view.rootIndex() first_index = self._file_model.index(0, 0, parent) row = self._file_model.rowCount(parent) - 1 last_index = self._file_model.index(row, 0, parent) if not first_index.isValid(): # No entries return assert last_index.isValid() idx = selmodel.currentIndex() if not idx.isValid(): # No item selected yet idx = last_index if which == 'prev' else first_index elif which == 'prev': idx = self._file_view.indexAbove(idx) else: assert which == 'next', which idx = self._file_view.indexBelow(idx) # wrap around if we arrived at beginning/end if not idx.isValid(): idx = last_index if which == 'prev' else first_index idx = self._do_completion(idx, which) selmodel.setCurrentIndex( idx, QItemSelectionModel.ClearAndSelect | # type: ignore QItemSelectionModel.Rows) self._insert_path(idx, clicked=False) def _do_completion(self, idx, which): filename = self._file_model.fileName(idx) while not filename.startswith(self._to_complete) and idx.isValid(): if which == 'prev': idx = self._file_view.indexAbove(idx) else: assert which == 'next', which idx = self._file_view.indexBelow(idx) filename = self._file_model.fileName(idx) return idx def _allowed_commands(self): return [('prompt-accept', 'Accept'), ('leave-mode', 'Abort')] class DownloadFilenamePrompt(FilenamePrompt): """A prompt for a filename for downloads.""" def __init__(self, question, parent=None): super().__init__(question, parent) self._file_model.setFilter( QDir.AllDirs | QDir.Drives | QDir.NoDot) # type: ignore def accept(self, value=None, save=False): done = super().accept(value, save) answer = self.question.answer if answer is not None: self.question.answer = downloads.FileDownloadTarget(answer) return done def download_open(self, cmdline, pdfjs): if pdfjs: target = downloads.PDFJSDownloadTarget( ) # type: downloads._DownloadTarget else: target = downloads.OpenFileDownloadTarget(cmdline) self.question.answer = target self.question.done() message.global_bridge.prompt_done.emit(self.KEY_MODE) def _allowed_commands(self): cmds = [ ('prompt-accept', 'Accept'), ('leave-mode', 'Abort'), ('prompt-open-download', "Open download"), ('prompt-open-download --pdfjs', "Open download via PDF.js"), ('prompt-yank', "Yank URL"), ] return cmds class AuthenticationPrompt(_BasePrompt): """A prompt for username/password.""" def __init__(self, question, parent=None): super().__init__(question, parent) self._init_texts(question) user_label = QLabel("Username:", self) self._user_lineedit = LineEdit(self) password_label = QLabel("Password:", self) self._password_lineedit = LineEdit(self) self._password_lineedit.setEchoMode(QLineEdit.Password) grid = QGridLayout() grid.addWidget(user_label, 1, 0) grid.addWidget(self._user_lineedit, 1, 1) grid.addWidget(password_label, 2, 0) grid.addWidget(self._password_lineedit, 2, 1) self._vbox.addLayout(grid) self._init_key_label() assert not question.default, question.default self.setFocusProxy(self._user_lineedit) def accept(self, value=None, save=False): self._check_save_support(save) if value is not None: if ':' not in value: raise Error("Value needs to be in the format " "username:password, but {} was given".format( value)) username, password = value.split(':', maxsplit=1) self.question.answer = AuthInfo(username, password) return True elif self._user_lineedit.hasFocus(): # Earlier, tab was bound to :prompt-accept, so to still support # that we simply switch the focus when tab was pressed. self._password_lineedit.setFocus() return False else: self.question.answer = AuthInfo(self._user_lineedit.text(), self._password_lineedit.text()) return True def item_focus(self, which): """Support switching between fields with tab.""" assert which in ['prev', 'next'], which if which == 'next' and self._user_lineedit.hasFocus(): self._password_lineedit.setFocus() elif which == 'prev' and self._password_lineedit.hasFocus(): self._user_lineedit.setFocus() def _allowed_commands(self): return [('prompt-accept', "Accept"), ('leave-mode', "Abort")] class YesNoPrompt(_BasePrompt): """A prompt with yes/no answers.""" KEY_MODE = usertypes.KeyMode.yesno def __init__(self, question, parent=None): super().__init__(question, parent) self._init_texts(question) self._init_key_label() def _check_save_support(self, save): if save and self.question.option is None: raise Error("No setting available to save the answer for this " "question.") def accept(self, value=None, save=False): self._check_save_support(save) if value is None: if self.question.default is None: raise Error("No default value was set for this question!") self.question.answer = self.question.default elif value == 'yes': self.question.answer = True elif value == 'no': self.question.answer = False else: raise Error("Invalid value {} - expected yes/no!".format(value)) if save: opt = config.instance.get_opt(self.question.option) assert isinstance(opt.typ, configtypes.Bool) pattern = urlmatch.UrlPattern(self.question.url) try: config.instance.set_obj(opt.name, self.question.answer, pattern=pattern, save_yaml=True) except configexc.Error as e: raise Error(str(e)) return True def _allowed_commands(self): cmds = [] cmds.append(('prompt-accept yes', "Yes")) if self.question.option is not None: cmds.append(('prompt-accept --save yes', "Always")) cmds.append(('prompt-accept no', "No")) if self.question.option is not None: cmds.append(('prompt-accept --save no', "Never")) if self.question.default is not None: assert self.question.default in [True, False] default = 'yes' if self.question.default else 'no' cmds.append(('prompt-accept', "Use default ({})".format(default))) cmds.append(('leave-mode', "Abort")) cmds.append(('prompt-yank', "Yank URL")) return cmds class AlertPrompt(_BasePrompt): """A prompt without any answer possibility.""" def __init__(self, question, parent=None): super().__init__(question, parent) self._init_texts(question) self._init_key_label() def accept(self, value=None, save=False): self._check_save_support(save) if value is not None: raise Error("No value is permitted with alert prompts!") # Simply mark prompt as done without setting self.question.answer return True def _allowed_commands(self): return [('prompt-accept', "Hide")] def init(): """Initialize global prompt objects.""" global prompt_queue prompt_queue = PromptQueue() message.global_bridge.ask_question.connect( # type: ignore prompt_queue.ask_question, Qt.DirectConnection) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1581772733.904756 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/0000755000175000017510000000000000000000000024032 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/__init__.py0000644000175000017510000000150700000000000026146 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Widgets needed for the statusbar.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/backforward.py0000644000175000017510000000337000000000000026674 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2017-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Navigation (back/forward) indicator displayed in the statusbar.""" from qutebrowser.mainwindow.statusbar import textbase class Backforward(textbase.TextBase): """Shows navigation indicator (if you can go backward and/or forward).""" def __init__(self, parent=None): super().__init__(parent) self.enabled = False def on_tab_cur_url_changed(self, tabs): """Called on URL changes.""" tab = tabs.widget.currentWidget() if tab is None: # pragma: no cover self.setText('') self.hide() return self.on_tab_changed(tab) def on_tab_changed(self, tab): """Update the text based on the given tab.""" text = '' if tab.history.can_go_back(): text += '<' if tab.history.can_go_forward(): text += '>' if text: text = '[' + text + ']' self.setText(text) self.setVisible(bool(text) and self.enabled) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/bar.py0000644000175000017510000003555500000000000025165 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """The main statusbar widget.""" import enum import attr from PyQt5.QtCore import pyqtSignal, pyqtSlot, pyqtProperty, Qt, QSize, QTimer from PyQt5.QtWidgets import QWidget, QHBoxLayout, QStackedLayout, QSizePolicy from qutebrowser.browser import browsertab from qutebrowser.config import config, stylesheet from qutebrowser.keyinput import modeman from qutebrowser.utils import usertypes, log, objreg, utils from qutebrowser.mainwindow.statusbar import (backforward, command, progress, keystring, percentage, url, tabindex) from qutebrowser.mainwindow.statusbar import text as textwidget @attr.s class ColorFlags: """Flags which change the appearance of the statusbar. Attributes: prompt: If we're currently in prompt-mode. insert: If we're currently in insert mode. command: If we're currently in command mode. mode: The current caret mode (CaretMode.off/.on/.selection). private: Whether this window is in private browsing mode. passthrough: If we're currently in passthrough-mode. """ CaretMode = enum.Enum('CaretMode', ['off', 'on', 'selection']) prompt = attr.ib(False) insert = attr.ib(False) command = attr.ib(False) caret = attr.ib(CaretMode.off) private = attr.ib(False) passthrough = attr.ib(False) def to_stringlist(self): """Get a string list of set flags used in the stylesheet. This also combines flags in ways they're used in the sheet. """ strings = [] if self.prompt: strings.append('prompt') if self.insert: strings.append('insert') if self.command: strings.append('command') if self.private: strings.append('private') if self.passthrough: strings.append('passthrough') if self.private and self.command: strings.append('private-command') if self.caret == self.CaretMode.on: strings.append('caret') elif self.caret == self.CaretMode.selection: strings.append('caret-selection') else: assert self.caret == self.CaretMode.off return strings def _generate_stylesheet(): flags = [ ('private', 'statusbar.private'), ('caret', 'statusbar.caret'), ('caret-selection', 'statusbar.caret.selection'), ('prompt', 'prompts'), ('insert', 'statusbar.insert'), ('command', 'statusbar.command'), ('passthrough', 'statusbar.passthrough'), ('private-command', 'statusbar.command.private'), ] qss = """ QWidget#StatusBar, QWidget#StatusBar QLabel, QWidget#StatusBar QLineEdit { font: {{ conf.fonts.statusbar }}; color: {{ conf.colors.statusbar.normal.fg }}; } QWidget#StatusBar { background-color: {{ conf.colors.statusbar.normal.bg }}; } """ for flag, option in flags: qss += """ QWidget#StatusBar[color_flags~="%s"], QWidget#StatusBar[color_flags~="%s"] QLabel, QWidget#StatusBar[color_flags~="%s"] QLineEdit { color: {{ conf.colors.%s }}; } QWidget#StatusBar[color_flags~="%s"] { background-color: {{ conf.colors.%s }}; } """ % (flag, flag, flag, # noqa: S001 option + '.fg', flag, option + '.bg') return qss class StatusBar(QWidget): """The statusbar at the bottom of the mainwindow. Attributes: txt: The Text widget in the statusbar. keystring: The KeyString widget in the statusbar. percentage: The Percentage widget in the statusbar. url: The UrlText widget in the statusbar. prog: The Progress widget in the statusbar. cmd: The Command widget in the statusbar. _hbox: The main QHBoxLayout. _stack: The QStackedLayout with cmd/txt widgets. _win_id: The window ID the statusbar is associated with. Signals: resized: Emitted when the statusbar has resized, so the completion widget can adjust its size to it. arg: The new size. moved: Emitted when the statusbar has moved, so the completion widget can move to the right position. arg: The new position. """ resized = pyqtSignal('QRect') moved = pyqtSignal('QPoint') STYLESHEET = _generate_stylesheet() def __init__(self, *, win_id, private, parent=None): super().__init__(parent) self.setObjectName(self.__class__.__name__) self.setAttribute(Qt.WA_StyledBackground) stylesheet.set_register(self) self.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) self._win_id = win_id self._color_flags = ColorFlags() self._color_flags.private = private self._hbox = QHBoxLayout(self) self._set_hbox_padding() self._hbox.setSpacing(5) self._stack = QStackedLayout() self._hbox.addLayout(self._stack) self._stack.setContentsMargins(0, 0, 0, 0) self.cmd = command.Command(private=private, win_id=win_id) self._stack.addWidget(self.cmd) objreg.register('status-command', self.cmd, scope='window', window=win_id) self.txt = textwidget.Text() self._stack.addWidget(self.txt) self.cmd.show_cmd.connect(self._show_cmd_widget) self.cmd.hide_cmd.connect(self._hide_cmd_widget) self._hide_cmd_widget() self.url = url.UrlText() self.percentage = percentage.Percentage() self.backforward = backforward.Backforward() self.tabindex = tabindex.TabIndex() self.keystring = keystring.KeyString() self.prog = progress.Progress(self) self._draw_widgets() config.instance.changed.connect(self._on_config_changed) QTimer.singleShot(0, self.maybe_hide) def __repr__(self): return utils.get_repr(self) @pyqtSlot(str) def _on_config_changed(self, option): if option == 'statusbar.hide': self.maybe_hide() elif option == 'statusbar.padding': self._set_hbox_padding() elif option == 'statusbar.widgets': self._draw_widgets() def _draw_widgets(self): """Draw statusbar widgets.""" # Start with widgets hidden and show them when needed for widget in [self.url, self.percentage, self.backforward, self.tabindex, self.keystring, self.prog]: assert isinstance(widget, QWidget) widget.hide() self._hbox.removeWidget(widget) tab = self._current_tab() # Read the list and set widgets accordingly for segment in config.val.statusbar.widgets: if segment == 'url': self._hbox.addWidget(self.url) self.url.show() elif segment == 'scroll': self._hbox.addWidget(self.percentage) self.percentage.show() elif segment == 'scroll_raw': self._hbox.addWidget(self.percentage) self.percentage.set_raw() self.percentage.show() elif segment == 'history': self._hbox.addWidget(self.backforward) self.backforward.enabled = True if tab: self.backforward.on_tab_changed(tab) elif segment == 'tabs': self._hbox.addWidget(self.tabindex) self.tabindex.show() elif segment == 'keypress': self._hbox.addWidget(self.keystring) self.keystring.show() elif segment == 'progress': self._hbox.addWidget(self.prog) self.prog.enabled = True if tab: self.prog.on_tab_changed(tab) @pyqtSlot() def maybe_hide(self): """Hide the statusbar if it's configured to do so.""" tab = self._current_tab() hide = config.val.statusbar.hide if hide or (tab is not None and tab.data.fullscreen): self.hide() else: self.show() def _set_hbox_padding(self): padding = config.val.statusbar.padding self._hbox.setContentsMargins(padding.left, 0, padding.right, 0) @pyqtProperty('QStringList') def color_flags(self): """Getter for self.color_flags, so it can be used as Qt property.""" return self._color_flags.to_stringlist() def _current_tab(self): """Get the currently displayed tab.""" window = objreg.get('tabbed-browser', scope='window', window=self._win_id) return window.widget.currentWidget() def set_mode_active(self, mode, val): """Setter for self.{insert,command,caret}_active. Re-set the stylesheet after setting the value, so everything gets updated by Qt properly. """ if mode == usertypes.KeyMode.insert: log.statusbar.debug("Setting insert flag to {}".format(val)) self._color_flags.insert = val if mode == usertypes.KeyMode.passthrough: log.statusbar.debug("Setting passthrough flag to {}".format(val)) self._color_flags.passthrough = val if mode == usertypes.KeyMode.command: log.statusbar.debug("Setting command flag to {}".format(val)) self._color_flags.command = val elif mode in [usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]: log.statusbar.debug("Setting prompt flag to {}".format(val)) self._color_flags.prompt = val elif mode == usertypes.KeyMode.caret: if not val: # Turning on is handled in on_current_caret_selection_toggled log.statusbar.debug("Setting caret mode off") self._color_flags.caret = ColorFlags.CaretMode.off stylesheet.set_register(self, update=False) def _set_mode_text(self, mode): """Set the mode text.""" if mode == 'passthrough': key_instance = config.key_instance all_bindings = key_instance.get_reverse_bindings_for('passthrough') bindings = all_bindings.get('leave-mode') if bindings: suffix = ' ({} to leave)'.format(' or '.join(bindings)) else: suffix = '' else: suffix = '' text = "-- {} MODE --{}".format(mode.upper(), suffix) self.txt.set_text(self.txt.Text.normal, text) def _show_cmd_widget(self): """Show command widget instead of temporary text.""" self._stack.setCurrentWidget(self.cmd) self.show() def _hide_cmd_widget(self): """Show temporary text instead of command widget.""" log.statusbar.debug("Hiding cmd widget") self._stack.setCurrentWidget(self.txt) self.maybe_hide() @pyqtSlot(str) def set_text(self, val): """Set a normal (persistent) text in the status bar.""" self.txt.set_text(self.txt.Text.normal, val) @pyqtSlot(usertypes.KeyMode) def on_mode_entered(self, mode): """Mark certain modes in the commandline.""" mode_manager = modeman.instance(self._win_id) if mode_manager.parsers[mode].passthrough: self._set_mode_text(mode.name) if mode in [usertypes.KeyMode.insert, usertypes.KeyMode.command, usertypes.KeyMode.caret, usertypes.KeyMode.prompt, usertypes.KeyMode.yesno, usertypes.KeyMode.passthrough]: self.set_mode_active(mode, True) @pyqtSlot(usertypes.KeyMode, usertypes.KeyMode) def on_mode_left(self, old_mode, new_mode): """Clear marked mode.""" mode_manager = modeman.instance(self._win_id) if mode_manager.parsers[old_mode].passthrough: if mode_manager.parsers[new_mode].passthrough: self._set_mode_text(new_mode.name) else: self.txt.set_text(self.txt.Text.normal, '') if old_mode in [usertypes.KeyMode.insert, usertypes.KeyMode.command, usertypes.KeyMode.caret, usertypes.KeyMode.prompt, usertypes.KeyMode.yesno, usertypes.KeyMode.passthrough]: self.set_mode_active(old_mode, False) @pyqtSlot(browsertab.AbstractTab) def on_tab_changed(self, tab): """Notify sub-widgets when the tab has been changed.""" self.url.on_tab_changed(tab) self.prog.on_tab_changed(tab) self.percentage.on_tab_changed(tab) self.backforward.on_tab_changed(tab) self.maybe_hide() assert tab.is_private == self._color_flags.private @pyqtSlot(bool) def on_caret_selection_toggled(self, selection): """Update the statusbar when entering/leaving caret selection mode.""" log.statusbar.debug("Setting caret selection {}".format(selection)) if selection: self._set_mode_text("caret selection") self._color_flags.caret = ColorFlags.CaretMode.selection else: self._set_mode_text("caret") self._color_flags.caret = ColorFlags.CaretMode.on stylesheet.set_register(self, update=False) def resizeEvent(self, e): """Extend resizeEvent of QWidget to emit a resized signal afterwards. Args: e: The QResizeEvent. """ super().resizeEvent(e) self.resized.emit(self.geometry()) def moveEvent(self, e): """Extend moveEvent of QWidget to emit a moved signal afterwards. Args: e: The QMoveEvent. """ super().moveEvent(e) self.moved.emit(e.pos()) def minimumSizeHint(self): """Set the minimum height to the text height plus some padding.""" padding = config.cache['statusbar.padding'] width = super().minimumSizeHint().width() height = self.fontMetrics().height() + padding.top + padding.bottom return QSize(width, height) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/command.py0000644000175000017510000002443400000000000026031 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """The commandline in the statusbar.""" from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QSize from PyQt5.QtGui import QKeyEvent from PyQt5.QtWidgets import QSizePolicy, QWidget from qutebrowser.keyinput import modeman, modeparsers from qutebrowser.api import cmdutils from qutebrowser.misc import cmdhistory, editor from qutebrowser.misc import miscwidgets as misc from qutebrowser.utils import usertypes, log, objreg, message, utils from qutebrowser.config import config class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit): """The commandline part of the statusbar. Attributes: _win_id: The window ID this widget is associated with. Signals: got_cmd: Emitted when a command is triggered by the user. arg: The command string and also potentially the count. got_search: Emitted when a search should happen. clear_completion_selection: Emitted before the completion widget is hidden. hide_completion: Emitted when the completion widget should be hidden. update_completion: Emitted when the completion should be shown/updated. show_cmd: Emitted when command input should be shown. hide_cmd: Emitted when command input can be hidden. """ got_cmd = pyqtSignal([str], [str, int]) got_search = pyqtSignal(str, bool) # text, reverse clear_completion_selection = pyqtSignal() hide_completion = pyqtSignal() update_completion = pyqtSignal() show_cmd = pyqtSignal() hide_cmd = pyqtSignal() def __init__(self, *, win_id: int, private: bool, parent: QWidget = None) -> None: misc.CommandLineEdit.__init__(self, parent=parent) misc.MinimalLineEditMixin.__init__(self) self._win_id = win_id if not private: command_history = objreg.get('command-history') self.history.history = command_history.data self.history.changed.connect(command_history.changed) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Ignored) self.cursorPositionChanged.connect( self.update_completion) # type: ignore self.textChanged.connect(self.update_completion) # type: ignore self.textChanged.connect(self.updateGeometry) self.textChanged.connect(self._incremental_search) def _handle_search(self) -> bool: """Check if the currently entered text is a search, and if so, run it. Return: True if a search was executed, False otherwise. """ if self.prefix() == '/': self.got_search.emit(self.text()[1:], False) return True elif self.prefix() == '?': self.got_search.emit(self.text()[1:], True) return True else: return False def prefix(self) -> str: """Get the currently entered command prefix.""" text = self.text() if not text: return '' elif text[0] in modeparsers.STARTCHARS: return text[0] else: return '' def set_cmd_text(self, text: str) -> None: """Preset the statusbar to some text. Args: text: The text to set as string. """ self.setText(text) log.modes.debug("Setting command text, focusing {!r}".format(self)) modeman.enter(self._win_id, usertypes.KeyMode.command, 'cmd focus') self.setFocus() self.show_cmd.emit() @cmdutils.register(instance='status-command', name='set-cmd-text', scope='window', maxsplit=0) @cmdutils.argument('count', value=cmdutils.Value.count) def set_cmd_text_command(self, text: str, count: int = None, space: bool = False, append: bool = False, run_on_count: bool = False) -> None: """Preset the statusbar to some text. // Wrapper for set_cmd_text to check the arguments and allow multiple strings which will get joined. Args: text: The commandline to set. count: The count if given. space: If given, a space is added to the end. append: If given, the text is appended to the current text. run_on_count: If given with a count, the command is run with the given count rather than setting the command text. """ if space: text += ' ' if append: if not self.text(): raise cmdutils.CommandError("No current text!") text = self.text() + text if not text or text[0] not in modeparsers.STARTCHARS: raise cmdutils.CommandError( "Invalid command text '{}'.".format(text)) if run_on_count and count is not None: self.got_cmd[str, int].emit(text, count) # type: ignore else: self.set_cmd_text(text) @cmdutils.register(instance='status-command', modes=[usertypes.KeyMode.command], scope='window') def command_history_prev(self) -> None: """Go back in the commandline history.""" try: if not self.history.is_browsing(): item = self.history.start(self.text().strip()) else: item = self.history.previtem() except (cmdhistory.HistoryEmptyError, cmdhistory.HistoryEndReachedError): return if item: self.set_cmd_text(item) @cmdutils.register(instance='status-command', modes=[usertypes.KeyMode.command], scope='window') def command_history_next(self) -> None: """Go forward in the commandline history.""" if not self.history.is_browsing(): return try: item = self.history.nextitem() except cmdhistory.HistoryEndReachedError: return if item: self.set_cmd_text(item) @cmdutils.register(instance='status-command', modes=[usertypes.KeyMode.command], scope='window') def command_accept(self, rapid: bool = False) -> None: """Execute the command currently in the commandline. Args: rapid: Run the command without closing or clearing the command bar. """ text = self.text() self.history.append(text) was_search = self._handle_search() if not rapid: modeman.leave(self._win_id, usertypes.KeyMode.command, 'cmd accept') if not was_search: self.got_cmd[str].emit(text[1:]) # type: ignore @cmdutils.register(instance='status-command', scope='window') def edit_command(self, run: bool = False) -> None: """Open an editor to modify the current command. Args: run: Run the command if the editor exits successfully. """ ed = editor.ExternalEditor(parent=self) def callback(text: str) -> None: """Set the commandline to the edited text.""" if not text or text[0] not in modeparsers.STARTCHARS: message.error('command must start with one of {}' .format(modeparsers.STARTCHARS)) return self.set_cmd_text(text) if run: self.command_accept() ed.file_updated.connect(callback) ed.edit(self.text()) @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode: usertypes.KeyMode) -> None: """Clear up when command mode was left. - Clear the statusbar text if it's explicitly unfocused. - Clear completion selection - Hide completion Args: mode: The mode which was left. """ if mode == usertypes.KeyMode.command: self.setText('') self.history.stop() self.hide_cmd.emit() self.clear_completion_selection.emit() self.hide_completion.emit() def setText(self, text: str) -> None: """Extend setText to set prefix and make sure the prompt is ok.""" if not text: pass elif text[0] in modeparsers.STARTCHARS: super().set_prompt(text[0]) else: raise utils.Unreachable("setText got called with invalid text " "'{}'!".format(text)) super().setText(text) def keyPressEvent(self, e: QKeyEvent) -> None: """Override keyPressEvent to ignore Return key presses. If this widget is focused, we are in passthrough key mode, and Enter/Shift+Enter/etc. will cause QLineEdit to think it's finished without command_accept to be called. """ text = self.text() if text in modeparsers.STARTCHARS and e.key() == Qt.Key_Backspace: e.accept() modeman.leave(self._win_id, usertypes.KeyMode.command, 'prefix deleted') return if e.key() == Qt.Key_Return: e.ignore() return else: super().keyPressEvent(e) def sizeHint(self) -> QSize: """Dynamically calculate the needed size.""" height = super().sizeHint().height() text = self.text() if not text: text = 'x' width = self.fontMetrics().width(text) return QSize(width, height) @pyqtSlot() def _incremental_search(self) -> None: if not config.val.search.incremental: return self._handle_search() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/keystring.py0000644000175000017510000000174500000000000026432 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Keychain string displayed in the statusbar.""" from qutebrowser.mainwindow.statusbar import textbase class KeyString(textbase.TextBase): """Keychain string displayed in the statusbar.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/percentage.py0000644000175000017510000000417700000000000026532 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Scroll percentage displayed in the statusbar.""" from PyQt5.QtCore import pyqtSlot, Qt from qutebrowser.mainwindow.statusbar import textbase from qutebrowser.misc import throttle class Percentage(textbase.TextBase): """Reading percentage displayed in the statusbar.""" def __init__(self, parent=None): """Constructor. Set percentage to 0%.""" super().__init__(parent, elidemode=Qt.ElideNone) self._strings = self._calc_strings() self._set_text = throttle.Throttle(self.setText, 100, parent=self) self.set_perc(0, 0) def set_raw(self): self._strings = self._calc_strings(raw=True) def _calc_strings(self, raw=False): """Pre-calculate strings for the statusbar.""" fmt = '[{:02}]' if raw else '[{:02}%]' strings = {i: fmt.format(i) for i in range(1, 100)} strings.update({0: '[top]', 100: '[bot]'}) return strings @pyqtSlot(int, int) def set_perc(self, x, y): # pylint: disable=unused-argument """Setter to be used as a Qt slot. Args: x: The x percentage (int), currently ignored. y: The y percentage (int) """ self._set_text(self._strings.get(y, '[???]')) def on_tab_changed(self, tab): """Update scroll position when tab changed.""" self.set_perc(*tab.scroller.pos_perc()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/progress.py0000644000175000017510000000556300000000000026261 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """The progress bar in the statusbar.""" from PyQt5.QtCore import pyqtSlot, QSize from PyQt5.QtWidgets import QProgressBar, QSizePolicy from qutebrowser.config import stylesheet from qutebrowser.utils import utils, usertypes class Progress(QProgressBar): """The progress bar part of the status bar.""" STYLESHEET = """ QProgressBar { border-radius: 0px; border: 2px solid transparent; background-color: transparent; font: {{ conf.fonts.statusbar }}; } QProgressBar::chunk { background-color: {{ conf.colors.statusbar.progress.bg }}; } """ def __init__(self, parent=None): super().__init__(parent) stylesheet.set_register(self) self.enabled = False self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) self.setTextVisible(False) self.hide() def __repr__(self): return utils.get_repr(self, value=self.value()) @pyqtSlot() def on_load_started(self): """Clear old error and show progress, used as slot to loadStarted.""" self.setValue(0) self.setVisible(self.enabled) @pyqtSlot(int) def on_load_progress(self, value): """Hide the statusbar when loading finished. We use this instead of loadFinished because we sometimes get loadStarted and loadProgress(100) without loadFinished from Qt. WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223 """ self.setValue(value) if value == 100: self.hide() def on_tab_changed(self, tab): """Set the correct value when the current tab changed.""" self.setValue(tab.progress()) if self.enabled and tab.load_status() == usertypes.LoadStatus.loading: self.show() else: self.hide() def sizeHint(self): """Set the height to the text height.""" width = super().sizeHint().width() height = self.fontMetrics().height() return QSize(width, height) def minimumSizeHint(self): return self.sizeHint() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/tabindex.py0000644000175000017510000000231200000000000026200 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2015-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """TabIndex displayed in the statusbar.""" from PyQt5.QtCore import pyqtSlot from qutebrowser.mainwindow.statusbar import textbase class TabIndex(textbase.TextBase): """Shows current tab index and number of tabs in the statusbar.""" @pyqtSlot(int, int) def on_tab_index_changed(self, current, count): """Update tab index when tab changed.""" self.setText('[{}/{}]'.format(current + 1, count)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/text.py0000644000175000017510000000515600000000000025377 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Text displayed in the statusbar.""" import enum from PyQt5.QtCore import pyqtSlot from qutebrowser.mainwindow.statusbar import textbase from qutebrowser.utils import log class Text(textbase.TextBase): """Text displayed in the statusbar. Attributes: _normaltext: The "permanent" text. Never automatically cleared. _temptext: The temporary text to display. The temptext is shown from StatusBar when a temporary text or error is available. If not, the permanent text is shown. """ Text = enum.Enum('Text', ['normal', 'temp']) def __init__(self, parent=None): super().__init__(parent) self._normaltext = '' self._temptext = '' def set_text(self, which, text): """Set a text. Args: which: Which text to set, a self.Text instance. text: The text to set. """ log.statusbar.debug("Setting {} text to '{}'.".format( which.name, text)) if which is self.Text.normal: self._normaltext = text elif which is self.Text.temp: self._temptext = text else: raise ValueError("Invalid value {} for which!".format(which)) self.update_text() @pyqtSlot(str) def maybe_reset_text(self, text): """Clear a normal text if it still matches an expected text.""" if self._normaltext == text: log.statusbar.debug("Resetting: '{}'".format(text)) self.set_text(self.Text.normal, '') else: log.statusbar.debug("Ignoring reset: '{}'".format(text)) def update_text(self): """Update QLabel text when needed.""" if self._temptext: self.setText(self._temptext) elif self._normaltext: self.setText(self._normaltext) else: self.setText('') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/textbase.py0000644000175000017510000000571700000000000026235 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Base text widgets for statusbar.""" from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QLabel, QSizePolicy from PyQt5.QtGui import QPainter from qutebrowser.utils import qtutils, utils class TextBase(QLabel): """A text in the statusbar. Unlike QLabel, the text will get elided. Eliding is loosely based on http://gedgedev.blogspot.ch/2010/12/elided-labels-in-qt.html Attributes: _elidemode: Where to elide the text. _elided_text: The current elided text. """ def __init__(self, parent=None, elidemode=Qt.ElideRight): super().__init__(parent) self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum) self._elidemode = elidemode self._elided_text = '' def __repr__(self): return utils.get_repr(self, text=self.text()) def _update_elided_text(self, width): """Update the elided text when necessary. Args: width: The maximal width the text should take. """ if self.text(): self._elided_text = self.fontMetrics().elidedText( self.text(), self._elidemode, width, Qt.TextShowMnemonic) else: self._elided_text = '' def setText(self, txt): """Extend QLabel::setText to update the elided text afterwards. Args: txt: The text to set (string). """ super().setText(txt) if self._elidemode != Qt.ElideNone: self._update_elided_text(self.geometry().width()) def resizeEvent(self, e): """Extend QLabel::resizeEvent to update the elided text afterwards.""" super().resizeEvent(e) size = e.size() qtutils.ensure_valid(size) self._update_elided_text(size.width()) def paintEvent(self, e): """Override QLabel::paintEvent to draw elided text.""" if self._elidemode == Qt.ElideNone: super().paintEvent(e) else: e.accept() painter = QPainter(self) geom = self.geometry() qtutils.ensure_valid(geom) painter.drawText(0, 0, geom.width(), geom.height(), int(self.alignment()), self._elided_text) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/statusbar/url.py0000644000175000017510000001335000000000000025210 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """URL displayed in the statusbar.""" import enum from PyQt5.QtCore import pyqtSlot, pyqtProperty, QUrl from qutebrowser.mainwindow.statusbar import textbase from qutebrowser.config import stylesheet from qutebrowser.utils import usertypes, urlutils # Note this has entries for success/error/warn from widgets.webview:LoadStatus UrlType = enum.Enum('UrlType', ['success', 'success_https', 'error', 'warn', 'hover', 'normal']) class UrlText(textbase.TextBase): """URL displayed in the statusbar. Attributes: _normal_url: The normal URL to be displayed as a UrlType instance. _normal_url_type: The type of the normal URL as a UrlType instance. _hover_url: The URL we're currently hovering over. _ssl_errors: Whether SSL errors occurred while loading. _urltype: The URL type to show currently (normal/ok/error/warn/hover). Accessed via the urltype property. """ STYLESHEET = """ QLabel#UrlText[urltype="normal"] { color: {{ conf.colors.statusbar.url.fg }}; } QLabel#UrlText[urltype="success"] { color: {{ conf.colors.statusbar.url.success.http.fg }}; } QLabel#UrlText[urltype="success_https"] { color: {{ conf.colors.statusbar.url.success.https.fg }}; } QLabel#UrlText[urltype="error"] { color: {{ conf.colors.statusbar.url.error.fg }}; } QLabel#UrlText[urltype="warn"] { color: {{ conf.colors.statusbar.url.warn.fg }}; } QLabel#UrlText[urltype="hover"] { color: {{ conf.colors.statusbar.url.hover.fg }}; } """ def __init__(self, parent=None): super().__init__(parent) self._urltype = None self.setObjectName(self.__class__.__name__) stylesheet.set_register(self) self._hover_url = None self._normal_url = None self._normal_url_type = UrlType.normal @pyqtProperty(str) def urltype(self): """Getter for self.urltype, so it can be used as Qt property. Return: The urltype as a string (!) """ if self._urltype is None: return "" else: return self._urltype.name def _update_url(self): """Update the displayed URL if the url or the hover url changed.""" old_urltype = self._urltype if self._hover_url is not None: self.setText(self._hover_url) self._urltype = UrlType.hover elif self._normal_url is not None: self.setText(self._normal_url) self._urltype = self._normal_url_type else: self.setText('') self._urltype = UrlType.normal if old_urltype != self._urltype: # We can avoid doing an unpolish here because the new style will # always override the old one. self.style().polish(self) @pyqtSlot(usertypes.LoadStatus) def on_load_status_changed(self, status): """Slot for load_status_changed. Sets URL color accordingly. Args: status: The usertypes.LoadStatus. """ assert isinstance(status, usertypes.LoadStatus), status if status in [usertypes.LoadStatus.success, usertypes.LoadStatus.success_https, usertypes.LoadStatus.error, usertypes.LoadStatus.warn]: self._normal_url_type = UrlType[status.name] else: self._normal_url_type = UrlType.normal self._update_url() @pyqtSlot(QUrl) def set_url(self, url): """Setter to be used as a Qt slot. Args: url: The URL to set as QUrl, or None. """ if url is None: self._normal_url = None elif not url.isValid(): self._normal_url = "Invalid URL!" else: self._normal_url = urlutils.safe_display_string(url) self._normal_url_type = UrlType.normal self._update_url() @pyqtSlot(str) def set_hover_url(self, link): """Setter to be used as a Qt slot. Saves old shown URL in self._old_url and restores it later if a link is "un-hovered" when it gets called with empty parameters. Args: link: The link which was hovered (string) """ if link: qurl = QUrl(link) if qurl.isValid(): self._hover_url = urlutils.safe_display_string(qurl) else: self._hover_url = '(invalid URL!) {}'.format(link) else: self._hover_url = None self._update_url() def on_tab_changed(self, tab): """Update URL if the tab changed.""" self._hover_url = None if tab.url().isValid(): self._normal_url = urlutils.safe_display_string(tab.url()) else: self._normal_url = '' self.on_load_status_changed(tab.load_status()) self._update_url() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/tabbedbrowser.py0000644000175000017510000011770400000000000025233 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """The main tabbed browser widget.""" import collections import functools import weakref import typing import attr from PyQt5.QtWidgets import QSizePolicy, QWidget, QApplication from PyQt5.QtCore import pyqtSignal, pyqtSlot, QTimer, QUrl from PyQt5.QtGui import QIcon from qutebrowser.config import config from qutebrowser.keyinput import modeman from qutebrowser.mainwindow import tabwidget, mainwindow from qutebrowser.browser import signalfilter, browsertab, history from qutebrowser.utils import (log, usertypes, utils, qtutils, objreg, urlutils, message, jinja) from qutebrowser.misc import quitter @attr.s class UndoEntry: """Information needed for :undo.""" url = attr.ib() history = attr.ib() index = attr.ib() pinned = attr.ib() class TabDeque: """Class which manages the 'last visited' tab stack. Instead of handling deletions by clearing old entries, they are handled by checking if they exist on access. This allows us to save an iteration on every tab delete. Currently, we assume we will switch to the tab returned by any of the getter functions. This is done because the on_switch functions will be called upon switch, and we don't want to duplicate entries in the stack for a single switch. """ def __init__(self) -> None: self._stack = collections.deque( maxlen=config.val.tabs.focus_stack_size ) # type: typing.Deque[weakref.ReferenceType[QWidget]] # Items that have been removed from the primary stack. self._stack_deleted = [ ] # type: typing.List[weakref.ReferenceType[QWidget]] self._ignore_next = False self._keep_deleted_next = False def on_switch(self, old_tab: QWidget) -> None: """Record tab switch events.""" if self._ignore_next: self._ignore_next = False self._keep_deleted_next = False return tab = weakref.ref(old_tab) if self._stack_deleted and not self._keep_deleted_next: self._stack_deleted = [] self._keep_deleted_next = False self._stack.append(tab) def prev(self, cur_tab: QWidget) -> QWidget: """Get the 'previous' tab in the stack. Throws IndexError on failure. """ tab = None # type: typing.Optional[QWidget] while tab is None or tab.pending_removal or tab is cur_tab: tab = self._stack.pop()() self._stack_deleted.append(weakref.ref(cur_tab)) self._ignore_next = True return tab def next(self, cur_tab: QWidget, *, keep_overflow=True) -> QWidget: """Get the 'next' tab in the stack. Throws IndexError on failure. """ tab = None # type: typing.Optional[QWidget] while tab is None or tab.pending_removal or tab is cur_tab: tab = self._stack_deleted.pop()() # On next tab-switch, current tab will be added to stack as normal. # However, we shouldn't wipe the overflow stack as normal. if keep_overflow: self._keep_deleted_next = True return tab def last(self, cur_tab: QWidget) -> QWidget: """Get the last tab. Throws IndexError on failure. """ try: return self.next(cur_tab, keep_overflow=False) except IndexError: return self.prev(cur_tab) def update_size(self) -> None: """Update the maxsize of this TabDeque.""" newsize = config.val.tabs.focus_stack_size if newsize < 0: newsize = None # We can't resize a collections.deque so just recreate it >:( self._stack = collections.deque(self._stack, maxlen=newsize) class TabDeletedError(Exception): """Exception raised when _tab_index is called for a deleted tab.""" class TabbedBrowser(QWidget): """A TabWidget with QWebViews inside. Provides methods to manage tabs, convenience methods to interact with the current tab (cur_*) and filters signals to re-emit them when they occurred in the currently visible tab. For all tab-specific signals (cur_*) emitted by a tab, this happens: - the signal gets filtered with _filter_signals and self.cur_* gets emitted if the signal occurred in the current tab. Attributes: search_text/search_options: Search parameters which are shared between all tabs. _win_id: The window ID this tabbedbrowser is associated with. _filter: A SignalFilter instance. _now_focused: The tab which is focused now. _tab_insert_idx_left: Where to insert a new tab with tabs.new_tab_position set to 'prev'. _tab_insert_idx_right: Same as above, for 'next'. _undo_stack: List of lists of UndoEntry objects of closed tabs. shutting_down: Whether we're currently shutting down. _local_marks: Jump markers local to each page _global_marks: Jump markers used across all pages default_window_icon: The qutebrowser window icon is_private: Whether private browsing is on for this window. Signals: cur_progress: Progress of the current tab changed (load_progress). cur_load_started: Current tab started loading (load_started) cur_load_finished: Current tab finished loading (load_finished) cur_url_changed: Current URL changed. cur_link_hovered: Link hovered in current tab (link_hovered) cur_scroll_perc_changed: Scroll percentage of current tab changed. arg 1: x-position in %. arg 2: y-position in %. cur_load_status_changed: Loading status of current tab changed. close_window: The last tab was closed, close this window. resized: Emitted when the browser window has resized, so the completion widget can adjust its size to it. arg: The new size. current_tab_changed: The current tab changed to the emitted tab. new_tab: Emits the new WebView and its index when a new tab is opened. """ cur_progress = pyqtSignal(int) cur_load_started = pyqtSignal() cur_load_finished = pyqtSignal(bool) cur_url_changed = pyqtSignal(QUrl) cur_link_hovered = pyqtSignal(str) cur_scroll_perc_changed = pyqtSignal(int, int) cur_load_status_changed = pyqtSignal(usertypes.LoadStatus) cur_fullscreen_requested = pyqtSignal(bool) cur_caret_selection_toggled = pyqtSignal(bool) close_window = pyqtSignal() resized = pyqtSignal('QRect') current_tab_changed = pyqtSignal(browsertab.AbstractTab) new_tab = pyqtSignal(browsertab.AbstractTab, int) def __init__(self, *, win_id, private, parent=None): if private: assert not qtutils.is_single_process() super().__init__(parent) self.widget = tabwidget.TabWidget(win_id, parent=self) self._win_id = win_id self._tab_insert_idx_left = 0 self._tab_insert_idx_right = -1 self.shutting_down = False self.widget.tabCloseRequested.connect( # type: ignore self.on_tab_close_requested) self.widget.new_tab_requested.connect(self.tabopen) self.widget.currentChanged.connect( # type: ignore self._on_current_changed) self.cur_fullscreen_requested.connect(self.widget.tabBar().maybe_hide) self.widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-65223 if qtutils.version_check('5.10', compiled=False): self.cur_load_finished.connect(self._leave_modes_on_load) else: self.cur_load_started.connect(self._leave_modes_on_load) # This init is never used, it is immediately thrown away in the next # line. self._undo_stack = ( collections.deque() ) # type: typing.MutableSequence[typing.MutableSequence[UndoEntry]] self._update_stack_size() self._filter = signalfilter.SignalFilter(win_id, self) self._now_focused = None self.search_text = None self.search_options = {} # type: typing.Mapping[str, typing.Any] self._local_marks = { } # type: typing.MutableMapping[QUrl, typing.MutableMapping[str, int]] self._global_marks = { } # type: typing.MutableMapping[str, typing.Tuple[int, QUrl]] self.default_window_icon = self.widget.window().windowIcon() self.is_private = private self.tab_deque = TabDeque() config.instance.changed.connect(self._on_config_changed) quitter.instance.shutting_down.connect(self.shutdown) def _update_stack_size(self): newsize = config.instance.get('tabs.undo_stack_size') if newsize < 0: newsize = None # We can't resize a collections.deque so just recreate it >:( self._undo_stack = collections.deque(self._undo_stack, maxlen=newsize) def __repr__(self): return utils.get_repr(self, count=self.widget.count()) @pyqtSlot(str) def _on_config_changed(self, option): if option == 'tabs.favicons.show': self._update_favicons() elif option == 'window.title_format': self._update_window_title() elif option == 'tabs.undo_stack_size': self._update_stack_size() elif option in ['tabs.title.format', 'tabs.title.format_pinned']: self.widget.update_tab_titles() elif option == "tabs.focus_stack_size": self.tab_deque.update_size() def _tab_index(self, tab): """Get the index of a given tab. Raises TabDeletedError if the tab doesn't exist anymore. """ try: idx = self.widget.indexOf(tab) except RuntimeError as e: log.webview.debug("Got invalid tab ({})!".format(e)) raise TabDeletedError(e) if idx == -1: log.webview.debug("Got invalid tab (index is -1)!") raise TabDeletedError("index is -1!") return idx def widgets(self): """Get a list of open tab widgets. We don't implement this as generator so we can delete tabs while iterating over the list. """ widgets = [] for i in range(self.widget.count()): widget = self.widget.widget(i) if widget is None: log.webview.debug( # type: ignore "Got None-widget in tabbedbrowser!") else: widgets.append(widget) return widgets def _update_window_title(self, field=None): """Change the window title to match the current tab. Args: idx: The tab index to update. field: A field name which was updated. If given, the title is only set if the given field is in the template. """ title_format = config.cache['window.title_format'] if field is not None and ('{' + field + '}') not in title_format: return idx = self.widget.currentIndex() if idx == -1: # (e.g. last tab removed) log.webview.debug("Not updating window title because index is -1") return fields = self.widget.get_tab_fields(idx) fields['id'] = self._win_id title = title_format.format(**fields) self.widget.window().setWindowTitle(title) def _connect_tab_signals(self, tab): """Set up the needed signals for tab.""" # filtered signals tab.link_hovered.connect( self._filter.create(self.cur_link_hovered, tab)) tab.load_progress.connect( self._filter.create(self.cur_progress, tab)) tab.load_finished.connect( self._filter.create(self.cur_load_finished, tab)) tab.load_started.connect( self._filter.create(self.cur_load_started, tab)) tab.scroller.perc_changed.connect( self._filter.create(self.cur_scroll_perc_changed, tab)) tab.url_changed.connect( self._filter.create(self.cur_url_changed, tab)) tab.load_status_changed.connect( self._filter.create(self.cur_load_status_changed, tab)) tab.fullscreen_requested.connect( self._filter.create(self.cur_fullscreen_requested, tab)) tab.caret.selection_toggled.connect( self._filter.create(self.cur_caret_selection_toggled, tab)) # misc tab.scroller.perc_changed.connect(self._on_scroll_pos_changed) tab.scroller.before_jump_requested.connect(lambda: self.set_mark("'")) tab.url_changed.connect( functools.partial(self._on_url_changed, tab)) tab.title_changed.connect( functools.partial(self._on_title_changed, tab)) tab.icon_changed.connect( functools.partial(self._on_icon_changed, tab)) tab.load_progress.connect( functools.partial(self._on_load_progress, tab)) tab.load_finished.connect( functools.partial(self._on_load_finished, tab)) tab.load_started.connect( functools.partial(self._on_load_started, tab)) tab.load_status_changed.connect( functools.partial(self._on_load_status_changed, tab)) tab.window_close_requested.connect( functools.partial(self._on_window_close_requested, tab)) tab.renderer_process_terminated.connect( functools.partial(self._on_renderer_process_terminated, tab)) tab.audio.muted_changed.connect( functools.partial(self._on_audio_changed, tab)) tab.audio.recently_audible_changed.connect( functools.partial(self._on_audio_changed, tab)) tab.new_tab_requested.connect(self.tabopen) if not self.is_private: tab.history_item_triggered.connect( history.web_history.add_from_tab) def current_url(self): """Get the URL of the current tab. Intended to be used from command handlers. Return: The current URL as QUrl. """ idx = self.widget.currentIndex() return self.widget.tab_url(idx) def shutdown(self): """Try to shut down all tabs cleanly.""" self.shutting_down = True # Reverse tabs so we don't have to recacluate tab titles over and over # Removing first causes [2..-1] to be recomputed # Removing the last causes nothing to be recomputed for tab in reversed(self.widgets()): self._remove_tab(tab) def tab_close_prompt_if_pinned( self, tab, force, yes_action, text="Are you sure you want to close a pinned tab?"): """Helper method for tab_close. If tab is pinned, prompt. If not, run yes_action. If tab is destroyed, abort question. """ if tab.data.pinned and not force: message.confirm_async( title='Pinned Tab', text=text, yes_action=yes_action, default=False, abort_on=[tab.destroyed]) else: yes_action() def close_tab(self, tab, *, add_undo=True, new_undo=True): """Close a tab. Args: tab: The QWebView to be closed. add_undo: Whether the tab close can be undone. new_undo: Whether the undo entry should be a new item in the stack. """ last_close = config.val.tabs.last_close count = self.widget.count() if last_close == 'ignore' and count == 1: return self._remove_tab(tab, add_undo=add_undo, new_undo=new_undo) if count == 1: # We just closed the last tab above. if last_close == 'close': self.close_window.emit() elif last_close == 'blank': self.load_url(QUrl('about:blank'), newtab=True) elif last_close == 'startpage': for url in config.val.url.start_pages: self.load_url(url, newtab=True) elif last_close == 'default-page': self.load_url(config.val.url.default_page, newtab=True) def _remove_tab(self, tab, *, add_undo=True, new_undo=True, crashed=False): """Remove a tab from the tab list and delete it properly. Args: tab: The QWebView to be closed. add_undo: Whether the tab close can be undone. new_undo: Whether the undo entry should be a new item in the stack. crashed: Whether we're closing a tab with crashed renderer process. """ idx = self.widget.indexOf(tab) if idx == -1: if crashed: return raise TabDeletedError("tab {} is not contained in " "TabbedWidget!".format(tab)) if tab is self._now_focused: self._now_focused = None tab.pending_removal = True if tab.url().isEmpty(): # There are some good reasons why a URL could be empty # (target="_blank" with a download, see [1]), so we silently ignore # this. # [1] https://github.com/qutebrowser/qutebrowser/issues/163 pass elif not tab.url().isValid(): # We display a warning for URLs which are not empty but invalid - # but we don't return here because we want the tab to close either # way. urlutils.invalid_url_error(tab.url(), "saving tab") elif add_undo: try: history_data = tab.history.private_api.serialize() except browsertab.WebTabError: pass # special URL else: entry = UndoEntry(tab.url(), history_data, idx, tab.data.pinned) if new_undo or not self._undo_stack: self._undo_stack.append([entry]) else: self._undo_stack[-1].append(entry) tab.private_api.shutdown() self.widget.removeTab(idx) if not crashed: # WORKAROUND for a segfault when we delete the crashed tab. # see https://bugreports.qt.io/browse/QTBUG-58698 tab.layout().unwrap() tab.deleteLater() def undo(self): """Undo removing of a tab or tabs.""" # Remove unused tab which may be created after the last tab is closed last_close = config.val.tabs.last_close use_current_tab = False if last_close in ['blank', 'startpage', 'default-page']: only_one_tab_open = self.widget.count() == 1 no_history = len(self.widget.widget(0).history) == 1 urls = { 'blank': QUrl('about:blank'), 'startpage': config.val.url.start_pages[0], 'default-page': config.val.url.default_page, } first_tab_url = self.widget.widget(0).url() last_close_urlstr = urls[last_close].toString().rstrip('/') first_tab_urlstr = first_tab_url.toString().rstrip('/') last_close_url_used = first_tab_urlstr == last_close_urlstr use_current_tab = (only_one_tab_open and no_history and last_close_url_used) for entry in reversed(self._undo_stack.pop()): if use_current_tab: newtab = self.widget.widget(0) use_current_tab = False else: # FIXME:typing mypy thinks this is None due to @pyqtSlot newtab = typing.cast( browsertab.AbstractTab, self.tabopen(background=False, idx=entry.index)) newtab.history.private_api.deserialize(entry.history) self.widget.set_tab_pinned(newtab, entry.pinned) @pyqtSlot('QUrl', bool) def load_url(self, url, newtab): """Open a URL, used as a slot. Args: url: The URL to open as QUrl. newtab: True to open URL in a new tab, False otherwise. """ qtutils.ensure_valid(url) if newtab or self.widget.currentWidget() is None: self.tabopen(url, background=False) else: self.widget.currentWidget().load_url(url) @pyqtSlot(int) def on_tab_close_requested(self, idx): """Close a tab via an index.""" tab = self.widget.widget(idx) if tab is None: log.webview.debug( # type: ignore "Got invalid tab {} for index {}!".format(tab, idx)) return self.tab_close_prompt_if_pinned( tab, False, lambda: self.close_tab(tab)) @pyqtSlot(browsertab.AbstractTab) def _on_window_close_requested(self, widget): """Close a tab with a widget given.""" try: self.close_tab(widget) except TabDeletedError: log.webview.debug("Requested to close {!r} which does not " "exist!".format(widget)) @pyqtSlot('QUrl') @pyqtSlot('QUrl', bool) @pyqtSlot('QUrl', bool, bool) def tabopen( self, url: QUrl = None, background: bool = None, related: bool = True, idx: int = None, ) -> browsertab.AbstractTab: """Open a new tab with a given URL. Inner logic for open-tab and open-tab-bg. Also connect all the signals we need to _filter_signals. Args: url: The URL to open as QUrl or None for an empty tab. background: Whether to open the tab in the background. if None, the `tabs.background` setting decides. related: Whether the tab was opened from another existing tab. If this is set, the new position might be different. With the default settings we handle it like Chromium does: - Tabs from clicked links etc. are to the right of the current (related=True). - Explicitly opened tabs are at the very right (related=False) idx: The index where the new tab should be opened. Return: The opened WebView instance. """ if url is not None: qtutils.ensure_valid(url) log.webview.debug("Creating new tab with URL {}, background {}, " "related {}, idx {}".format( url, background, related, idx)) prev_focus = QApplication.focusWidget() if config.val.tabs.tabs_are_windows and self.widget.count() > 0: window = mainwindow.MainWindow(private=self.is_private) window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=window.win_id) return tabbed_browser.tabopen(url=url, background=background, related=related) tab = browsertab.create(win_id=self._win_id, private=self.is_private, parent=self.widget) self._connect_tab_signals(tab) if idx is None: idx = self._get_new_tab_idx(related) self.widget.insertTab(idx, tab, "") if url is not None: tab.load_url(url) if background is None: background = config.val.tabs.background if background: # Make sure the background tab has the correct initial size. # With a foreground tab, it's going to be resized correctly by the # layout anyways. tab.resize(self.widget.currentWidget().size()) self.widget.tab_index_changed.emit(self.widget.currentIndex(), self.widget.count()) # Refocus webview in case we lost it by spawning a bg tab self.widget.currentWidget().setFocus() else: self.widget.setCurrentWidget(tab) # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68076 # Still seems to be needed with Qt 5.11.1 tab.setFocus() mode = modeman.instance(self._win_id).mode if mode in [usertypes.KeyMode.command, usertypes.KeyMode.prompt, usertypes.KeyMode.yesno]: # If we were in a command prompt, restore old focus # The above commands need to be run to switch tabs if prev_focus is not None: prev_focus.setFocus() tab.show() self.new_tab.emit(tab, idx) return tab def _get_new_tab_idx(self, related): """Get the index of a tab to insert. Args: related: Whether the tab was opened from another tab (as a "child") Return: The index of the new tab. """ if related: pos = config.val.tabs.new_position.related else: pos = config.val.tabs.new_position.unrelated if pos == 'prev': if config.val.tabs.new_position.stacking: idx = self._tab_insert_idx_left # On first sight, we'd think we have to decrement # self._tab_insert_idx_left here, as we want the next tab to be # *before* the one we just opened. However, since we opened a # tab *before* the currently focused tab, indices will shift by # 1 automatically. else: idx = self.widget.currentIndex() elif pos == 'next': if config.val.tabs.new_position.stacking: idx = self._tab_insert_idx_right else: idx = self.widget.currentIndex() + 1 self._tab_insert_idx_right += 1 elif pos == 'first': idx = 0 elif pos == 'last': idx = -1 else: raise ValueError("Invalid tabs.new_position '{}'.".format(pos)) log.webview.debug("tabs.new_position {} -> opening new tab at {}, " "next left: {} / right: {}".format( pos, idx, self._tab_insert_idx_left, self._tab_insert_idx_right)) return idx def _update_favicons(self): """Update favicons when config was changed.""" for tab in self.widgets(): self.widget.update_tab_favicon(tab) @pyqtSlot() def _on_load_started(self, tab): """Clear icon and update title when a tab started loading. Args: tab: The tab where the signal belongs to. """ if tab.data.keep_icon: tab.data.keep_icon = False else: if (config.cache['tabs.tabs_are_windows'] and tab.data.should_show_icon()): self.widget.window().setWindowIcon(self.default_window_icon) @pyqtSlot() def _on_load_status_changed(self, tab): """Update tab/window titles if the load status changed.""" try: idx = self._tab_index(tab) except TabDeletedError: # We can get signals for tabs we already deleted... return self.widget.update_tab_title(idx) if idx == self.widget.currentIndex(): self._update_window_title() @pyqtSlot() def _leave_modes_on_load(self): """Leave insert/hint mode when loading started.""" try: url = self.current_url() if not url.isValid(): url = None except qtutils.QtValueError: url = None if config.instance.get('input.insert_mode.leave_on_load', url=url): modeman.leave(self._win_id, usertypes.KeyMode.insert, 'load started', maybe=True) else: log.modes.debug("Ignoring leave_on_load request due to setting.") if config.cache['hints.leave_on_load']: modeman.leave(self._win_id, usertypes.KeyMode.hint, 'load started', maybe=True) else: log.modes.debug("Ignoring leave_on_load request due to setting.") @pyqtSlot(browsertab.AbstractTab, str) def _on_title_changed(self, tab, text): """Set the title of a tab. Slot for the title_changed signal of any tab. Args: tab: The WebView where the title was changed. text: The text to set. """ if not text: log.webview.debug("Ignoring title change to '{}'.".format(text)) return try: idx = self._tab_index(tab) except TabDeletedError: # We can get signals for tabs we already deleted... return log.webview.debug("Changing title for idx {} to '{}'".format( idx, text)) self.widget.set_page_title(idx, text) if idx == self.widget.currentIndex(): self._update_window_title() @pyqtSlot(browsertab.AbstractTab, QUrl) def _on_url_changed(self, tab, url): """Set the new URL as title if there's no title yet. Args: tab: The WebView where the title was changed. url: The new URL. """ try: idx = self._tab_index(tab) except TabDeletedError: # We can get signals for tabs we already deleted... return if not self.widget.page_title(idx): self.widget.set_page_title(idx, url.toDisplayString()) @pyqtSlot(browsertab.AbstractTab, QIcon) def _on_icon_changed(self, tab, icon): """Set the icon of a tab. Slot for the iconChanged signal of any tab. Args: tab: The WebView where the title was changed. icon: The new icon """ if not tab.data.should_show_icon(): return try: idx = self._tab_index(tab) except TabDeletedError: # We can get signals for tabs we already deleted... return self.widget.setTabIcon(idx, icon) if config.val.tabs.tabs_are_windows: self.widget.window().setWindowIcon(icon) @pyqtSlot(usertypes.KeyMode) def on_mode_entered(self, mode): """Save input mode when tabs.mode_on_change = restore.""" if (config.val.tabs.mode_on_change == 'restore' and mode in modeman.INPUT_MODES): tab = self.widget.currentWidget() if tab is not None: tab.data.input_mode = mode @pyqtSlot(usertypes.KeyMode) def on_mode_left(self, mode): """Give focus to current tab if command mode was left.""" widget = self.widget.currentWidget() if widget is None: return # type: ignore if mode in [usertypes.KeyMode.command] + modeman.PROMPT_MODES: log.modes.debug("Left status-input mode, focusing {!r}".format( widget)) widget.setFocus() if config.val.tabs.mode_on_change == 'restore': widget.data.input_mode = usertypes.KeyMode.normal @pyqtSlot(int) def _on_current_changed(self, idx): """Add prev tab to stack and leave hinting mode when focus changed.""" mode_on_change = config.val.tabs.mode_on_change if idx == -1 or self.shutting_down: # closing the last tab (before quitting) or shutting down return tab = self.widget.widget(idx) if tab is None: log.webview.debug( # type: ignore "on_current_changed got called with invalid index {}" .format(idx)) return log.modes.debug("Current tab changed, focusing {!r}".format(tab)) tab.setFocus() modes_to_leave = [usertypes.KeyMode.hint, usertypes.KeyMode.caret] mm_instance = modeman.instance(self._win_id) current_mode = mm_instance.mode log.modes.debug("Mode before tab change: {} (mode_on_change = {})" .format(current_mode.name, mode_on_change)) if mode_on_change == 'normal': modes_to_leave += modeman.INPUT_MODES for mode in modes_to_leave: modeman.leave(self._win_id, mode, 'tab changed', maybe=True) if (mode_on_change == 'restore' and current_mode not in modeman.PROMPT_MODES): modeman.enter(self._win_id, tab.data.input_mode, 'restore') if self._now_focused is not None: self.tab_deque.on_switch(self._now_focused) log.modes.debug("Mode after tab change: {} (mode_on_change = {})" .format(current_mode.name, mode_on_change)) self._now_focused = tab self.current_tab_changed.emit(tab) QTimer.singleShot(0, self._update_window_title) self._tab_insert_idx_left = self.widget.currentIndex() self._tab_insert_idx_right = self.widget.currentIndex() + 1 @pyqtSlot() def on_cmd_return_pressed(self): """Set focus when the commandline closes.""" log.modes.debug("Commandline closed, focusing {!r}".format(self)) def _on_load_progress(self, tab, perc): """Adjust tab indicator on load progress.""" try: idx = self._tab_index(tab) except TabDeletedError: # We can get signals for tabs we already deleted... return start = config.cache['colors.tabs.indicator.start'] stop = config.cache['colors.tabs.indicator.stop'] system = config.cache['colors.tabs.indicator.system'] color = utils.interpolate_color(start, stop, perc, system) self.widget.set_tab_indicator_color(idx, color) self.widget.update_tab_title(idx) if idx == self.widget.currentIndex(): self._update_window_title() def _on_load_finished(self, tab, ok): """Adjust tab indicator when loading finished.""" try: idx = self._tab_index(tab) except TabDeletedError: # We can get signals for tabs we already deleted... return if ok: start = config.cache['colors.tabs.indicator.start'] stop = config.cache['colors.tabs.indicator.stop'] system = config.cache['colors.tabs.indicator.system'] color = utils.interpolate_color(start, stop, 100, system) else: color = config.cache['colors.tabs.indicator.error'] self.widget.set_tab_indicator_color(idx, color) if idx == self.widget.currentIndex(): tab.private_api.handle_auto_insert_mode(ok) @pyqtSlot() def _on_scroll_pos_changed(self): """Update tab and window title when scroll position changed.""" idx = self.widget.currentIndex() if idx == -1: # (e.g. last tab removed) log.webview.debug("Not updating scroll position because index is " "-1") return self._update_window_title('scroll_pos') self.widget.update_tab_title(idx, 'scroll_pos') def _on_audio_changed(self, tab, _muted): """Update audio field in tab when mute or recentlyAudible changed.""" try: idx = self._tab_index(tab) except TabDeletedError: # We can get signals for tabs we already deleted... return self.widget.update_tab_title(idx, 'audio') if idx == self.widget.currentIndex(): self._update_window_title('audio') def _on_renderer_process_terminated(self, tab, status, code): """Show an error when a renderer process terminated.""" if status == browsertab.TerminationStatus.normal: return messages = { browsertab.TerminationStatus.abnormal: "Renderer process exited with status {}".format(code), browsertab.TerminationStatus.crashed: "Renderer process crashed", browsertab.TerminationStatus.killed: "Renderer process was killed", browsertab.TerminationStatus.unknown: "Renderer process did not start", } msg = messages[status] def show_error_page(html): tab.set_html(html) log.webview.error(msg) if qtutils.version_check('5.9', compiled=False): url_string = tab.url(requested=True).toDisplayString() error_page = jinja.render( 'error.html', title="Error loading {}".format(url_string), url=url_string, error=msg) QTimer.singleShot(100, lambda: show_error_page(error_page)) else: # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-58698 message.error(msg) self._remove_tab(tab, crashed=True) if self.widget.count() == 0: self.tabopen(QUrl('about:blank')) def resizeEvent(self, e): """Extend resizeEvent of QWidget to emit a resized signal afterwards. Args: e: The QResizeEvent """ super().resizeEvent(e) self.resized.emit(self.geometry()) def wheelEvent(self, e): """Override wheelEvent of QWidget to forward it to the focused tab. Args: e: The QWheelEvent """ if self._now_focused is not None: self._now_focused.wheelEvent(e) else: e.ignore() def set_mark(self, key): """Set a mark at the current scroll position in the current tab. Args: key: mark identifier; capital indicates a global mark """ # strip the fragment as it may interfere with scrolling try: url = self.current_url().adjusted(QUrl.RemoveFragment) except qtutils.QtValueError: # show an error only if the mark is not automatically set if key != "'": message.error("Failed to set mark: url invalid") return point = self.widget.currentWidget().scroller.pos_px() if key.isupper(): self._global_marks[key] = point, url else: if url not in self._local_marks: self._local_marks[url] = {} self._local_marks[url][key] = point def jump_mark(self, key): """Jump to the mark named by `key`. Args: key: mark identifier; capital indicates a global mark """ try: # consider urls that differ only in fragment to be identical urlkey = self.current_url().adjusted(QUrl.RemoveFragment) except qtutils.QtValueError: urlkey = None tab = self.widget.currentWidget() if key.isupper(): if key in self._global_marks: point, url = self._global_marks[key] def callback(ok): """Scroll once loading finished.""" if ok: self.cur_load_finished.disconnect(callback) tab.scroller.to_point(point) self.load_url(url, newtab=False) self.cur_load_finished.connect(callback) else: message.error("Mark {} is not set".format(key)) elif urlkey is None: message.error("Current URL is invalid!") elif urlkey in self._local_marks and key in self._local_marks[urlkey]: point = self._local_marks[urlkey][key] # save the pre-jump position in the special ' mark # this has to happen after we read the mark, otherwise jump_mark # "'" would just jump to the current position every time tab.scroller.before_jump_requested.emit() tab.scroller.to_point(point) else: message.error("Mark {} is not set".format(key)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/mainwindow/tabwidget.py0000644000175000017510000010660600000000000024357 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """The tab widget used for TabbedBrowser from browser.py.""" import typing import functools import contextlib import attr from PyQt5.QtCore import (pyqtSignal, pyqtSlot, Qt, QSize, QRect, QPoint, QTimer, QUrl) from PyQt5.QtWidgets import (QTabWidget, QTabBar, QSizePolicy, QCommonStyle, QStyle, QStylePainter, QStyleOptionTab, QStyleFactory, QWidget) from PyQt5.QtGui import QIcon, QPalette, QColor from qutebrowser.utils import qtutils, objreg, utils, usertypes, log from qutebrowser.config import config, stylesheet from qutebrowser.misc import objects, debugcachestats from qutebrowser.browser import browsertab class TabWidget(QTabWidget): """The tab widget used for TabbedBrowser. Signals: tab_index_changed: Emitted when the current tab was changed. arg 0: The index of the tab which is now focused. arg 1: The total count of tabs. new_tab_requested: Emitted when a new tab is requested. """ tab_index_changed = pyqtSignal(int, int) new_tab_requested = pyqtSignal('QUrl', bool, bool) # Strings for controlling the mute/audible text MUTE_STRING = '[M] ' AUDIBLE_STRING = '[A] ' def __init__(self, win_id, parent=None): super().__init__(parent) bar = TabBar(win_id, self) self.setStyle(TabBarStyle()) self.setTabBar(bar) bar.tabCloseRequested.connect(self.tabCloseRequested) # type: ignore bar.tabMoved.connect(functools.partial( # type: ignore QTimer.singleShot, 0, self.update_tab_titles)) bar.currentChanged.connect(self._on_current_changed) # type: ignore bar.new_tab_requested.connect(self._on_new_tab_requested) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.setDocumentMode(True) self.setElideMode(Qt.ElideRight) self.setUsesScrollButtons(True) bar.setDrawBase(False) self._init_config() config.instance.changed.connect(self._init_config) @config.change_filter('tabs') def _init_config(self): """Initialize attributes based on the config.""" tabbar = self.tabBar() self.setMovable(True) self.setTabsClosable(False) position = config.val.tabs.position selection_behavior = config.val.tabs.select_on_remove self.setTabPosition(position) tabbar.vertical = position in [QTabWidget.West, QTabWidget.East] tabbar.setSelectionBehaviorOnRemove(selection_behavior) tabbar.refresh() def set_tab_indicator_color(self, idx, color): """Set the tab indicator color. Args: idx: The tab index. color: A QColor. """ bar = self.tabBar() bar.set_tab_data(idx, 'indicator-color', color) bar.update(bar.tabRect(idx)) def set_tab_pinned(self, tab: QWidget, pinned: bool) -> None: """Set the tab status as pinned. Args: tab: The tab to pin pinned: Pinned tab state to set. """ idx = self.indexOf(tab) tab.data.pinned = pinned self.update_tab_favicon(tab) self.update_tab_title(idx) def tab_indicator_color(self, idx): """Get the tab indicator color for the given index.""" return self.tabBar().tab_indicator_color(idx) def set_page_title(self, idx, title): """Set the tab title user data.""" tabbar = self.tabBar() if config.cache['tabs.tooltips']: # always show only plain title in tooltips tabbar.setTabToolTip(idx, title) tabbar.set_tab_data(idx, 'page-title', title) self.update_tab_title(idx) def page_title(self, idx): """Get the tab title user data.""" return self.tabBar().page_title(idx) def update_tab_title(self, idx, field=None): """Update the tab text for the given tab. Args: idx: The tab index to update. field: A field name which was updated. If given, the title is only set if the given field is in the template. """ tab = self.widget(idx) if tab.data.pinned: fmt = config.cache['tabs.title.format_pinned'] else: fmt = config.cache['tabs.title.format'] if (field is not None and (fmt is None or ('{' + field + '}') not in fmt)): return fields = self.get_tab_fields(idx) fields['current_title'] = fields['current_title'].replace('&', '&&') fields['index'] = idx + 1 title = '' if fmt is None else fmt.format(**fields) tabbar = self.tabBar() # Only change the tab title if it changes, setting the tab title causes # a size recalculation which is slow. if tabbar.tabText(idx) != title: tabbar.setTabText(idx, title) def get_tab_fields(self, idx): """Get the tab field data.""" tab = self.widget(idx) if tab is None: log.misc.debug( # type: ignore "Got None-tab in get_tab_fields!") page_title = self.page_title(idx) fields = {} fields['id'] = tab.tab_id fields['current_title'] = page_title fields['title_sep'] = ' - ' if page_title else '' fields['perc_raw'] = tab.progress() fields['backend'] = objects.backend.name fields['private'] = ' [Private Mode] ' if tab.is_private else '' try: if tab.audio.is_muted(): fields['audio'] = TabWidget.MUTE_STRING elif tab.audio.is_recently_audible(): fields['audio'] = TabWidget.AUDIBLE_STRING else: fields['audio'] = '' except browsertab.WebTabError: # Muting is only implemented with QtWebEngine fields['audio'] = '' if tab.load_status() == usertypes.LoadStatus.loading: fields['perc'] = '[{}%] '.format(tab.progress()) else: fields['perc'] = '' try: url = self.tab_url(idx) except qtutils.QtValueError: fields['host'] = '' fields['current_url'] = '' fields['protocol'] = '' else: fields['host'] = url.host() fields['current_url'] = url.toDisplayString() fields['protocol'] = url.scheme() y = tab.scroller.pos_perc()[1] if y is None: scroll_pos = '???' elif y <= 0: scroll_pos = 'top' elif y >= 100: scroll_pos = 'bot' else: scroll_pos = '{:2}%'.format(y) fields['scroll_pos'] = scroll_pos return fields @contextlib.contextmanager def _toggle_visibility(self): """Toggle visibility while running. Every single call to setTabText calls the size hinting functions for every single tab, which are slow. Since we know we are updating all the tab's titles, we can delay this processing by making the tab non-visible. To avoid flickering, disable repaint updates whlie we work. """ bar = self.tabBar() toggle = (self.count() > 10 and not bar.drag_in_progress and bar.isVisible()) if toggle: bar.setUpdatesEnabled(False) bar.setVisible(False) yield if toggle: bar.setVisible(True) bar.setUpdatesEnabled(True) def update_tab_titles(self): """Update all texts.""" with self._toggle_visibility(): for idx in range(self.count()): self.update_tab_title(idx) def tabInserted(self, idx): """Update titles when a tab was inserted.""" super().tabInserted(idx) self.update_tab_titles() def tabRemoved(self, idx): """Update titles when a tab was removed.""" super().tabRemoved(idx) self.update_tab_titles() def addTab(self, page, icon_or_text, text_or_empty=None): """Override addTab to use our own text setting logic. Unfortunately QTabWidget::addTab has these two overloads: - QWidget * page, const QIcon & icon, const QString & label - QWidget * page, const QString & label This means we'll get different arguments based on the chosen overload. Args: page: The QWidget to add. icon_or_text: Either the QIcon to add or the label. text_or_empty: Either the label or None. Return: The index of the newly added tab. """ if text_or_empty is None: text = icon_or_text new_idx = super().addTab(page, '') else: icon = icon_or_text text = text_or_empty new_idx = super().addTab(page, icon, '') self.set_page_title(new_idx, text) return new_idx def insertTab(self, idx, page, icon_or_text, text_or_empty=None): """Override insertTab to use our own text setting logic. Unfortunately QTabWidget::insertTab has these two overloads: - int index, QWidget * page, const QIcon & icon, const QString & label - int index, QWidget * page, const QString & label This means we'll get different arguments based on the chosen overload. Args: idx: Where to insert the widget. page: The QWidget to add. icon_or_text: Either the QIcon to add or the label. text_or_empty: Either the label or None. Return: The index of the newly added tab. """ if text_or_empty is None: text = icon_or_text new_idx = super().insertTab(idx, page, '') else: icon = icon_or_text text = text_or_empty new_idx = super().insertTab(idx, page, icon, '') self.set_page_title(new_idx, text) return new_idx @pyqtSlot(int) def _on_current_changed(self, index): """Emit the tab_index_changed signal if the current tab changed.""" self.tabBar().on_current_changed() self.tab_index_changed.emit(index, self.count()) @pyqtSlot() def _on_new_tab_requested(self): """Open a new tab.""" self.new_tab_requested.emit(config.val.url.default_page, False, False) def tab_url(self, idx): """Get the URL of the tab at the given index. Return: The tab URL as QUrl. """ tab = self.widget(idx) if tab is None: url = QUrl() # type: ignore else: url = tab.url() # It's possible for url to be invalid, but the caller will handle that. qtutils.ensure_valid(url) return url def update_tab_favicon(self, tab: QWidget) -> None: """Update favicon of the given tab.""" idx = self.indexOf(tab) if tab.data.should_show_icon(): self.setTabIcon(idx, tab.icon()) if config.val.tabs.tabs_are_windows: self.window().setWindowIcon(tab.icon()) else: self.setTabIcon(idx, QIcon()) if config.val.tabs.tabs_are_windows: self.window().setWindowIcon(self.window().windowIcon()) def setTabIcon(self, idx: int, icon: QIcon): """Always show tab icons for pinned tabs in some circumstances.""" tab = typing.cast(typing.Optional[browsertab.AbstractTab], self.widget(idx)) if (icon.isNull() and config.cache['tabs.favicons.show'] != 'never' and config.cache['tabs.pinned.shrink'] and not self.tabBar().vertical and tab is not None and tab.data.pinned): icon = self.style().standardIcon(QStyle.SP_FileIcon) super().setTabIcon(idx, icon) class TabBar(QTabBar): """Custom tab bar with our own style. FIXME: Dragging tabs doesn't look as nice as it does in QTabBar. However, fixing this would be a lot of effort, so we'll postpone it until we're reimplementing drag&drop for other reasons. https://github.com/qutebrowser/qutebrowser/issues/126 Attributes: vertical: When the tab bar is currently vertical. win_id: The window ID this TabBar belongs to. Signals: new_tab_requested: Emitted when a new tab is requested. """ STYLESHEET = """ TabBar { background-color: {{ conf.colors.tabs.bar.bg }}; } """ new_tab_requested = pyqtSignal() def __init__(self, win_id, parent=None): super().__init__(parent) self._win_id = win_id self.setStyle(TabBarStyle()) self._set_font() config.instance.changed.connect(self._on_config_changed) self.vertical = False self._auto_hide_timer = QTimer() self._auto_hide_timer.setSingleShot(True) self._auto_hide_timer.timeout.connect(self.maybe_hide) self._on_show_switching_delay_changed() self.setAutoFillBackground(True) self.drag_in_progress = False stylesheet.set_register(self) QTimer.singleShot(0, self.maybe_hide) def __repr__(self): return utils.get_repr(self, count=self.count()) def _current_tab(self): """Get the current tab object.""" return self.parent().currentWidget() @pyqtSlot(str) def _on_config_changed(self, option: str) -> None: if option == 'fonts.tabs': self._set_font() elif option == 'tabs.favicons.scale': self._set_icon_size() elif option == 'tabs.show_switching_delay': self._on_show_switching_delay_changed() elif option == 'tabs.show': self.maybe_hide() if option.startswith('colors.tabs.'): self.update() # Clear tab size caches when appropriate if option in ["tabs.indicator.padding", "tabs.padding", "tabs.indicator.width", "tabs.min_width", "tabs.pinned.shrink"]: self._minimum_tab_size_hint_helper.cache_clear() self._minimum_tab_height.cache_clear() def _on_show_switching_delay_changed(self): """Set timer interval when tabs.show_switching_delay got changed.""" self._auto_hide_timer.setInterval(config.val.tabs.show_switching_delay) def on_current_changed(self): """Show tab bar when current tab got changed.""" self.maybe_hide() # for fullscreen tabs if config.val.tabs.show == 'switching': self.show() self._auto_hide_timer.start() @pyqtSlot() def maybe_hide(self): """Hide the tab bar if needed.""" show = config.val.tabs.show tab = self._current_tab() if (show in ['never', 'switching'] or (show == 'multiple' and self.count() == 1) or (tab and tab.data.fullscreen)): self.hide() else: self.show() def set_tab_data(self, idx, key, value): """Set tab data as a dictionary.""" if not 0 <= idx < self.count(): raise IndexError("Tab index ({}) out of range ({})!".format( idx, self.count())) data = self.tabData(idx) if data is None: data = {} data[key] = value self.setTabData(idx, data) def tab_data(self, idx, key): """Get tab data for a given key.""" if not 0 <= idx < self.count(): raise IndexError("Tab index ({}) out of range ({})!".format( idx, self.count())) data = self.tabData(idx) if data is None: data = {} return data[key] def tab_indicator_color(self, idx): """Get the tab indicator color for the given index.""" try: return self.tab_data(idx, 'indicator-color') except KeyError: return QColor() def page_title(self, idx): """Get the tab title user data. Args: idx: The tab index to get the title for. handle_unset: Whether to return an empty string on KeyError. """ try: return self.tab_data(idx, 'page-title') except KeyError: return '' def refresh(self): """Properly repaint the tab bar and relayout tabs.""" # This is a horrible hack, but we need to do this so the underlying Qt # code sets layoutDirty so it actually relayouts the tabs. self.setIconSize(self.iconSize()) def _set_font(self): """Set the tab bar font.""" self.setFont(config.val.fonts.tabs) self._set_icon_size() # clear tab size cache self._minimum_tab_size_hint_helper.cache_clear() self._minimum_tab_height.cache_clear() def _set_icon_size(self): """Set the tab bar favicon size.""" size = self.fontMetrics().height() - 2 size = int(size * config.val.tabs.favicons.scale) self.setIconSize(QSize(size, size)) def mouseReleaseEvent(self, e): """Override mouseReleaseEvent to know when drags stop.""" self.drag_in_progress = False super().mouseReleaseEvent(e) def mousePressEvent(self, e): """Override mousePressEvent to close tabs if configured. Also keep track of if we are currently in a drag.""" self.drag_in_progress = True button = config.val.tabs.close_mouse_button if (e.button() == Qt.RightButton and button == 'right' or e.button() == Qt.MiddleButton and button == 'middle'): e.accept() idx = self.tabAt(e.pos()) if idx == -1: action = config.val.tabs.close_mouse_button_on_bar if action == 'ignore': return elif action == 'new-tab': self.new_tab_requested.emit() return elif action == 'close-current': idx = self.currentIndex() elif action == 'close-last': idx = self.count() - 1 self.tabCloseRequested.emit(idx) # type: ignore return super().mousePressEvent(e) def minimumTabSizeHint(self, index: int, ellipsis: bool = True) -> QSize: """Set the minimum tab size to indicator/icon/... text. Args: index: The index of the tab to get a size hint for. ellipsis: Whether to use ellipsis to calculate width instead of the tab's text. Forced to False for pinned tabs. Return: A QSize of the smallest tab size we can make. """ icon = self.tabIcon(index) if icon.isNull(): icon_width = 0 else: icon_width = min( icon.actualSize(self.iconSize()).width(), self.iconSize().width()) + TabBarStyle.ICON_PADDING pinned = self._tab_pinned(index) if not self.vertical and pinned and config.val.tabs.pinned.shrink: # Never consider ellipsis an option for horizontal pinned tabs ellipsis = False return self._minimum_tab_size_hint_helper(self.tabText(index), icon_width, ellipsis, pinned) @debugcachestats.register(name='tab width cache') @functools.lru_cache(maxsize=2**9) def _minimum_tab_size_hint_helper(self, tab_text: str, icon_width: int, ellipsis: bool, pinned: bool) -> QSize: """Helper function to cache tab results. Config values accessed in here should be added to _on_config_changed to ensure cache is flushed when needed. """ text = '\u2026' if ellipsis else tab_text # Don't ever shorten if text is shorter than the ellipsis def _text_to_width(text): # Calculate text width taking into account qt mnemonics return self.fontMetrics().size(Qt.TextShowMnemonic, text).width() text_width = min(_text_to_width(text), _text_to_width(tab_text)) padding = config.cache['tabs.padding'] indicator_width = config.cache['tabs.indicator.width'] indicator_padding = config.cache['tabs.indicator.padding'] padding_h = padding.left + padding.right # Only add padding if indicator exists if indicator_width != 0: padding_h += indicator_padding.left + indicator_padding.right height = self._minimum_tab_height() width = (text_width + icon_width + padding_h + indicator_width) min_width = config.cache['tabs.min_width'] if (not self.vertical and min_width > 0 and not pinned or not config.cache['tabs.pinned.shrink']): width = max(min_width, width) return QSize(width, height) @functools.lru_cache(maxsize=1) def _minimum_tab_height(self): padding = config.cache['tabs.padding'] return self.fontMetrics().height() + padding.top + padding.bottom def _tab_pinned(self, index: int) -> bool: """Return True if tab is pinned.""" if not 0 <= index < self.count(): raise IndexError("Tab index ({}) out of range ({})!".format( index, self.count())) widget = self.parent().widget(index) if widget is None: # This could happen when Qt calls tabSizeHint while initializing # tabs. return False return widget.data.pinned def tabSizeHint(self, index: int) -> QSize: """Override tabSizeHint to customize qb's tab size. https://wiki.python.org/moin/PyQt/Customising%20tab%20bars Args: index: The index of the tab. Return: A QSize. """ if self.count() == 0: # This happens on startup on macOS. # We return it directly rather than setting `size' because we don't # want to ensure it's valid in this special case. return QSize() height = self._minimum_tab_height() if self.vertical: confwidth = str(config.cache['tabs.width']) if confwidth.endswith('%'): main_window = objreg.get('main-window', scope='window', window=self._win_id) perc = int(confwidth.rstrip('%')) width = main_window.width() * perc / 100 else: width = int(confwidth) size = QSize(width, height) else: if config.cache['tabs.pinned.shrink'] and self._tab_pinned(index): # Give pinned tabs the minimum size they need to display their # titles, let Qt handle scaling it down if we get too small. width = self.minimumTabSizeHint(index, ellipsis=False).width() else: # Request as much space as possible so we fill the tabbar, let # Qt shrink us down. If for some reason (tests, bugs) # self.width() gives 0, use a sane min of 10 px width = max(self.width(), 10) max_width = config.cache['tabs.max_width'] if max_width > 0: width = min(max_width, width) size = QSize(width, height) qtutils.ensure_valid(size) return size def paintEvent(self, event): """Override paintEvent to draw the tabs like we want to.""" p = QStylePainter(self) selected = self.currentIndex() for idx in range(self.count()): if not event.region().intersects(self.tabRect(idx)): # Don't repaint if we are outside the requested region continue tab = QStyleOptionTab() self.initStyleOption(tab, idx) setting = 'colors.tabs' if self._tab_pinned(idx): setting += '.pinned' if idx == selected: setting += '.selected' setting += '.odd' if (idx + 1) % 2 else '.even' tab.palette.setColor(QPalette.Window, config.cache[setting + '.bg']) tab.palette.setColor(QPalette.WindowText, config.cache[setting + '.fg']) indicator_color = self.tab_indicator_color(idx) tab.palette.setColor(QPalette.Base, indicator_color) p.drawControl(QStyle.CE_TabBarTab, tab) def tabInserted(self, idx): """Update visibility when a tab was inserted.""" super().tabInserted(idx) self.maybe_hide() def tabRemoved(self, idx): """Update visibility when a tab was removed.""" super().tabRemoved(idx) self.maybe_hide() def wheelEvent(self, e): """Override wheelEvent to make the action configurable. Args: e: The QWheelEvent """ if config.val.tabs.mousewheel_switching: super().wheelEvent(e) else: tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) tabbed_browser.wheelEvent(e) @attr.s class Layouts: """Layout information for tab. Used by TabBarStyle._tab_layout(). """ text = attr.ib() icon = attr.ib() indicator = attr.ib() class TabBarStyle(QCommonStyle): """Qt style used by TabBar to fix some issues with the default one. This fixes the following things: - Remove the focus rectangle Ubuntu draws on tabs. - Force text to be left-aligned even though Qt has "centered" hardcoded. Unfortunately PyQt doesn't support QProxyStyle, so we need to do this the hard way... Based on: http://stackoverflow.com/a/17294081 https://code.google.com/p/makehuman/source/browse/trunk/makehuman/lib/qtgui.py """ ICON_PADDING = 4 def __init__(self): """Initialize all functions we're not overriding. This simply calls the corresponding function in self._style. """ self._style = QStyleFactory.create('Fusion') for method in ['drawComplexControl', 'drawItemPixmap', 'generatedIconPixmap', 'hitTestComplexControl', 'itemPixmapRect', 'itemTextRect', 'polish', 'styleHint', 'subControlRect', 'unpolish', 'drawItemText', 'sizeFromContents', 'drawPrimitive']: target = getattr(self._style, method) setattr(self, method, functools.partial(target)) super().__init__() def _draw_indicator(self, layouts, opt, p): """Draw the tab indicator. Args: layouts: The layouts from _tab_layout. opt: QStyleOption from drawControl. p: QPainter from drawControl. """ color = opt.palette.base().color() rect = layouts.indicator if color.isValid() and rect.isValid(): p.fillRect(rect, color) def _draw_icon(self, layouts, opt, p): """Draw the tab icon. Args: layouts: The layouts from _tab_layout. opt: QStyleOption p: QPainter """ qtutils.ensure_valid(layouts.icon) icon_mode = (QIcon.Normal if opt.state & QStyle.State_Enabled else QIcon.Disabled) icon_state = (QIcon.On if opt.state & QStyle.State_Selected else QIcon.Off) icon = opt.icon.pixmap(opt.iconSize, icon_mode, icon_state) self._style.drawItemPixmap(p, layouts.icon, Qt.AlignCenter, icon) def drawControl(self, element, opt, p, widget=None): """Override drawControl to draw odd tabs in a different color. Draws the given element with the provided painter with the style options specified by option. Args: element: ControlElement opt: QStyleOption p: QPainter widget: QWidget """ if element not in [QStyle.CE_TabBarTab, QStyle.CE_TabBarTabShape, QStyle.CE_TabBarTabLabel]: # Let the real style draw it. self._style.drawControl(element, opt, p, widget) return layouts = self._tab_layout(opt) if layouts is None: log.misc.warning("Could not get layouts for tab!") return if element == QStyle.CE_TabBarTab: # We override this so we can control TabBarTabShape/TabBarTabLabel. self.drawControl(QStyle.CE_TabBarTabShape, opt, p, widget) self.drawControl(QStyle.CE_TabBarTabLabel, opt, p, widget) elif element == QStyle.CE_TabBarTabShape: p.fillRect(opt.rect, opt.palette.window()) self._draw_indicator(layouts, opt, p) # We use super() rather than self._style here because we don't want # any sophisticated drawing. super().drawControl(QStyle.CE_TabBarTabShape, opt, p, widget) elif element == QStyle.CE_TabBarTabLabel: if not opt.icon.isNull() and layouts.icon.isValid(): self._draw_icon(layouts, opt, p) alignment = (config.cache['tabs.title.alignment'] | Qt.AlignVCenter | Qt.TextHideMnemonic) self._style.drawItemText(p, layouts.text, int(alignment), opt.palette, bool(opt.state & QStyle.State_Enabled), opt.text, QPalette.WindowText) else: raise ValueError("Invalid element {!r}".format(element)) def pixelMetric(self, metric, option=None, widget=None): """Override pixelMetric to not shift the selected tab. Args: metric: PixelMetric option: const QStyleOption * widget: const QWidget * Return: An int. """ if metric in [QStyle.PM_TabBarTabShiftHorizontal, QStyle.PM_TabBarTabShiftVertical, QStyle.PM_TabBarTabHSpace, QStyle.PM_TabBarTabVSpace, QStyle.PM_TabBarScrollButtonWidth]: return 0 else: return self._style.pixelMetric(metric, option, widget) def subElementRect(self, sr, opt, widget=None): """Override subElementRect to use our own _tab_layout implementation. Args: sr: SubElement opt: QStyleOption widget: QWidget Return: A QRect. """ if sr == QStyle.SE_TabBarTabText: layouts = self._tab_layout(opt) if layouts is None: log.misc.warning("Could not get layouts for tab!") return QRect() return layouts.text elif sr in [QStyle.SE_TabWidgetTabBar, QStyle.SE_TabBarScrollLeftButton]: # Handling SE_TabBarScrollLeftButton so the left scroll button is # aligned properly. Otherwise, empty space will be shown after the # last tab even though the button width is set to 0 # # Need to use super() because we also use super() to render # element in drawControl(); otherwise, we may get bit by # style differences... return super().subElementRect(sr, opt, widget) else: return self._style.subElementRect(sr, opt, widget) def _tab_layout(self, opt): """Compute the text/icon rect from the opt rect. This is based on Qt's QCommonStylePrivate::tabLayout (qtbase/src/widgets/styles/qcommonstyle.cpp) as we can't use the private implementation. Args: opt: QStyleOptionTab Return: A Layout object with two QRects. """ padding = config.cache['tabs.padding'] indicator_padding = config.cache['tabs.indicator.padding'] text_rect = QRect(opt.rect) if not text_rect.isValid(): # This happens sometimes according to crash reports, but no idea # why... return None text_rect.adjust(padding.left, padding.top, -padding.right, -padding.bottom) indicator_width = config.cache['tabs.indicator.width'] if indicator_width == 0: indicator_rect = QRect() else: indicator_rect = QRect(opt.rect) qtutils.ensure_valid(indicator_rect) indicator_rect.adjust(padding.left + indicator_padding.left, padding.top + indicator_padding.top, 0, -(padding.bottom + indicator_padding.bottom)) indicator_rect.setWidth(indicator_width) text_rect.adjust(indicator_width + indicator_padding.left + indicator_padding.right, 0, 0, 0) icon_rect = self._get_icon_rect(opt, text_rect) if icon_rect.isValid(): text_rect.adjust( icon_rect.width() + TabBarStyle.ICON_PADDING, 0, 0, 0) text_rect = self._style.visualRect(opt.direction, opt.rect, text_rect) return Layouts(text=text_rect, icon=icon_rect, indicator=indicator_rect) def _get_icon_rect(self, opt, text_rect): """Get a QRect for the icon to draw. Args: opt: QStyleOptionTab text_rect: The QRect for the text. Return: A QRect. """ icon_size = opt.iconSize if not icon_size.isValid(): icon_extent = self.pixelMetric(QStyle.PM_SmallIconSize) icon_size = QSize(icon_extent, icon_extent) icon_mode = (QIcon.Normal if opt.state & QStyle.State_Enabled else QIcon.Disabled) icon_state = (QIcon.On if opt.state & QStyle.State_Selected else QIcon.Off) # reserve space for favicon when tab bar is vertical (issue #1968) position = config.cache['tabs.position'] if (position in [QTabWidget.East, QTabWidget.West] and config.cache['tabs.favicons.show'] != 'never'): tab_icon_size = icon_size else: actual_size = opt.icon.actualSize(icon_size, icon_mode, icon_state) tab_icon_size = QSize( min(actual_size.width(), icon_size.width()), min(actual_size.height(), icon_size.height())) icon_top = text_rect.center().y() + 1 - tab_icon_size.height() // 2 icon_rect = QRect(QPoint(text_rect.left(), icon_top), tab_icon_size) icon_rect = self._style.visualRect(opt.direction, opt.rect, icon_rect) return icon_rect ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581772733.9080892 qutebrowser-1.10.1/qutebrowser/misc/0000755000175000017510000000000000000000000020601 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/__init__.py0000644000175000017510000000146400000000000022717 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Misc. modules.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/autoupdate.py0000644000175000017510000000546000000000000023333 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Classes related to auto-updating and getting the latest version.""" import json from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl from qutebrowser.misc import httpclient class PyPIVersionClient(QObject): """A client for the PyPI API using HTTPClient. It gets the latest version of qutebrowser from PyPI. Attributes: _client: The HTTPClient used. Class attributes: API_URL: The base API URL. Signals: success: Emitted when getting the version info succeeded. arg: The newest version. error: Emitted when getting the version info failed. arg: The error message, as string. """ API_URL = 'https://pypi.org/pypi/{}/json' success = pyqtSignal(str) error = pyqtSignal(str) def __init__(self, parent=None, client=None): super().__init__(parent) if client is None: self._client = httpclient.HTTPClient(self) else: self._client = client self._client.error.connect(self.error) # type: ignore self._client.success.connect(self.on_client_success) def get_version(self, package='qutebrowser'): """Get the newest version of a given package. Emits success/error when done. Args: package: The name of the package to check. """ url = QUrl(self.API_URL.format(package)) self._client.get(url) @pyqtSlot(str) def on_client_success(self, data): """Process the data and finish when the client finished. Args: data: A string with the received data. """ try: json_data = json.loads(data) except ValueError as e: self.error.emit("Invalid JSON received in reply: {}!".format(e)) return try: self.success.emit(json_data['info']['version']) except KeyError as e: self.error.emit("Malformed data received in reply " "({!r} not found)!".format(e)) return ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/backendproblem.py0000644000175000017510000004631200000000000024131 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2017-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Dialogs shown when there was a problem with a backend choice.""" import os import sys import functools import html import ctypes import ctypes.util import enum import shutil import typing import argparse import attr from PyQt5.QtCore import Qt from PyQt5.QtWidgets import (QApplication, QDialog, QPushButton, QHBoxLayout, QVBoxLayout, QLabel, QMessageBox, QWidget) from PyQt5.QtNetwork import QSslSocket from qutebrowser.config import config, configfiles from qutebrowser.utils import (usertypes, version, qtutils, log, utils, standarddir) from qutebrowser.misc import objects, msgbox, savemanager, quitter class _Result(enum.IntEnum): """The result code returned by the backend problem dialog.""" quit = QDialog.Accepted + 1 restart = QDialog.Accepted + 2 restart_webkit = QDialog.Accepted + 3 restart_webengine = QDialog.Accepted + 4 @attr.s class _Button: """A button passed to BackendProblemDialog.""" text = attr.ib() # type: str setting = attr.ib() # type: str value = attr.ib() # type: str default = attr.ib(default=False) # type: bool def _other_backend( backend: usertypes.Backend ) -> typing.Tuple[usertypes.Backend, str]: """Get the other backend enum/setting for a given backend.""" other_backend = { usertypes.Backend.QtWebKit: usertypes.Backend.QtWebEngine, usertypes.Backend.QtWebEngine: usertypes.Backend.QtWebKit, }[backend] other_setting = other_backend.name.lower()[2:] return (other_backend, other_setting) def _error_text(because: str, text: str, backend: usertypes.Backend) -> str: """Get an error text for the given information.""" other_backend, other_setting = _other_backend(backend) if other_backend == usertypes.Backend.QtWebKit: warning = ("Note that QtWebKit hasn't been updated since " "July 2017 (including security updates).") else: warning = "" return ("Failed to start with the {backend} backend!" "

qutebrowser tried to start with the {backend} backend but " "failed because {because}.

{text}" "

Forcing the {other_backend.name} backend

" "

This forces usage of the {other_backend.name} backend by " "setting the backend = '{other_setting}' option " "(if you have a config.py file, you'll need to set " "this manually). {warning}

".format( backend=backend.name, because=because, text=text, other_backend=other_backend, other_setting=other_setting, warning=warning)) class _Dialog(QDialog): """A dialog which gets shown if there are issues with the backend.""" def __init__(self, *, because: str, text: str, backend: usertypes.Backend, buttons: typing.Sequence[_Button] = None, parent: QWidget = None) -> None: super().__init__(parent) vbox = QVBoxLayout(self) other_backend, other_setting = _other_backend(backend) text = _error_text(because, text, backend) label = QLabel(text) label.setWordWrap(True) label.setTextFormat(Qt.RichText) vbox.addWidget(label) hbox = QHBoxLayout() buttons = [] if buttons is None else buttons quit_button = QPushButton("Quit") quit_button.clicked.connect(lambda: self.done(_Result.quit)) hbox.addWidget(quit_button) backend_text = "Force {} backend".format(other_backend.name) if other_backend == usertypes.Backend.QtWebKit: backend_text += ' (not recommended)' backend_button = QPushButton(backend_text) backend_button.clicked.connect(functools.partial( self._change_setting, 'backend', other_setting)) hbox.addWidget(backend_button) for button in buttons: btn = QPushButton(button.text) btn.setDefault(button.default) btn.clicked.connect(functools.partial( self._change_setting, button.setting, button.value)) hbox.addWidget(btn) vbox.addLayout(hbox) def _change_setting(self, setting: str, value: str) -> None: """Change the given setting and restart.""" config.instance.set_obj(setting, value, save_yaml=True) if setting == 'backend' and value == 'webkit': self.done(_Result.restart_webkit) elif setting == 'backend' and value == 'webengine': self.done(_Result.restart_webengine) else: self.done(_Result.restart) @attr.s class _BackendImports: """Whether backend modules could be imported.""" webkit_available = attr.ib(default=None) # type: bool webengine_available = attr.ib(default=None) # type: bool webkit_error = attr.ib(default=None) # type: str webengine_error = attr.ib(default=None) # type: str class _BackendProblemChecker: """Check for various backend-specific issues.""" def __init__(self, *, no_err_windows: bool, save_manager: savemanager.SaveManager) -> None: self._save_manager = save_manager self._no_err_windows = no_err_windows def _show_dialog(self, *args: typing.Any, **kwargs: typing.Any) -> None: """Show a dialog for a backend problem.""" if self._no_err_windows: text = _error_text(*args, **kwargs) print(text, file=sys.stderr) sys.exit(usertypes.Exit.err_init) dialog = _Dialog(*args, **kwargs) status = dialog.exec_() self._save_manager.save_all(is_exit=True) if status in [_Result.quit, QDialog.Rejected]: pass elif status == _Result.restart_webkit: quitter.instance.restart(override_args={'backend': 'webkit'}) elif status == _Result.restart_webengine: quitter.instance.restart(override_args={'backend': 'webengine'}) elif status == _Result.restart: quitter.instance.restart() else: raise utils.Unreachable(status) sys.exit(usertypes.Exit.err_init) def _nvidia_shader_workaround(self) -> None: """Work around QOpenGLShaderProgram issues. NOTE: This needs to be called before _handle_nouveau_graphics, or some setups will segfault in version.opengl_vendor(). See https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826 """ self._assert_backend(usertypes.Backend.QtWebEngine) if os.environ.get('QUTE_SKIP_LIBGL_WORKAROUND'): return libgl = ctypes.util.find_library("GL") if libgl is not None: ctypes.CDLL(libgl, mode=ctypes.RTLD_GLOBAL) def _handle_nouveau_graphics(self) -> None: """Force software rendering when using the Nouveau driver. WORKAROUND for https://bugreports.qt.io/browse/QTBUG-41242 Should be fixed in Qt 5.10 via https://codereview.qt-project.org/#/c/208664/ """ self._assert_backend(usertypes.Backend.QtWebEngine) if os.environ.get('QUTE_SKIP_NOUVEAU_CHECK'): return if qtutils.version_check('5.10', compiled=False): return if version.opengl_vendor() != 'nouveau': return if (os.environ.get('LIBGL_ALWAYS_SOFTWARE') == '1' or # qt.force_software_rendering = 'software-opengl' 'QT_XCB_FORCE_SOFTWARE_OPENGL' in os.environ or # qt.force_software_rendering = 'chromium', also see: # https://build.opensuse.org/package/view_file/openSUSE:Factory/libqt5-qtwebengine/disable-gpu-when-using-nouveau-boo-1005323.diff?expand=1 'QT_WEBENGINE_DISABLE_NOUVEAU_WORKAROUND' in os.environ): return button = _Button("Force software rendering", 'qt.force_software_rendering', 'chromium') self._show_dialog( backend=usertypes.Backend.QtWebEngine, because="you're using Nouveau graphics", text=("

There are two ways to fix this:

" "

Forcing software rendering

" "

This allows you to use the newer QtWebEngine backend " "(based on Chromium) but could have noticeable performance " "impact (depending on your hardware). This sets the " "qt.force_software_rendering = 'chromium' option " "(if you have a config.py file, you'll need to set " "this manually).

"), buttons=[button], ) raise utils.Unreachable def _handle_wayland(self) -> None: self._assert_backend(usertypes.Backend.QtWebEngine) if os.environ.get('QUTE_SKIP_WAYLAND_CHECK'): return platform = QApplication.instance().platformName() if platform not in ['wayland', 'wayland-egl']: return has_qt511 = qtutils.version_check('5.11', compiled=False) if has_qt511 and config.val.qt.force_software_rendering == 'chromium': return if qtutils.version_check('5.11.2', compiled=False): return buttons = [] text = "

You can work around this in one of the following ways:

" if 'DISPLAY' in os.environ: # XWayland is available, but QT_QPA_PLATFORM=wayland is set buttons.append( _Button("Force XWayland", 'qt.force_platform', 'xcb')) text += ("

Force Qt to use XWayland

" "

This allows you to use the newer QtWebEngine backend " "(based on Chromium). " "This sets the qt.force_platform = 'xcb' option " "(if you have a config.py file, you'll need to " "set this manually).

") else: text += ("

Set up XWayland

" "

This allows you to use the newer QtWebEngine backend " "(based on Chromium). ") if has_qt511: buttons.append(_Button("Force software rendering", 'qt.force_software_rendering', 'chromium')) text += ("

Forcing software rendering

" "

This allows you to use the newer QtWebEngine backend " "(based on Chromium) but could have noticeable " "performance impact (depending on your hardware). This " "sets the qt.force_software_rendering = " "'chromium' option (if you have a config.py " "file, you'll need to set this manually).

") self._show_dialog(backend=usertypes.Backend.QtWebEngine, because="you're using Wayland", text=text, buttons=buttons) def _try_import_backends(self) -> _BackendImports: """Check whether backends can be imported and return BackendImports.""" # pylint: disable=unused-import results = _BackendImports() try: from PyQt5 import QtWebKit from PyQt5 import QtWebKitWidgets except ImportError as e: results.webkit_available = False results.webkit_error = str(e) else: if qtutils.is_new_qtwebkit(): results.webkit_available = True else: results.webkit_available = False results.webkit_error = "Unsupported legacy QtWebKit found" try: from PyQt5 import QtWebEngineWidgets except ImportError as e: results.webengine_available = False results.webengine_error = str(e) else: results.webengine_available = True assert results.webkit_available is not None assert results.webengine_available is not None if not results.webkit_available: assert results.webkit_error is not None if not results.webengine_available: assert results.webengine_error is not None return results def _handle_ssl_support(self, fatal: bool = False) -> None: """Check for full SSL availability. If "fatal" is given, show an error and exit. """ text = ("Could not initialize QtNetwork SSL support. If you use " "OpenSSL 1.1 with a PyQt package from PyPI (e.g. on Archlinux " "or Debian Stretch), you need to set LD_LIBRARY_PATH to the " "path of OpenSSL 1.0. This only affects downloads.") if QSslSocket.supportsSsl(): return if fatal: errbox = msgbox.msgbox(parent=None, title="SSL error", text="Could not initialize SSL support.", icon=QMessageBox.Critical, plain_text=False) errbox.exec_() sys.exit(usertypes.Exit.err_init) assert not fatal log.init.warning(text) def _check_backend_modules(self) -> None: """Check for the modules needed for QtWebKit/QtWebEngine.""" imports = self._try_import_backends() if imports.webkit_available and imports.webengine_available: return elif not imports.webkit_available and not imports.webengine_available: text = ("

qutebrowser needs QtWebKit or QtWebEngine, but " "neither could be imported!

" "

The errors encountered were:

    " "
  • QtWebKit: {webkit_error}" "
  • QtWebEngine: {webengine_error}" "

".format( webkit_error=html.escape(imports.webkit_error), webengine_error=html.escape(imports.webengine_error))) errbox = msgbox.msgbox(parent=None, title="No backend library found!", text=text, icon=QMessageBox.Critical, plain_text=False) errbox.exec_() sys.exit(usertypes.Exit.err_init) elif objects.backend == usertypes.Backend.QtWebKit: if imports.webkit_available: return assert imports.webengine_available self._show_dialog( backend=usertypes.Backend.QtWebKit, because="QtWebKit could not be imported", text="

The error encountered was:
{}

".format( html.escape(imports.webkit_error)) ) elif objects.backend == usertypes.Backend.QtWebEngine: if imports.webengine_available: return assert imports.webkit_available self._show_dialog( backend=usertypes.Backend.QtWebEngine, because="QtWebEngine could not be imported", text="

The error encountered was:
{}

".format( html.escape(imports.webengine_error)) ) raise utils.Unreachable def _handle_cache_nuking(self) -> None: """Nuke the QtWebEngine cache if the Qt version changed. WORKAROUND for https://bugreports.qt.io/browse/QTBUG-72532 """ if not configfiles.state.qt_version_changed: return # Only nuke the cache in cases where we know there are problems. # It seems these issues started with Qt 5.12. # They should be fixed with Qt 5.12.5: # https://codereview.qt-project.org/c/qt/qtwebengine-chromium/+/265408 affected = (qtutils.version_check('5.12', compiled=False) and not qtutils.version_check('5.12.5', compiled=False)) if not affected: return log.init.info("Qt version changed, nuking QtWebEngine cache") cache_dir = os.path.join(standarddir.cache(), 'webengine') if os.path.exists(cache_dir): shutil.rmtree(cache_dir) def _handle_serviceworker_nuking(self) -> None: """Nuke the service workers directory if the Qt version changed. WORKAROUND for: https://bugreports.qt.io/browse/QTBUG-72532 https://bugreports.qt.io/browse/QTBUG-82105 """ if ('serviceworker_workaround' not in configfiles.state['general'] and qtutils.version_check('5.14', compiled=False)): # Nuke the service worker directory once for every install with Qt # 5.14, given that it seems to cause a variety of segfaults. configfiles.state['general']['serviceworker_workaround'] = '514' affected = True else: # Otherwise, just nuke it when the Qt version changed. affected = configfiles.state.qt_version_changed if not affected: return service_worker_dir = os.path.join(standarddir.data(), 'webengine', 'Service Worker') bak_dir = service_worker_dir + '-bak' if not os.path.exists(service_worker_dir): return log.init.info("Qt version changed, removing service workers") # Keep one backup around - we're not 100% sure what persistent data # could be in there, but this folder can grow to ~300 MB. if os.path.exists(bak_dir): shutil.rmtree(bak_dir) shutil.move(service_worker_dir, bak_dir) def _assert_backend(self, backend: usertypes.Backend) -> None: assert objects.backend == backend, objects.backend def check(self) -> None: """Run all checks.""" self._check_backend_modules() if objects.backend == usertypes.Backend.QtWebEngine: self._handle_ssl_support() self._handle_wayland() self._nvidia_shader_workaround() self._handle_nouveau_graphics() self._handle_cache_nuking() self._handle_serviceworker_nuking() else: self._assert_backend(usertypes.Backend.QtWebKit) self._handle_ssl_support(fatal=True) def init(*, args: argparse.Namespace, save_manager: savemanager.SaveManager) -> None: """Run all checks.""" checker = _BackendProblemChecker(no_err_windows=args.no_err_windows, save_manager=save_manager) checker.check() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/checkpyver.py0000644000175000017510000000440400000000000023320 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The-Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Check if qutebrowser is run with the correct python version. This should import and run fine with both python2 and python3. """ import sys try: # Python3 from tkinter import Tk, messagebox except ImportError: # pragma: no cover try: # Python2 from Tkinter import Tk # type: ignore import tkMessageBox as messagebox # type: ignore # noqa: N813 except ImportError: # Some Python without Tk Tk = None # type: ignore messagebox = None # type: ignore # First we check the version of Python. This code should run fine with python2 # and python3. We don't have Qt available here yet, so we just print an error # to stderr. def check_python_version(): """Check if correct python version is run.""" if sys.hexversion < 0x03050200: # We don't use .format() and print_function here just in case someone # still has < 2.6 installed. version_str = '.'.join(map(str, sys.version_info[:3])) text = ("At least Python 3.5.2 is required to run qutebrowser, but " + "it's running with " + version_str + ".\n") if (Tk and # type: ignore '--no-err-windows' not in sys.argv): # pragma: no cover root = Tk() root.withdraw() messagebox.showerror("qutebrowser: Fatal error!", text) else: sys.stderr.write(text) sys.stderr.flush() sys.exit(1) if __name__ == '__main__': check_python_version() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/cmdhistory.py0000644000175000017510000001043200000000000023340 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Command history for the status bar.""" import typing from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject from qutebrowser.utils import usertypes, log, standarddir, objreg from qutebrowser.misc import lineparser class HistoryEmptyError(Exception): """Raised when the history is empty.""" class HistoryEndReachedError(Exception): """Raised when the end of the history is reached.""" class History(QObject): """Command history. Attributes: history: A list of executed commands, with newer commands at the end. _tmphist: Temporary history for history browsing (as NeighborList) Signals: changed: Emitted when an entry was added to the history. """ changed = pyqtSignal() def __init__(self, *, history=None, parent=None): """Constructor. Args: history: The initial history to set. """ super().__init__(parent) self._tmphist = None if history is None: self.history = [] # type: typing.MutableSequence[str] else: self.history = history def __getitem__(self, idx): return self.history[idx] def is_browsing(self): """Check _tmphist to see if we're browsing.""" return self._tmphist is not None def start(self, text): """Start browsing to the history. Called when the user presses the up/down key and wasn't browsing the history already. Args: text: The preset text. """ log.misc.debug("Preset text: '{}'".format(text)) if text: items = [ e for e in self.history if e.startswith(text)] # type: typing.MutableSequence[str] else: items = self.history if not items: raise HistoryEmptyError self._tmphist = usertypes.NeighborList(items) return self._tmphist.lastitem() @pyqtSlot() def stop(self): """Stop browsing the history.""" self._tmphist = None def previtem(self): """Get the previous item in the temp history. start() needs to be called before calling this. """ if not self.is_browsing(): raise ValueError("Currently not browsing history") assert self._tmphist is not None try: return self._tmphist.previtem() except IndexError: raise HistoryEndReachedError def nextitem(self): """Get the next item in the temp history. start() needs to be called before calling this. """ if not self.is_browsing(): raise ValueError("Currently not browsing history") assert self._tmphist is not None try: return self._tmphist.nextitem() except IndexError: raise HistoryEndReachedError def append(self, text): """Append a new item to the history. Args: text: The text to append. """ if not self.history or text != self.history[-1]: self.history.append(text) self.changed.emit() def init(): """Initialize the LimitLineParser storing the history.""" save_manager = objreg.get('save-manager') command_history = lineparser.LimitLineParser( standarddir.data(), 'cmd-history', limit='completion.cmd_history_max_items') objreg.register('command-history', command_history) save_manager.add_saveable('command-history', command_history.save, command_history.changed) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/consolewidget.py0000644000175000017510000001630300000000000024024 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Debugging console.""" import sys import code import typing from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt from PyQt5.QtWidgets import QTextEdit, QWidget, QVBoxLayout, QApplication from PyQt5.QtGui import QTextCursor from qutebrowser.config import config from qutebrowser.misc import cmdhistory, miscwidgets from qutebrowser.utils import utils, objreg console_widget = None class ConsoleLineEdit(miscwidgets.CommandLineEdit): """A QLineEdit which executes entered code and provides a history. Attributes: _history: The command history of executed commands. Signals: execute: Emitted when a commandline should be executed. """ execute = pyqtSignal(str) def __init__(self, _namespace, parent): """Constructor. Args: _namespace: The local namespace of the interpreter. """ super().__init__(parent=parent) self._update_font() config.instance.changed.connect(self._update_font) self._history = cmdhistory.History(parent=self) self.returnPressed.connect(self.on_return_pressed) @pyqtSlot() def on_return_pressed(self): """Execute the line of code which was entered.""" self._history.stop() text = self.text() if text: self._history.append(text) self.execute.emit(text) self.setText('') def history_prev(self): """Go back in the history.""" try: if not self._history.is_browsing(): item = self._history.start(self.text().strip()) else: item = self._history.previtem() except (cmdhistory.HistoryEmptyError, cmdhistory.HistoryEndReachedError): return self.setText(item) def history_next(self): """Go forward in the history.""" if not self._history.is_browsing(): return try: item = self._history.nextitem() except cmdhistory.HistoryEndReachedError: return self.setText(item) def keyPressEvent(self, e): """Override keyPressEvent to handle special keypresses.""" if e.key() == Qt.Key_Up: self.history_prev() e.accept() elif e.key() == Qt.Key_Down: self.history_next() e.accept() elif e.modifiers() & Qt.ControlModifier and e.key() == Qt.Key_C: self.setText('') e.accept() else: super().keyPressEvent(e) @config.change_filter('fonts.debug_console') def _update_font(self): """Set the correct font.""" self.setFont(config.val.fonts.debug_console) class ConsoleTextEdit(QTextEdit): """Custom QTextEdit for console output.""" def __init__(self, parent=None): super().__init__(parent) self.setAcceptRichText(False) self.setReadOnly(True) config.instance.changed.connect(self._update_font) self._update_font() self.setFocusPolicy(Qt.ClickFocus) def __repr__(self): return utils.get_repr(self) @config.change_filter('fonts.debug_console') def _update_font(self): """Update font when config changed.""" self.setFont(config.val.fonts.debug_console) def append_text(self, text): """Append new text and scroll output to bottom. We can't use Qt's way to append stuff because that inserts weird newlines. """ self.moveCursor(QTextCursor.End) self.insertPlainText(text) scrollbar = self.verticalScrollBar() scrollbar.setValue(scrollbar.maximum()) class ConsoleWidget(QWidget): """A widget with an interactive Python console. Attributes: _lineedit: The line edit in the console. _output: The output widget in the console. _vbox: The layout which contains everything. _more: A flag which is set when more input is expected. _buffer: The buffer for multi-line commands. _interpreter: The InteractiveInterpreter to execute code with. """ def __init__(self, parent=None): super().__init__(parent) if not hasattr(sys, 'ps1'): sys.ps1 = '>>> ' if not hasattr(sys, 'ps2'): sys.ps2 = '... ' namespace = { '__name__': '__console__', '__doc__': None, 'q_app': QApplication.instance(), # We use parent as self here because the user "feels" the whole # console, not just the line edit. 'self': parent, 'objreg': objreg, } self._more = False self._buffer = [] # type: typing.MutableSequence[str] self._lineedit = ConsoleLineEdit(namespace, self) self._lineedit.execute.connect(self.push) self._output = ConsoleTextEdit() self.write(self._curprompt()) self._vbox = QVBoxLayout() self._vbox.setSpacing(0) self._vbox.addWidget(self._output) self._vbox.addWidget(self._lineedit) self.setLayout(self._vbox) self._lineedit.setFocus() self._interpreter = code.InteractiveInterpreter(namespace) def __repr__(self): return utils.get_repr(self, visible=self.isVisible()) def write(self, line): """Write a line of text (without added newline) to the output.""" self._output.append_text(line) @pyqtSlot(str) def push(self, line): """Push a line to the interpreter.""" self._buffer.append(line) source = '\n'.join(self._buffer) self.write(line + '\n') # We do two special things with the context managers here: # - We replace stdout/stderr to capture output. Even if we could # override InteractiveInterpreter's write method, most things are # printed elsewhere (e.g. by exec). Other Python GUI shells do the # same. # - We disable our exception hook, so exceptions from the console get # printed and don't open a crashdialog. with utils.fake_io(self.write), utils.disabled_excepthook(): self._more = self._interpreter.runsource(source, '') self.write(self._curprompt()) if not self._more: self._buffer = [] def _curprompt(self): """Get the prompt which is visible currently.""" return sys.ps2 if self._more else sys.ps1 def init(): """Initialize a global console.""" global console_widget console_widget = ConsoleWidget() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/crashdialog.py0000644000175000017510000006222500000000000023442 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """The dialog which gets shown when qutebrowser crashes.""" import re import os import sys import html import getpass import fnmatch import traceback import datetime import enum import typing import pkg_resources from PyQt5.QtCore import pyqtSlot, Qt, QSize from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton, QVBoxLayout, QHBoxLayout, QCheckBox, QDialogButtonBox, QApplication, QMessageBox) import qutebrowser from qutebrowser.utils import version, log, utils from qutebrowser.misc import (miscwidgets, autoupdate, msgbox, httpclient, pastebin) from qutebrowser.config import config, configfiles from qutebrowser.browser import history class Result(enum.IntEnum): """The result code returned by the crash dialog.""" restore = QDialog.Accepted + 1 no_restore = QDialog.Accepted + 2 def parse_fatal_stacktrace(text): """Get useful information from a fatal faulthandler stacktrace. Args: text: The text to parse. Return: A tuple with the first element being the error type, and the second element being the first stacktrace frame. """ lines = [ r'(?PFatal Python error|Windows fatal exception): (?P.*)', r' *', r'(Current )?[Tt]hread [^ ]* \(most recent call first\): *', r' File ".*", line \d+ in (?P.*)', ] m = re.search('\n'.join(lines), text) if m is None: # We got some invalid text. return ('', '') else: msg = m.group('msg') typ = m.group('type') func = m.group('func') if typ == 'Windows fatal exception': msg = 'Windows ' + msg return msg, func def _get_environment_vars(): """Gather environment variables for the crash info.""" masks = ('DESKTOP_SESSION', 'DE', 'QT_*', 'PYTHON*', 'LC_*', 'LANG', 'XDG_*', 'QUTE_*', 'PATH') info = [] for key, value in os.environ.items(): for m in masks: if fnmatch.fnmatch(key, m): info.append('{} = {}'.format(key, value)) return '\n'.join(sorted(info)) class _CrashDialog(QDialog): """Dialog which gets shown after there was a crash. Attributes: These are just here to have a static reference to avoid GCing. _vbox: The main QVBoxLayout _lbl: The QLabel with the static text _debug_log: The QTextEdit with the crash information _btn_box: The QDialogButtonBox containing the buttons. _url: Pastebin URL QLabel. _crash_info: A list of tuples with title and crash information. _paste_client: A PastebinClient instance to use. _pypi_client: A PyPIVersionClient instance to use. _paste_text: The text to pastebin. """ def __init__(self, debug, parent=None): """Constructor for CrashDialog. Args: debug: Whether --debug was given. """ super().__init__(parent) # We don't set WA_DeleteOnClose here as on an exception, we'll get # closed anyways, and it only could have unintended side-effects. self._crash_info = [] # type: typing.List[typing.Tuple[str, str]] self._btn_box = None self._paste_text = None self.setWindowTitle("Whoops!") self.resize(QSize(640, 600)) self._vbox = QVBoxLayout(self) http_client = httpclient.HTTPClient() self._paste_client = pastebin.PastebinClient(http_client, self) self._pypi_client = autoupdate.PyPIVersionClient(self) self._init_text() self._init_contact_input() info = QLabel("What were you doing when this crash/bug happened?") self._vbox.addWidget(info) self._info = QTextEdit() self._info.setTabChangesFocus(True) self._info.setAcceptRichText(False) self._info.setPlaceholderText("- Opened http://www.example.com/\n" "- Switched tabs\n" "- etc...") self._vbox.addWidget(self._info, 5) self._vbox.addSpacing(15) self._debug_log = QTextEdit() self._debug_log.setTabChangesFocus(True) self._debug_log.setAcceptRichText(False) self._debug_log.setLineWrapMode(QTextEdit.NoWrap) self._debug_log.hide() self._fold = miscwidgets.DetailFold("Show log", self) self._fold.toggled.connect(self._debug_log.setVisible) if debug: self._fold.toggle() self._vbox.addWidget(self._fold) self._vbox.addWidget(self._debug_log, 10) self._vbox.addSpacing(15) self._init_checkboxes() self._init_info_text() self._init_buttons() def keyPressEvent(self, e): """Prevent closing :report dialogs when pressing .""" if config.val.input.escape_quits_reporter or e.key() != Qt.Key_Escape: super().keyPressEvent(e) def __repr__(self): return utils.get_repr(self) def _init_contact_input(self): """Initialize the widget asking for contact info.""" contact = QLabel("I'd like to be able to follow up with you, to keep " "you posted on the status of this crash and get more " "information if I need it - how can I contact you?") contact.setWordWrap(True) self._vbox.addWidget(contact) self._contact = QTextEdit() self._contact.setTabChangesFocus(True) self._contact.setAcceptRichText(False) try: try: info = configfiles.state['general']['contact-info'] except KeyError: self._contact.setPlaceholderText("Mail or IRC nickname") else: self._contact.setPlainText(info) except Exception: log.misc.exception("Failed to get contact information!") self._contact.setPlaceholderText("Mail or IRC nickname") self._vbox.addWidget(self._contact, 2) def _init_text(self): """Initialize the main text to be displayed on an exception. Should be extended by subclasses to set the actual text. """ self._lbl = QLabel() self._lbl.setWordWrap(True) self._lbl.setOpenExternalLinks(True) self._lbl.setTextInteractionFlags(Qt.LinksAccessibleByMouse) self._vbox.addWidget(self._lbl) def _init_checkboxes(self): """Initialize the checkboxes.""" def _init_buttons(self): """Initialize the buttons.""" self._btn_box = QDialogButtonBox() self._vbox.addWidget(self._btn_box) self._btn_report = QPushButton("Report") self._btn_report.setDefault(True) self._btn_report.clicked.connect(self.on_report_clicked) self._btn_box.addButton(self._btn_report, QDialogButtonBox.AcceptRole) self._btn_cancel = QPushButton("Don't report") self._btn_cancel.setAutoDefault(False) self._btn_cancel.clicked.connect(self.finish) self._btn_box.addButton(self._btn_cancel, QDialogButtonBox.RejectRole) def _init_info_text(self): """Add an info text encouraging the user to report crashes.""" info_label = QLabel("
There is currently a big backlog of crash " "reports. Thus, it might take a while until your " "report is seen.
A new tool allowing for more " "automation will fix this, but is not ready yet " "at this point.") info_label.setWordWrap(True) self._vbox.addWidget(info_label) def _gather_crash_info(self): """Gather crash information to display. Args: pages: A list of lists of the open pages (URLs as strings) cmdhist: A list with the command history (as strings) exc: An exception tuple (type, value, traceback) """ try: application = QApplication.instance() launch_time = application.launch_time.ctime() crash_time = datetime.datetime.now().ctime() text = 'Launch: {}\nCrash: {}'.format(launch_time, crash_time) self._crash_info.append(('Timestamps', text)) except Exception: self._crash_info.append(("Launch time", traceback.format_exc())) try: self._crash_info.append(("Version info", version.version())) except Exception: self._crash_info.append(("Version info", traceback.format_exc())) try: self._crash_info.append(("Config", config.instance.dump_userconfig())) except Exception: self._crash_info.append(("Config", traceback.format_exc())) try: self._crash_info.append(("Environment", _get_environment_vars())) except Exception: self._crash_info.append(("Environment", traceback.format_exc())) def _set_crash_info(self): """Set/update the crash info.""" self._crash_info = [] self._gather_crash_info() chunks = [] for (header, body) in self._crash_info: if body is not None: h = '==== {} ===='.format(header) chunks.append('\n'.join([h, body])) text = '\n\n'.join(chunks) self._debug_log.setText(text) def _get_error_type(self): """Get the type of the error we're reporting.""" raise NotImplementedError def _get_paste_title_desc(self): """Get a short description of the paste.""" return '' def _get_paste_title(self): """Get a title for the paste.""" desc = self._get_paste_title_desc() title = "qute {} {}".format(qutebrowser.__version__, self._get_error_type()) if desc: title += ' {}'.format(desc) return title def _save_contact_info(self): """Save the contact info to disk.""" try: info = self._contact.toPlainText() configfiles.state['general']['contact-info'] = info except Exception: log.misc.exception("Failed to save contact information!") def report(self): """Paste the crash info into the pastebin.""" lines = [] lines.append("========== Report ==========") lines.append(self._info.toPlainText()) lines.append("========== Contact ==========") lines.append(self._contact.toPlainText()) lines.append("========== Debug log ==========") lines.append(self._debug_log.toPlainText()) self._paste_text = '\n\n'.join(lines) try: user = getpass.getuser() except Exception: log.misc.exception("Error while getting user") user = 'unknown' try: # parent: http://p.cmpl.cc/90286958 self._paste_client.paste(user, self._get_paste_title(), self._paste_text, parent='90286958') except Exception as e: log.misc.exception("Error while paste-binning") exc_text = '{}: {}'.format(e.__class__.__name__, e) self.show_error(exc_text) @pyqtSlot() def on_report_clicked(self): """Report and close dialog if report button was clicked.""" self._btn_report.setEnabled(False) self._btn_cancel.setEnabled(False) self._btn_report.setText("Reporting...") self._paste_client.success.connect(self.on_paste_success) self._paste_client.error.connect(self.show_error) self.report() @pyqtSlot() def on_paste_success(self): """Get the newest version from PyPI when the paste is done.""" self._pypi_client.success.connect(self.on_version_success) self._pypi_client.error.connect(self.on_version_error) self._pypi_client.get_version() @pyqtSlot(str) def show_error(self, text): """Show a paste error dialog. Args: text: The paste text to show. """ error_dlg = ReportErrorDialog(text, self._paste_text, self) error_dlg.finished.connect(self.finish) # type: ignore error_dlg.show() @pyqtSlot(str) def on_version_success(self, newest): """Called when the version was obtained from self._pypi_client. Args: newest: The newest version as a string. """ new_version = pkg_resources.parse_version(newest) cur_version = pkg_resources.parse_version(qutebrowser.__version__) lines = ['The report has been sent successfully. Thanks!'] if new_version > cur_version: lines.append("Note: The newest available version is v{}, " "but you're currently running v{} - please " "update!".format(newest, qutebrowser.__version__)) text = '

'.join(lines) msgbox.information(self, "Report successfully sent!", text, on_finished=self.finish, plain_text=False) @pyqtSlot(str) def on_version_error(self, msg): """Called when the version was not obtained from self._pypi_client. Args: msg: The error message to show. """ lines = ['The report has been sent successfully. Thanks!'] lines.append("There was an error while getting the newest version: " "{}. Please check for a new version on " "qutebrowser.org " "by yourself.".format(msg)) text = '

'.join(lines) msgbox.information(self, "Report successfully sent!", text, on_finished=self.finish, plain_text=False) @pyqtSlot() def finish(self): """Save contact info and close the dialog.""" self._save_contact_info() self.accept() class ExceptionCrashDialog(_CrashDialog): """Dialog which gets shown on an exception. Attributes: _pages: A list of lists of the open pages (URLs as strings) _cmdhist: A list with the command history (as strings) _exc: An exception tuple (type, value, traceback) _qobjects: A list of all QObjects as string. """ def __init__(self, debug, pages, cmdhist, exc, qobjects, parent=None): super().__init__(debug, parent) self._pages = pages self._cmdhist = cmdhist self._exc = exc self._qobjects = qobjects self.setModal(True) self._set_crash_info() def _init_text(self): super()._init_text() text = "Argh! qutebrowser crashed unexpectedly." self._lbl.setText(text) def _init_checkboxes(self): """Add checkboxes to the dialog.""" super()._init_checkboxes() self._chk_restore = QCheckBox("Restore open pages") self._chk_restore.setChecked(True) self._vbox.addWidget(self._chk_restore) self._chk_log = QCheckBox("Include a debug log in the report") self._chk_log.setChecked(True) try: if config.val.content.private_browsing: self._chk_log.setChecked(False) except Exception: log.misc.exception("Error while checking private browsing mode") self._chk_log.toggled.connect(self._set_crash_info) self._vbox.addWidget(self._chk_log) info_label = QLabel("This makes it a lot easier to diagnose the " "crash.
Note that the log might contain " "sensitive information such as which pages you " "visited or keyboard input.
You can show " "and edit the log above.") info_label.setWordWrap(True) self._vbox.addWidget(info_label) def _get_error_type(self): return 'exc' def _get_paste_title_desc(self): desc = traceback.format_exception_only(self._exc[0], self._exc[1]) return desc[0].rstrip() def _gather_crash_info(self): self._crash_info += [ ("Exception", ''.join(traceback.format_exception(*self._exc))), ] super()._gather_crash_info() if self._chk_log.isChecked(): self._crash_info += [ ("Commandline args", ' '.join(sys.argv[1:])), ("Open Pages", '\n\n'.join('\n'.join(e) for e in self._pages)), ("Command history", '\n'.join(self._cmdhist)), ("Objects", self._qobjects), ] try: text = "Log output was disabled." if log.ram_handler is not None: text = log.ram_handler.dump_log() self._crash_info.append(("Debug log", text)) except Exception: self._crash_info.append(("Debug log", traceback.format_exc())) @pyqtSlot() def finish(self): self._save_contact_info() if self._chk_restore.isChecked(): self.done(Result.restore) else: self.done(Result.no_restore) class FatalCrashDialog(_CrashDialog): """Dialog which gets shown when a fatal error occurred. Attributes: _log: The log text to display. _type: The type of error which occurred. _func: The function (top of the stack) in which the error occurred. _chk_history: A checkbox for the user to decide if page history should be sent. """ def __init__(self, debug, text, parent=None): super().__init__(debug, parent) self._log = text self.setAttribute(Qt.WA_DeleteOnClose) self._set_crash_info() self._type, self._func = parse_fatal_stacktrace(self._log) def _get_error_type(self): if self._type in ['Segmentation fault', 'Windows access violation']: return 'segv' else: return self._type def _get_paste_title_desc(self): return self._func def _init_text(self): super()._init_text() text = ("qutebrowser was restarted after a fatal crash.
" "
Note: Crash reports for fatal crashes sometimes don't " "contain the information necessary to fix an issue. Please " "follow the steps in " "stacktrace.asciidoc to submit a stacktrace.
") self._lbl.setText(text) def _init_checkboxes(self): """Add checkboxes to the dialog.""" super()._init_checkboxes() self._chk_history = QCheckBox("Include a history of the last " "accessed pages in the report.") self._chk_history.setChecked(True) try: if config.val.content.private_browsing: self._chk_history.setChecked(False) except Exception: log.misc.exception("Error while checking private browsing mode") self._chk_history.toggled.connect(self._set_crash_info) self._vbox.addWidget(self._chk_history) def _gather_crash_info(self): self._crash_info.append(("Fault log", self._log)) super()._gather_crash_info() if self._chk_history.isChecked(): try: if history.web_history is None: history_data = '' # type: ignore else: history_data = '\n'.join(str(e) for e in history.web_history.get_recent()) except Exception: history_data = traceback.format_exc() self._crash_info.append(("History", history_data)) @pyqtSlot() def on_report_clicked(self): """Prevent empty reports.""" if (not self._info.toPlainText().strip() and not self._contact.toPlainText().strip() and self._get_error_type() == 'segv' and self._func == 'qt_mainloop'): msgbox.msgbox(parent=self, title='Empty crash info', text="Empty reports for fatal crashes are useless " "and mean I'll spend time deleting reports I could " "spend on developing qutebrowser instead.\n\nPlease " "help making qutebrowser better by providing more " "information, or don't report this.", icon=QMessageBox.Critical) else: super().on_report_clicked() class ReportDialog(_CrashDialog): """Dialog which gets shown when the user wants to report an issue by hand. Attributes: _pages: A list of the open pages (URLs as strings) _cmdhist: A list with the command history (as strings) _qobjects: A list of all QObjects as string. """ def __init__(self, pages, cmdhist, qobjects, parent=None): super().__init__(False, parent) self.setAttribute(Qt.WA_DeleteOnClose) self._pages = pages self._cmdhist = cmdhist self._qobjects = qobjects self._set_crash_info() def _init_text(self): super()._init_text() text = "Please describe the bug you encountered below." self._lbl.setText(text) def _init_info_text(self): """We don't want an info text as the user wanted to report.""" def _get_error_type(self): return 'report' def _gather_crash_info(self): super()._gather_crash_info() self._crash_info += [ ("Commandline args", ' '.join(sys.argv[1:])), ("Open Pages", '\n\n'.join('\n'.join(e) for e in self._pages)), ("Command history", '\n'.join(self._cmdhist)), ("Objects", self._qobjects), ] try: text = "Log output was disabled." if log.ram_handler is not None: text = log.ram_handler.dump_log() self._crash_info.append(("Debug log", text)) except Exception: self._crash_info.append(("Debug log", traceback.format_exc())) class ReportErrorDialog(QDialog): """An error dialog shown on unsuccessful reports.""" def __init__(self, exc_text, text, parent=None): super().__init__(parent) vbox = QVBoxLayout(self) label = QLabel("There was an error while reporting the crash:" "
{}

" "Please copy the text below and send a mail to " "" "crash@qutebrowser.org - Thanks!".format( html.escape(exc_text))) vbox.addWidget(label) txt = QTextEdit() txt.setReadOnly(True) txt.setTabChangesFocus(True) txt.setAcceptRichText(False) txt.setText(text) txt.selectAll() vbox.addWidget(txt) hbox = QHBoxLayout() hbox.addStretch() btn = QPushButton("Close") btn.clicked.connect(self.close) # type: ignore hbox.addWidget(btn) vbox.addLayout(hbox) def dump_exception_info(exc, pages, cmdhist, qobjects): """Dump exception info to stderr. Args: exc: An exception tuple (type, value, traceback) pages: A list of lists of the open pages (URLs as strings) cmdhist: A list with the command history (as strings) qobjects: A list of all QObjects as string. """ print(file=sys.stderr) print("\n\n===== Handling exception with --no-err-windows... =====\n\n", file=sys.stderr) print("\n---- Exceptions ----", file=sys.stderr) print(''.join(traceback.format_exception(*exc)), file=sys.stderr) print("\n---- Version info ----", file=sys.stderr) try: print(version.version(), file=sys.stderr) except Exception: traceback.print_exc() print("\n---- Config ----", file=sys.stderr) try: print(config.instance.dump_userconfig(), file=sys.stderr) except Exception: traceback.print_exc() print("\n---- Commandline args ----", file=sys.stderr) print(' '.join(sys.argv[1:]), file=sys.stderr) print("\n---- Open pages ----", file=sys.stderr) print('\n\n'.join('\n'.join(e) for e in pages), file=sys.stderr) print("\n---- Command history ----", file=sys.stderr) print('\n'.join(cmdhist), file=sys.stderr) print("\n---- Objects ----", file=sys.stderr) print(qobjects, file=sys.stderr) print("\n---- Environment ----", file=sys.stderr) try: print(_get_environment_vars(), file=sys.stderr) except Exception: traceback.print_exc() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/crashsignal.py0000644000175000017510000003743100000000000023461 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Handlers for crashes and OS signals.""" import os import os.path import sys import bdb import pdb # noqa: T002 import signal import argparse import functools import faulthandler import typing try: # WORKAROUND for segfaults when using pdb in pytest for some reason... import readline # pylint: disable=unused-import except ImportError: pass import attr from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QObject, QSocketNotifier, QTimer, QUrl) from PyQt5.QtWidgets import QApplication from qutebrowser.api import cmdutils from qutebrowser.misc import earlyinit, crashdialog, ipc, objects from qutebrowser.utils import usertypes, standarddir, log, objreg, debug, utils if typing.TYPE_CHECKING: from qutebrowser.misc import quitter @attr.s class ExceptionInfo: """Information stored when there was an exception.""" pages = attr.ib() cmd_history = attr.ib() objects = attr.ib() crash_handler = typing.cast('CrashHandler', None) class CrashHandler(QObject): """Handler for crashes, reports and exceptions. Attributes: _app: The QApplication instance. _quitter: The Quitter instance. _args: The argparse namespace. _crash_dialog: The CrashDialog currently being shown. _crash_log_file: The file handle for the faulthandler crash log. _crash_log_data: Crash data read from the previous crash log. is_crashing: Used by mainwindow.py to skip confirm questions on crashes. """ def __init__(self, *, app, quitter, args, parent=None): super().__init__(parent) self._app = app self._quitter = quitter self._args = args self._crash_log_file = None self._crash_log_data = None self._crash_dialog = None self.is_crashing = False def activate(self): """Activate the exception hook.""" sys.excepthook = self.exception_hook def init_faulthandler(self): """Handle a segfault from a previous run and set up faulthandler.""" logname = os.path.join(standarddir.data(), 'crash.log') try: # First check if an old logfile exists. if os.path.exists(logname): with open(logname, 'r', encoding='ascii') as f: self._crash_log_data = f.read() os.remove(logname) self._init_crashlogfile() else: # There's no log file, so we can use this to display crashes to # the user on the next start. self._init_crashlogfile() except OSError: log.init.exception("Error while handling crash log file!") self._init_crashlogfile() def display_faulthandler(self): """If there was data in the crash log file, display a dialog.""" assert not self._args.no_err_windows if self._crash_log_data: # Crashlog exists and has data in it, so something crashed # previously. self._crash_dialog = crashdialog.FatalCrashDialog( self._args.debug, self._crash_log_data) self._crash_dialog.show() self._crash_log_data = None def _recover_pages(self, forgiving=False): """Try to recover all open pages. Called from exception_hook, so as forgiving as possible. Args: forgiving: Whether to ignore exceptions. Return: A list containing a list for each window, which in turn contain the opened URLs. """ pages = [] for win_id in objreg.window_registry: win_pages = [] tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) for tab in tabbed_browser.widgets(): try: urlstr = tab.url().toString( QUrl.RemovePassword | QUrl.FullyEncoded) if urlstr: win_pages.append(urlstr) except Exception: if forgiving: log.destroy.exception("Error while recovering tab") else: raise pages.append(win_pages) return pages def _init_crashlogfile(self): """Start a new logfile and redirect faulthandler to it.""" logname = os.path.join(standarddir.data(), 'crash.log') try: self._crash_log_file = open(logname, 'w', encoding='ascii') except OSError: log.init.exception("Error while opening crash log file!") else: earlyinit.init_faulthandler(self._crash_log_file) @cmdutils.register(instance='crash-handler') def report(self): """Report a bug in qutebrowser.""" pages = self._recover_pages() cmd_history = objreg.get('command-history')[-5:] all_objects = debug.get_all_objects() self._crash_dialog = crashdialog.ReportDialog(pages, cmd_history, all_objects) self._crash_dialog.show() @pyqtSlot() def shutdown(self): self.destroy_crashlogfile() def destroy_crashlogfile(self): """Clean up the crash log file and delete it.""" if self._crash_log_file is None: return # We use sys.__stderr__ instead of sys.stderr here so this will still # work when sys.stderr got replaced, e.g. by "Python Tools for Visual # Studio". if sys.__stderr__ is not None: faulthandler.enable(sys.__stderr__) else: faulthandler.disable() # type: ignore try: self._crash_log_file.close() os.remove(self._crash_log_file.name) except OSError: log.destroy.exception("Could not remove crash log!") def _get_exception_info(self): """Get info needed for the exception hook/dialog. Return: An ExceptionInfo object. """ try: pages = self._recover_pages(forgiving=True) except Exception as e: log.destroy.exception("Error while recovering pages: {}".format(e)) pages = [] try: cmd_history = objreg.get('command-history')[-5:] except Exception as e: log.destroy.exception("Error while getting history: {}".format(e)) cmd_history = [] try: all_objects = debug.get_all_objects() except Exception: log.destroy.exception("Error while getting objects") all_objects = "" return ExceptionInfo(pages, cmd_history, all_objects) def exception_hook(self, exctype, excvalue, tb): """Handle uncaught python exceptions. It'll try very hard to write all open tabs to a file, and then exit gracefully. """ exc = (exctype, excvalue, tb) if not self._quitter.quit_status['crash']: log.misc.error("ARGH, there was an exception while the crash " "dialog is already shown:", exc_info=exc) return log.misc.error("Uncaught exception", exc_info=exc) is_ignored_exception = (exctype is bdb.BdbQuit or not issubclass(exctype, Exception)) if 'pdb-postmortem' in objects.debug_flags: if tb is None: pdb.set_trace() # noqa: T100 else: pdb.post_mortem(tb) if is_ignored_exception or 'pdb-postmortem' in objects.debug_flags: # pdb exit, KeyboardInterrupt, ... sys.exit(usertypes.Exit.exception) self._quitter.quit_status['crash'] = False info = self._get_exception_info() if ipc.server is not None: try: ipc.server.ignored = True except Exception: log.destroy.exception("Error while ignoring ipc") try: self._app.lastWindowClosed.disconnect( self._quitter.on_last_window_closed) except TypeError: log.destroy.exception("Error while preventing shutdown") self.is_crashing = True self._app.closeAllWindows() if self._args.no_err_windows: crashdialog.dump_exception_info(exc, info.pages, info.cmd_history, info.objects) else: self._crash_dialog = crashdialog.ExceptionCrashDialog( self._args.debug, info.pages, info.cmd_history, exc, info.objects) ret = self._crash_dialog.exec_() if ret == crashdialog.Result.restore: self._quitter.restart(info.pages) # We might risk a segfault here, but that's better than continuing to # run in some undefined state, so we only do the most needed shutdown # here. qInstallMessageHandler(None) self.destroy_crashlogfile() sys.exit(usertypes.Exit.exception) def raise_crashdlg(self): """Raise the crash dialog if one exists.""" if self._crash_dialog is not None: self._crash_dialog.raise_() class SignalHandler(QObject): """Handler responsible for handling OS signals (SIGINT, SIGTERM, etc.). Attributes: _app: The QApplication instance. _quitter: The Quitter instance. _activated: Whether activate() was called. _notifier: A QSocketNotifier used for signals on Unix. _timer: A QTimer used to poll for signals on Windows. _orig_handlers: A {signal: handler} dict of original signal handlers. _orig_wakeup_fd: The original wakeup filedescriptor. """ def __init__(self, *, app, quitter, parent=None): super().__init__(parent) self._app = app self._quitter = quitter self._notifier = None self._timer = usertypes.Timer(self, 'python_hacks') self._orig_handlers = { } # type: typing.MutableMapping[int, signal._HANDLER] self._activated = False self._orig_wakeup_fd = None # type: typing.Optional[int] def activate(self): """Set up signal handlers. On Windows this uses a QTimer to periodically hand control over to Python so it can handle signals. On Unix, it uses a QSocketNotifier with os.set_wakeup_fd to get notified. """ self._orig_handlers[signal.SIGINT] = signal.signal( signal.SIGINT, self.interrupt) self._orig_handlers[signal.SIGTERM] = signal.signal( signal.SIGTERM, self.interrupt) if utils.is_posix and hasattr(signal, 'set_wakeup_fd'): # pylint: disable=import-error,no-member,useless-suppression import fcntl 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) self._notifier = QSocketNotifier(read_fd, QSocketNotifier.Read, self) self._notifier.activated.connect( # type: ignore self.handle_signal_wakeup) self._orig_wakeup_fd = signal.set_wakeup_fd(write_fd) # pylint: enable=import-error,no-member,useless-suppression else: self._timer.start(1000) self._timer.timeout.connect(lambda: None) self._activated = True def deactivate(self): """Deactivate all signal handlers.""" if not self._activated: return if self._notifier is not None: assert self._orig_wakeup_fd is not None self._notifier.setEnabled(False) rfd = self._notifier.socket() wfd = signal.set_wakeup_fd(self._orig_wakeup_fd) os.close(int(rfd)) os.close(wfd) for sig, handler in self._orig_handlers.items(): signal.signal(sig, handler) self._timer.stop() self._activated = False @pyqtSlot() def handle_signal_wakeup(self): """Handle a newly arrived signal. This gets called via self._notifier when there's a signal. Python will get control here, so the signal will get handled. """ assert self._notifier is not None log.destroy.debug("Handling signal wakeup!") self._notifier.setEnabled(False) read_fd = self._notifier.socket() try: os.read(int(read_fd), 1) except OSError: log.destroy.exception("Failed to read wakeup fd.") self._notifier.setEnabled(True) def _log_later(self, *lines): """Log the given text line-wise with a QTimer.""" for line in lines: QTimer.singleShot(0, functools.partial(log.destroy.info, line)) def interrupt(self, signum, _frame): """Handler for signals to gracefully shutdown (SIGINT/SIGTERM). This calls shutdown and remaps the signal to call interrupt_forcefully the next time. """ signal.signal(signal.SIGINT, self.interrupt_forcefully) signal.signal(signal.SIGTERM, self.interrupt_forcefully) # Signals can arrive anywhere, so we do this in the main thread self._log_later("SIGINT/SIGTERM received, shutting down!", "Do the same again to forcefully quit.") QTimer.singleShot(0, functools.partial( self._quitter.shutdown, 128 + signum)) def interrupt_forcefully(self, signum, _frame): """Interrupt forcefully on the second SIGINT/SIGTERM request. This skips our shutdown routine and calls QApplication:exit instead. It then remaps the signals to call self.interrupt_really_forcefully the next time. """ signal.signal(signal.SIGINT, self.interrupt_really_forcefully) signal.signal(signal.SIGTERM, self.interrupt_really_forcefully) # Signals can arrive anywhere, so we do this in the main thread self._log_later("Forceful quit requested, goodbye cruel world!", "Do the same again to quit with even more force.") QTimer.singleShot(0, functools.partial(self._app.exit, 128 + signum)) def interrupt_really_forcefully(self, signum, _frame): """Interrupt with even more force on the third SIGINT/SIGTERM request. This doesn't run *any* Qt cleanup and simply exits via Python. It will most likely lead to a segfault. """ print("WHY ARE YOU DOING THIS TO ME? :(") sys.exit(128 + signum) def init(q_app: QApplication, args: argparse.Namespace, quitter: 'quitter.Quitter') -> None: """Initialize crash/signal handlers.""" global crash_handler crash_handler = CrashHandler( app=q_app, quitter=quitter, args=args, parent=q_app) objreg.register('crash-handler', crash_handler, command_only=True) crash_handler.activate() quitter.shutting_down.connect(crash_handler.shutdown) signal_handler = SignalHandler(app=q_app, quitter=quitter, parent=q_app) signal_handler.activate() quitter.shutting_down.connect(signal_handler.deactivate) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/debugcachestats.py0000644000175000017510000000324400000000000024307 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2019-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Implementation of the command debug-cache-stats. Because many modules depend on this command, this needs to have as few dependencies as possible to avoid cyclic dependencies. """ import typing # The second element of each tuple should be a lru_cache wrapped function _CACHE_FUNCTIONS = [] # type: typing.List[typing.Tuple[str, typing.Any]] _T = typing.TypeVar('_T', bound=typing.Callable) def register(name: typing.Optional[str] = None) -> typing.Callable[[_T], _T]: """Register a lru_cache wrapped function for debug_cache_stats.""" def wrapper(fn: _T) -> _T: _CACHE_FUNCTIONS.append((fn.__name__ if name is None else name, fn)) return fn return wrapper def debug_cache_stats() -> None: """Print LRU cache stats.""" from qutebrowser.utils import log for name, fn in _CACHE_FUNCTIONS: log.misc.info('{}: {}'.format(name, fn.cache_info())) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/earlyinit.py0000644000175000017510000002545400000000000023165 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The-Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Things which need to be done really early (e.g. before importing Qt). At this point we can be sure we have all python 3.5 features available. """ try: # Importing hunter to register its atexit handler early so it gets called # late. import hunter # pylint: disable=unused-import except ImportError: hunter = None import sys import faulthandler import traceback import signal import importlib import datetime try: import tkinter except ImportError: tkinter = None # type: ignore # NOTE: No qutebrowser or PyQt import should be done here, as some early # initialization needs to take place before that! START_TIME = datetime.datetime.now() def _missing_str(name, *, webengine=False): """Get an error string for missing packages. Args: name: The name of the package. webengine: Whether this is checking the QtWebEngine package """ blocks = ["Fatal error: {} is required to run qutebrowser but " "could not be imported! Maybe it's not installed?".format(name), "The error encountered was:
%ERROR%"] lines = ['Please search for the python3 version of {} in your ' 'distributions packages, or see ' 'https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc' .format(name)] blocks.append('
'.join(lines)) if not webengine: lines = ['If you installed a qutebrowser package for your ' 'distribution, please report this as a bug.'] blocks.append('
'.join(lines)) return '

'.join(blocks) def _die(message, exception=None): """Display an error message using Qt and quit. We import the imports here as we want to do other stuff before the imports. Args: message: The message to display. exception: The exception object if we're handling an exception. """ from PyQt5.QtWidgets import QApplication, QMessageBox from PyQt5.QtCore import Qt if (('--debug' in sys.argv or '--no-err-windows' in sys.argv) and exception is not None): print(file=sys.stderr) traceback.print_exc() app = QApplication(sys.argv) if '--no-err-windows' in sys.argv: print(message, file=sys.stderr) print("Exiting because of --no-err-windows.", file=sys.stderr) else: if exception is not None: message = message.replace('%ERROR%', str(exception)) msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!", message) msgbox.setTextFormat(Qt.RichText) msgbox.resize(msgbox.sizeHint()) msgbox.exec_() app.quit() sys.exit(1) def init_faulthandler(fileobj=sys.__stderr__): """Enable faulthandler module if available. This print a nice traceback on segfaults. We use sys.__stderr__ instead of sys.stderr here so this will still work when sys.stderr got replaced, e.g. by "Python Tools for Visual Studio". Args: fobj: An opened file object to write the traceback to. """ if fileobj is None: # When run with pythonw.exe, sys.__stderr__ can be None: # https://docs.python.org/3/library/sys.html#sys.__stderr__ # If we'd enable faulthandler in that case, we just get a weird # exception, so we don't enable faulthandler if we have no stdout. # # Later when we have our data dir available we re-enable faulthandler # to write to a file so we can display a crash to the user at the next # start. return faulthandler.enable(fileobj) if (hasattr(faulthandler, 'register') and hasattr(signal, 'SIGUSR1') and sys.stderr is not None): # If available, we also want a traceback on SIGUSR1. # pylint: disable=no-member,useless-suppression faulthandler.register(signal.SIGUSR1) # pylint: enable=no-member,useless-suppression def check_pyqt(): """Check if PyQt core modules (QtCore/QtWidgets) are installed.""" for name in ['PyQt5.QtCore', 'PyQt5.QtWidgets']: try: importlib.import_module(name) except ImportError as e: text = _missing_str(name) text = text.replace('', '') text = text.replace('', '') text = text.replace('
', '\n') text = text.replace('%ERROR%', str(e)) if tkinter and '--no-err-windows' not in sys.argv: root = tkinter.Tk() root.withdraw() tkinter.messagebox.showerror("qutebrowser: Fatal error!", text) else: print(text, file=sys.stderr) if '--debug' in sys.argv or '--no-err-windows' in sys.argv: print(file=sys.stderr) traceback.print_exc() sys.exit(1) def qt_version(qversion=None, qt_version_str=None): """Get a Qt version string based on the runtime/compiled versions.""" if qversion is None: from PyQt5.QtCore import qVersion qversion = qVersion() if qt_version_str is None: from PyQt5.QtCore import QT_VERSION_STR qt_version_str = QT_VERSION_STR if qversion != qt_version_str: return '{} (compiled {})'.format(qversion, qt_version_str) else: return qversion def check_qt_version(): """Check if the Qt version is recent enough.""" from PyQt5.QtCore import (qVersion, QT_VERSION, PYQT_VERSION, PYQT_VERSION_STR) from pkg_resources import parse_version from qutebrowser.utils import log parsed_qversion = parse_version(qVersion()) if (QT_VERSION < 0x050701 or PYQT_VERSION < 0x050700 or parsed_qversion < parse_version('5.7.1')): text = ("Fatal error: Qt >= 5.7.1 and PyQt >= 5.7 are required, " "but Qt {} / PyQt {} is installed.".format(qt_version(), PYQT_VERSION_STR)) _die(text) if qVersion().startswith('5.8.'): log.init.warning("Running qutebrowser with Qt 5.8 is untested and " "unsupported!") if (parsed_qversion >= parse_version('5.12') and (PYQT_VERSION < 0x050c00 or QT_VERSION < 0x050c00)): log.init.warning("Combining PyQt {} with Qt {} is unsupported! Ensure " "all versions are newer than 5.12 to avoid potential " "issues.".format(PYQT_VERSION_STR, qt_version())) def check_ssl_support(): """Check if SSL support is available.""" try: from PyQt5.QtNetwork import QSslSocket # pylint: disable=unused-import except ImportError: _die("Fatal error: Your Qt is built without SSL support.") def _check_modules(modules): """Make sure the given modules are available.""" from qutebrowser.utils import log for name, text in modules.items(): try: # https://bitbucket.org/fdik/pypeg/commits/dd15ca462b532019c0a3be1d39b8ee2f3fa32f4e # pylint: disable=bad-continuation with log.ignore_py_warnings( category=DeprecationWarning, message=r'invalid escape sequence' ), log.ignore_py_warnings( category=ImportWarning, message=r'Not importing directory .*: missing __init__' ), log.ignore_py_warnings( category=DeprecationWarning, message=r'the imp module is deprecated', ): # pylint: enable=bad-continuation importlib.import_module(name) except ImportError as e: _die(text, e) def check_libraries(): """Check if all needed Python libraries are installed.""" modules = { 'pkg_resources': _missing_str("pkg_resources/setuptools"), 'pypeg2': _missing_str("pypeg2"), 'jinja2': _missing_str("jinja2"), 'pygments': _missing_str("pygments"), 'yaml': _missing_str("PyYAML"), 'attr': _missing_str("attrs"), 'PyQt5.QtQml': _missing_str("PyQt5.QtQml"), 'PyQt5.QtSql': _missing_str("PyQt5.QtSql"), 'PyQt5.QtOpenGL': _missing_str("PyQt5.QtOpenGL"), } _check_modules(modules) def configure_pyqt(): """Remove the PyQt input hook and enable overflow checking. Doing this means we can't use the interactive shell anymore (which we don't anyways), but we can use pdb instead. """ from PyQt5 import QtCore QtCore.pyqtRemoveInputHook() try: QtCore.pyqt5_enable_new_onexit_scheme(True) # type: ignore except AttributeError: # Added in PyQt 5.13 somewhere, going to be the default in 5.14 pass from qutebrowser.qt import sip try: # Added in sip 4.19.4 sip.enableoverflowchecking(True) # type: ignore except AttributeError: pass def init_log(args): """Initialize logging. Args: args: The argparse namespace. """ from qutebrowser.utils import log log.init_log(args) log.init.debug("Log initialized.") def check_optimize_flag(): """Check whether qutebrowser is running with -OO.""" from qutebrowser.utils import log if sys.flags.optimize >= 2: log.init.warning("Running on optimize level higher than 1, " "unexpected behavior may occur.") def early_init(args): """Do all needed early initialization. Note that it's vital the other earlyinit functions get called in the right order! Args: args: The argparse namespace. """ # First we initialize the faulthandler as early as possible, so we # theoretically could catch segfaults occurring later during earlyinit. init_faulthandler() # Here we check if QtCore is available, and if not, print a message to the # console or via Tk. check_pyqt() # Init logging as early as possible init_log(args) # Now we can be sure QtCore is available, so we can print dialogs on # errors, so people only using the GUI notice them as well. check_libraries() check_qt_version() configure_pyqt() check_ssl_support() check_optimize_flag() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/editor.py0000644000175000017510000002273000000000000022445 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Launcher for an external editor.""" import os import tempfile from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QObject, QProcess, QFileSystemWatcher) from qutebrowser.config import config from qutebrowser.utils import message, log from qutebrowser.misc import guiprocess class ExternalEditor(QObject): """Class to simplify editing a text in an external editor. Attributes: _text: The current text before the editor is opened. _filename: The name of the file to be edited. _remove_file: Whether the file should be removed when the editor is closed. _proc: The GUIProcess of the editor. _watcher: A QFileSystemWatcher to watch the edited file for changes. Only set if watch=True. _content: The last-saved text of the editor. Signals: file_updated: The text in the edited file was updated. arg: The new text. editing_finished: The editor process was closed. """ file_updated = pyqtSignal(str) editing_finished = pyqtSignal() def __init__(self, parent=None, watch=False): super().__init__(parent) self._filename = None self._proc = None self._remove_file = None self._watcher = QFileSystemWatcher(parent=self) if watch else None self._content = None def _cleanup(self): """Clean up temporary files after the editor closed.""" assert self._remove_file is not None if self._watcher is not None and self._watcher.files(): failed = self._watcher.removePaths(self._watcher.files()) if failed: log.procs.error("Failed to unwatch paths: {}".format(failed)) if self._filename is None or not self._remove_file: # Could not create initial file. return assert self._proc is not None try: if self._proc.exit_status() != QProcess.CrashExit: os.remove(self._filename) except OSError as e: # NOTE: Do not replace this with "raise CommandError" as it's # executed async. message.error("Failed to delete tempfile... ({})".format(e)) @pyqtSlot(int, QProcess.ExitStatus) def _on_proc_closed(self, _exitcode, exitstatus): """Write the editor text into the form field and clean up tempfile. Callback for QProcess when the editor was closed. """ log.procs.debug("Editor closed") if exitstatus != QProcess.NormalExit: # No error/cleanup here, since we already handle this in # on_proc_error. return # do a final read to make sure we don't miss the last signal self._on_file_changed(self._filename) self.editing_finished.emit() self._cleanup() @pyqtSlot(QProcess.ProcessError) def _on_proc_error(self, _err): self._cleanup() def edit(self, text, caret_position=None): """Edit a given text. Args: text: The initial text to edit. caret_position: The position of the caret in the text. """ if self._filename is not None: raise ValueError("Already editing a file!") try: self._filename = self._create_tempfile(text, 'qutebrowser-editor-') except OSError as e: message.error("Failed to create initial file: {}".format(e)) return self._remove_file = True line, column = self._calc_line_and_column(text, caret_position) self._start_editor(line=line, column=column) def backup(self): """Create a backup if the content has changed from the original.""" if not self._content: return try: fname = self._create_tempfile(self._content, 'qutebrowser-editor-backup-') message.info('Editor backup at {}'.format(fname)) except OSError as e: message.error('Failed to create editor backup: {}'.format(e)) def _create_tempfile(self, text, prefix): # Close while the external process is running, as otherwise systems # with exclusive write access (e.g. Windows) may fail to update # the file from the external editor, see # https://github.com/qutebrowser/qutebrowser/issues/1767 with tempfile.NamedTemporaryFile( mode='w', prefix=prefix, encoding=config.val.editor.encoding, delete=False) as fobj: if text: fobj.write(text) return fobj.name @pyqtSlot(str) def _on_file_changed(self, path): try: with open(path, 'r', encoding=config.val.editor.encoding) as f: text = f.read() except OSError as e: # NOTE: Do not replace this with "raise CommandError" as it's # executed async. message.error("Failed to read back edited file: {}".format(e)) return log.procs.debug("Read back: {}".format(text)) if self._content != text: self._content = text self.file_updated.emit(text) def edit_file(self, filename): """Edit the file with the given filename.""" self._filename = filename self._remove_file = False self._start_editor() def _start_editor(self, line=1, column=1): """Start the editor with the file opened as self._filename. Args: line: the line number to pass to the editor column: the column number to pass to the editor """ self._proc = guiprocess.GUIProcess(what='editor', parent=self) self._proc.finished.connect(self._on_proc_closed) self._proc.error.connect(self._on_proc_error) editor = config.val.editor.command executable = editor[0] if self._watcher: assert self._filename is not None ok = self._watcher.addPath(self._filename) if not ok: log.procs.error("Failed to watch path: {}" .format(self._filename)) self._watcher.fileChanged.connect( # type: ignore self._on_file_changed) args = [self._sub_placeholder(arg, line, column) for arg in editor[1:]] log.procs.debug("Calling \"{}\" with args {}".format(executable, args)) self._proc.start(executable, args) def _calc_line_and_column(self, text, caret_position): r"""Calculate line and column numbers given a text and caret position. Both line and column are 1-based indexes, because that's what most editors use as line and column starting index. By "most" we mean at least vim, nvim, gvim, emacs, atom, sublimetext, notepad++, brackets, visual studio, QtCreator and so on. To find the line we just count how many newlines there are before the caret and add 1. To find the column we calculate the difference between the caret and the last newline before the caret. For example in the text `aaa\nbb|bbb` (| represents the caret): caret_position = 6 text[:caret_position] = `aaa\nbb` text[:caret_position].count('\n') = 1 caret_position - text[:caret_position].rfind('\n') = 3 Thus line, column = 2, 3, and the caret is indeed in the second line, third column Args: text: the text for which the numbers must be calculated caret_position: the position of the caret in the text, or None Return: A (line, column) tuple of (int, int) """ if caret_position is None: return 1, 1 line = text[:caret_position].count('\n') + 1 column = caret_position - text[:caret_position].rfind('\n') return line, column def _sub_placeholder(self, arg, line, column): """Substitute a single placeholder. If the `arg` input to this function is a valid placeholder it will be substituted with the appropriate value, otherwise it will be left unchanged. Args: arg: an argument of editor.command. line: the previously-calculated line number for the text caret. column: the previously-calculated column number for the text caret. Return: The substituted placeholder or the original argument. """ replacements = { '{}': self._filename, '{file}': self._filename, '{line}': str(line), '{line0}': str(line-1), '{column}': str(column), '{column0}': str(column-1) } for old, new in replacements.items(): arg = arg.replace(old, new) return arg ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/guiprocess.py0000644000175000017510000001503200000000000023337 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2015-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """A QProcess which shows notifications in the GUI.""" import locale import shlex from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QObject, QProcess, QProcessEnvironment) from qutebrowser.utils import message, log from qutebrowser.browser import qutescheme class GUIProcess(QObject): """An external process which shows notifications in the GUI. Args: cmd: The command which was started. args: A list of arguments which gets passed. verbose: Whether to show more messages. _output_messages: Show output as messages. _started: Whether the underlying process is started. _proc: The underlying QProcess. _what: What kind of thing is spawned (process/editor/userscript/...). Used in messages. Signals: error/finished/started signals proxied from QProcess. """ error = pyqtSignal(QProcess.ProcessError) finished = pyqtSignal(int, QProcess.ExitStatus) started = pyqtSignal() def __init__(self, what, *, verbose=False, additional_env=None, output_messages=False, parent=None): super().__init__(parent) self._what = what self.verbose = verbose self._output_messages = output_messages self._started = False self.cmd = None self.args = None self._proc = QProcess(self) self._proc.errorOccurred.connect(self._on_error) # type: ignore self._proc.errorOccurred.connect(self.error) # type: ignore self._proc.finished.connect(self._on_finished) # type: ignore self._proc.finished.connect(self.finished) # type: ignore self._proc.started.connect(self._on_started) # type: ignore self._proc.started.connect(self.started) # type: ignore if additional_env is not None: procenv = QProcessEnvironment.systemEnvironment() for k, v in additional_env.items(): procenv.insert(k, v) self._proc.setProcessEnvironment(procenv) @pyqtSlot() def _on_error(self): """Show a message if there was an error while spawning.""" msg = self._proc.errorString() message.error("Error while spawning {}: {}".format(self._what, msg)) @pyqtSlot(int, QProcess.ExitStatus) def _on_finished(self, code, status): """Show a message when the process finished.""" self._started = False log.procs.debug("Process finished with code {}, status {}.".format( code, status)) encoding = locale.getpreferredencoding(do_setlocale=False) stderr = bytes(self._proc.readAllStandardError()).decode( encoding, 'replace') stdout = bytes(self._proc.readAllStandardOutput()).decode( encoding, 'replace') if self._output_messages: if stdout: message.info(stdout.strip()) if stderr: message.error(stderr.strip()) if status == QProcess.CrashExit: exitinfo = "{} crashed!".format(self._what.capitalize()) message.error(exitinfo) elif status == QProcess.NormalExit and code == 0: exitinfo = "{} exited successfully.".format( self._what.capitalize()) if self.verbose: message.info(exitinfo) else: assert status == QProcess.NormalExit # We call this 'status' here as it makes more sense to the user - # it's actually 'code'. exitinfo = ("{} exited with status {}, see :messages for " "details.").format(self._what.capitalize(), code) message.error(exitinfo) if stdout: log.procs.error("Process stdout:\n" + stdout.strip()) if stderr: log.procs.error("Process stderr:\n" + stderr.strip()) qutescheme.spawn_output = self._spawn_format(exitinfo, stdout, stderr) def _spawn_format(self, exitinfo, stdout, stderr): """Produce a formatted string for spawn output.""" stdout = (stdout or "(No output)").strip() stderr = (stderr or "(No output)").strip() spawn_string = ("{}\n" "\nProcess stdout:\n {}" "\nProcess stderr:\n {}").format(exitinfo, stdout, stderr) return spawn_string @pyqtSlot() def _on_started(self): """Called when the process started successfully.""" log.procs.debug("Process started.") assert not self._started self._started = True def _pre_start(self, cmd, args): """Prepare starting of a QProcess.""" if self._started: raise ValueError("Trying to start a running QProcess!") self.cmd = cmd self.args = args fake_cmdline = ' '.join(shlex.quote(e) for e in [cmd] + list(args)) log.procs.debug("Executing: {}".format(fake_cmdline)) if self.verbose: message.info('Executing: ' + fake_cmdline) def start(self, cmd, args): """Convenience wrapper around QProcess::start.""" log.procs.debug("Starting process.") self._pre_start(cmd, args) self._proc.start(cmd, args) self._proc.closeWriteChannel() def start_detached(self, cmd, args): """Convenience wrapper around QProcess::startDetached.""" log.procs.debug("Starting detached.") self._pre_start(cmd, args) ok, _pid = self._proc.startDetached(cmd, args, None) # type: ignore if not ok: message.error("Error while spawning {}".format(self._what)) return False log.procs.debug("Process started.") self._started = True return True def exit_status(self): return self._proc.exitStatus() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/httpclient.py0000644000175000017510000001041700000000000023334 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """An HTTP client based on QNetworkAccessManager.""" import typing import functools import urllib.request import urllib.parse from PyQt5.QtCore import pyqtSignal, QObject, QTimer from PyQt5.QtNetwork import (QNetworkAccessManager, QNetworkRequest, QNetworkReply) class HTTPRequest(QNetworkRequest): """A QNetworkRquest that follows (secure) redirects by default.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) try: self.setAttribute(QNetworkRequest.RedirectPolicyAttribute, QNetworkRequest.NoLessSafeRedirectPolicy) except AttributeError: # RedirectPolicyAttribute was introduced in 5.9 to replace # FollowRedirectsAttribute. self.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) class HTTPClient(QObject): """An HTTP client based on QNetworkAccessManager. Intended for APIs, automatically decodes data. Attributes: _nam: The QNetworkAccessManager used. _timers: A {QNetworkReply: QTimer} dict. Signals: success: Emitted when the operation succeeded. arg: The received data. error: Emitted when the request failed. arg: The error message, as string. """ success = pyqtSignal(str) error = pyqtSignal(str) def __init__(self, parent=None): super().__init__(parent) self._nam = QNetworkAccessManager(self) self._timers = {} # type: typing.MutableMapping[QNetworkReply, QTimer] def post(self, url, data=None): """Create a new POST request. Args: url: The URL to post to, as QUrl. data: A dict of data to send. """ if data is None: data = {} encoded_data = urllib.parse.urlencode(data).encode('utf-8') request = HTTPRequest(url) request.setHeader(QNetworkRequest.ContentTypeHeader, 'application/x-www-form-urlencoded;charset=utf-8') reply = self._nam.post(request, encoded_data) self._handle_reply(reply) def get(self, url): """Create a new GET request. Emits success/error when done. Args: url: The URL to access, as QUrl. """ request = HTTPRequest(url) reply = self._nam.get(request) self._handle_reply(reply) def _handle_reply(self, reply): """Handle a new QNetworkReply.""" if reply.isFinished(): self.on_reply_finished(reply) else: timer = QTimer(self) timer.setInterval(10000) timer.timeout.connect(reply.abort) timer.start() self._timers[reply] = timer reply.finished.connect(functools.partial( self.on_reply_finished, reply)) def on_reply_finished(self, reply): """Read the data and finish when the reply finished. Args: reply: The QNetworkReply which finished. """ timer = self._timers.pop(reply) if timer is not None: timer.stop() timer.deleteLater() if reply.error() != QNetworkReply.NoError: self.error.emit(reply.errorString()) return try: data = bytes(reply.readAll()).decode('utf-8') except UnicodeDecodeError: self.error.emit("Invalid UTF-8 data received in reply!") return self.success.emit(data) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581772706.0 qutebrowser-1.10.1/qutebrowser/misc/ipc.py0000644000175000017510000004367400000000000021744 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Utilities for IPC with existing instances.""" import os import time import json import getpass import binascii import hashlib from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, Qt from PyQt5.QtNetwork import QLocalSocket, QLocalServer, QAbstractSocket import qutebrowser from qutebrowser.utils import log, usertypes, error, standarddir, utils CONNECT_TIMEOUT = 100 # timeout for connecting/disconnecting WRITE_TIMEOUT = 1000 READ_TIMEOUT = 5000 ATIME_INTERVAL = 60 * 60 * 3 * 1000 # 3 hours PROTOCOL_VERSION = 1 # The ipc server instance server = None def _get_socketname_windows(basedir): """Get a socketname to use for Windows.""" parts = ['qutebrowser', getpass.getuser()] if basedir is not None: md5 = hashlib.md5(basedir.encode('utf-8')).hexdigest() parts.append(md5) return '-'.join(parts) def _get_socketname(basedir): """Get a socketname to use.""" if utils.is_windows: # pragma: no cover return _get_socketname_windows(basedir) parts_to_hash = [getpass.getuser()] if basedir is not None: parts_to_hash.append(basedir) data_to_hash = '-'.join(parts_to_hash).encode('utf-8') md5 = hashlib.md5(data_to_hash).hexdigest() prefix = 'i-' if utils.is_mac else 'ipc-' filename = '{}{}'.format(prefix, md5) return os.path.join(standarddir.runtime(), filename) class Error(Exception): """Base class for IPC exceptions.""" class SocketError(Error): """Exception raised when there was an error with a QLocalSocket. Args: code: The error code. message: The error message. action: The action which was taken when the error happened. """ def __init__(self, action, socket): """Constructor. Args: action: The action which was taken when the error happened. socket: The QLocalSocket which has the error set. """ super().__init__() self.action = action self.code = socket.error() self.message = socket.errorString() def __str__(self): return "Error while {}: {} (error {})".format( self.action, self.message, self.code) class ListenError(Error): """Exception raised when there was a problem with listening to IPC. Args: code: The error code. message: The error message. """ def __init__(self, local_server): """Constructor. Args: local_server: The QLocalServer which has the error set. """ super().__init__() self.code = local_server.serverError() self.message = local_server.errorString() def __str__(self): return "Error while listening to IPC server: {} (error {})".format( self.message, self.code) class AddressInUseError(ListenError): """Emitted when the server address is already in use.""" class IPCServer(QObject): """IPC server to which clients connect to. Attributes: ignored: Whether requests are ignored (in exception hook). _timer: A timer to handle timeouts. _server: A QLocalServer to accept new connections. _socket: The QLocalSocket we're currently connected to. _socketname: The socketname to use. _atime_timer: Timer to update the atime of the socket regularly. Signals: got_args: Emitted when there was an IPC connection and arguments were passed. got_args: Emitted with the raw data an IPC connection got. got_invalid_data: Emitted when there was invalid incoming data. """ got_args = pyqtSignal(list, str, str) got_raw = pyqtSignal(bytes) got_invalid_data = pyqtSignal() def __init__(self, socketname, parent=None): """Start the IPC server and listen to commands. Args: socketname: The socketname to use. parent: The parent to be used. """ super().__init__(parent) self.ignored = False self._socketname = socketname self._timer = usertypes.Timer(self, 'ipc-timeout') self._timer.setInterval(READ_TIMEOUT) self._timer.timeout.connect(self.on_timeout) if utils.is_windows: # pragma: no cover self._atime_timer = None else: self._atime_timer = usertypes.Timer(self, 'ipc-atime') self._atime_timer.setInterval(ATIME_INTERVAL) self._atime_timer.timeout.connect(self.update_atime) self._atime_timer.setTimerType(Qt.VeryCoarseTimer) self._server = QLocalServer(self) self._server.newConnection.connect( # type: ignore self.handle_connection) self._socket = None self._old_socket = None if utils.is_windows: # pragma: no cover # If we use setSocketOptions on Unix with Qt < 5.4, we get a # NameError while listening... log.ipc.debug("Calling setSocketOptions") self._server.setSocketOptions(QLocalServer.UserAccessOption) else: # pragma: no cover log.ipc.debug("Not calling setSocketOptions") def _remove_server(self): """Remove an existing server.""" ok = QLocalServer.removeServer(self._socketname) if not ok: raise Error("Error while removing server {}!".format( self._socketname)) def listen(self): """Start listening on self._socketname.""" log.ipc.debug("Listening as {}".format(self._socketname)) if self._atime_timer is not None: # pragma: no branch self._atime_timer.start() self._remove_server() ok = self._server.listen(self._socketname) if not ok: if self._server.serverError() == QAbstractSocket.AddressInUseError: raise AddressInUseError(self._server) raise ListenError(self._server) if not utils.is_windows: # pragma: no cover # If we use setSocketOptions on Unix with Qt < 5.4, we get a # NameError while listening. # (see b135569d5c6e68c735ea83f42e4baf51f7972281) # # Also, we don't get an AddressInUseError with Qt 5.5: # https://bugreports.qt.io/browse/QTBUG-48635 # # This means we only use setSocketOption on Windows... try: os.chmod(self._server.fullServerName(), 0o700) except FileNotFoundError: # https://github.com/qutebrowser/qutebrowser/issues/1530 # The server doesn't actually exist even if ok was reported as # True, so report this as an error. raise ListenError(self._server) @pyqtSlot('QLocalSocket::LocalSocketError') def on_error(self, err): """Raise SocketError on fatal errors.""" if self._socket is None: # Sometimes this gets called from stale sockets. log.ipc.debug("In on_error with None socket!") return self._timer.stop() log.ipc.debug("Socket 0x{:x}: error {}: {}".format( id(self._socket), self._socket.error(), self._socket.errorString())) if err != QLocalSocket.PeerClosedError: raise SocketError("handling IPC connection", self._socket) @pyqtSlot() def handle_connection(self): """Handle a new connection to the server.""" if self.ignored: return if self._socket is not None: log.ipc.debug("Got new connection but ignoring it because we're " "still handling another one (0x{:x}).".format( id(self._socket))) return socket = self._server.nextPendingConnection() if socket is None: log.ipc.debug("No new connection to handle.") # type: ignore return log.ipc.debug("Client connected (socket 0x{:x}).".format(id(socket))) self._timer.start() self._socket = socket socket.readyRead.connect(self.on_ready_read) # type: ignore if socket.canReadLine(): log.ipc.debug("We can read a line immediately.") self.on_ready_read() socket.error.connect(self.on_error) # type: ignore if socket.error() not in [QLocalSocket.UnknownSocketError, QLocalSocket.PeerClosedError]: log.ipc.debug("We got an error immediately.") self.on_error(socket.error()) socket.disconnected.connect(self.on_disconnected) # type: ignore if socket.state() == QLocalSocket.UnconnectedState: log.ipc.debug("Socket was disconnected immediately.") self.on_disconnected() @pyqtSlot() def on_disconnected(self): """Clean up socket when the client disconnected.""" log.ipc.debug("Client disconnected from socket 0x{:x}.".format( id(self._socket))) self._timer.stop() if self._old_socket is not None: self._old_socket.deleteLater() self._old_socket = self._socket self._socket = None # Maybe another connection is waiting. self.handle_connection() def _handle_invalid_data(self): """Handle invalid data we got from a QLocalSocket.""" assert self._socket is not None log.ipc.error("Ignoring invalid IPC data from socket 0x{:x}.".format( id(self._socket))) self.got_invalid_data.emit() self._socket.error.connect(self.on_error) self._socket.disconnectFromServer() def _handle_data(self, data): """Handle data (as bytes) we got from on_ready_ready_read.""" try: decoded = data.decode('utf-8') except UnicodeDecodeError: log.ipc.error("invalid utf-8: {!r}".format(binascii.hexlify(data))) self._handle_invalid_data() return log.ipc.debug("Processing: {}".format(decoded)) try: json_data = json.loads(decoded) except ValueError: log.ipc.error("invalid json: {}".format(decoded.strip())) self._handle_invalid_data() return for name in ['args', 'target_arg']: if name not in json_data: log.ipc.error("Missing {}: {}".format(name, decoded.strip())) self._handle_invalid_data() return try: protocol_version = int(json_data['protocol_version']) except (KeyError, ValueError): log.ipc.error("invalid version: {}".format(decoded.strip())) self._handle_invalid_data() return if protocol_version != PROTOCOL_VERSION: log.ipc.error("incompatible version: expected {}, got {}".format( PROTOCOL_VERSION, protocol_version)) self._handle_invalid_data() return args = json_data['args'] target_arg = json_data['target_arg'] if target_arg is None: # https://www.riverbankcomputing.com/pipermail/pyqt/2016-April/037375.html target_arg = '' cwd = json_data.get('cwd', '') assert cwd is not None self.got_args.emit(args, target_arg, cwd) @pyqtSlot() def on_ready_read(self): """Read json data from the client.""" if self._socket is None: # pragma: no cover # This happens when doing a connection while another one is already # active for some reason. if self._old_socket is None: log.ipc.warning("In on_ready_read with None socket and " "old_socket!") return log.ipc.debug("In on_ready_read with None socket!") socket = self._old_socket else: socket = self._socket self._timer.stop() while socket is not None and socket.canReadLine(): data = bytes(socket.readLine()) self.got_raw.emit(data) log.ipc.debug("Read from socket 0x{:x}: {!r}".format( id(socket), data)) self._handle_data(data) self._timer.start() @pyqtSlot() def on_timeout(self): """Cancel the current connection if it was idle for too long.""" if self._socket is None: # pragma: no cover log.ipc.debug("on_timeout got called with None socket!") return log.ipc.error("IPC connection timed out " "(socket 0x{:x}).".format(id(self._socket))) self._socket.disconnectFromServer() if self._socket is not None: # pragma: no cover # on_socket_disconnected sets it to None self._socket.waitForDisconnected(CONNECT_TIMEOUT) if self._socket is not None: # pragma: no cover # on_socket_disconnected sets it to None self._socket.abort() @pyqtSlot() def update_atime(self): """Update the atime of the socket file all few hours. From the XDG basedir spec: To ensure that your files are not removed, they should have their access time timestamp modified at least once every 6 hours of monotonic time or the 'sticky' bit should be set on the file. """ path = self._server.fullServerName() if not path: log.ipc.error("In update_atime with no server path!") return log.ipc.debug("Touching {}".format(path)) os.utime(path) @pyqtSlot() def shutdown(self): """Shut down the IPC server cleanly.""" log.ipc.debug("Shutting down IPC (socket 0x{:x})".format( id(self._socket))) if self._socket is not None: self._socket.deleteLater() self._socket = None self._timer.stop() if self._atime_timer is not None: # pragma: no branch self._atime_timer.stop() try: self._atime_timer.timeout.disconnect(self.update_atime) except TypeError: pass self._server.close() self._server.deleteLater() self._remove_server() def send_to_running_instance(socketname, command, target_arg, *, socket=None): """Try to send a commandline to a running instance. Blocks for CONNECT_TIMEOUT ms. Args: socketname: The name which should be used for the socket. command: The command to send to the running instance. target_arg: --target command line argument socket: The socket to read data from, or None. Return: True if connecting was successful, False if no connection was made. """ if socket is None: socket = QLocalSocket() log.ipc.debug("Connecting to {}".format(socketname)) socket.connectToServer(socketname) connected = socket.waitForConnected(CONNECT_TIMEOUT) if connected: log.ipc.info("Opening in existing instance") json_data = {'args': command, 'target_arg': target_arg, 'version': qutebrowser.__version__, 'protocol_version': PROTOCOL_VERSION} try: cwd = os.getcwd() except OSError: pass else: json_data['cwd'] = cwd line = json.dumps(json_data) + '\n' data = line.encode('utf-8') log.ipc.debug("Writing: {!r}".format(data)) socket.writeData(data) socket.waitForBytesWritten(WRITE_TIMEOUT) if socket.error() != QLocalSocket.UnknownSocketError: raise SocketError("writing to running instance", socket) socket.disconnectFromServer() if socket.state() != QLocalSocket.UnconnectedState: socket.waitForDisconnected(CONNECT_TIMEOUT) return True else: if socket.error() not in [QLocalSocket.ConnectionRefusedError, QLocalSocket.ServerNotFoundError]: raise SocketError("connecting to running instance", socket) log.ipc.debug("No existing instance present (error {})".format( socket.error())) return False def display_error(exc, args): """Display a message box with an IPC error.""" error.handle_fatal_exc( exc, args, "Error while connecting to running instance!", post_text="Maybe another instance is running but frozen?") def send_or_listen(args): """Send the args to a running instance or start a new IPCServer. Args: args: The argparse namespace. Return: The IPCServer instance if no running instance was detected. None if an instance was running and received our request. """ global server socketname = _get_socketname(args.basedir) try: try: sent = send_to_running_instance(socketname, args.command, args.target) if sent: return None log.init.debug("Starting IPC server...") server = IPCServer(socketname) server.listen() return server except AddressInUseError: # This could be a race condition... log.init.debug("Got AddressInUseError, trying again.") time.sleep(0.5) sent = send_to_running_instance(socketname, args.command, args.target) if sent: return None else: raise except Error as e: display_error(e, args) raise ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/keyhintwidget.py0000644000175000017510000001147600000000000024043 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2016-2020 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Small window that pops up to show hints for possible keystrings. When a user inputs a key that forms a partial match, this shows a small window with each possible completion of that keystring and the corresponding command. It is intended to help discoverability of keybindings. """ import html import fnmatch import re from PyQt5.QtWidgets import QLabel, QSizePolicy from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt from qutebrowser.config import config, stylesheet from qutebrowser.utils import utils, usertypes from qutebrowser.misc import objects from qutebrowser.keyinput import keyutils class KeyHintView(QLabel): """The view showing hints for key bindings based on the current key string. Attributes: _win_id: Window ID of parent. Signals: update_geometry: Emitted when this widget should be resized/positioned. """ STYLESHEET = """ QLabel { font: {{ conf.fonts.keyhint }}; color: {{ conf.colors.keyhint.fg }}; background-color: {{ conf.colors.keyhint.bg }}; padding: 6px; {% if conf.statusbar.position == 'top' %} border-bottom-right-radius: {{ conf.keyhint.radius }}px; {% else %} border-top-right-radius: {{ conf.keyhint.radius }}px; {% endif %} } """ update_geometry = pyqtSignal() def __init__(self, win_id, parent=None): super().__init__(parent) self.setTextFormat(Qt.RichText) self._win_id = win_id self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum) self.hide() self._show_timer = usertypes.Timer(self, 'keyhint_show') self._show_timer.timeout.connect(self.show) self._show_timer.setSingleShot(True) stylesheet.set_register(self) def __repr__(self): return utils.get_repr(self, win_id=self._win_id) def showEvent(self, e): """Adjust the keyhint size when it's freshly shown.""" self.update_geometry.emit() super().showEvent(e) @pyqtSlot(str) def update_keyhint(self, modename, prefix): """Show hints for the given prefix (or hide if prefix is empty). Args: prefix: The current partial keystring. """ match = re.fullmatch(r'(\d*)(.*)', prefix) assert match is not None, prefix countstr, prefix = match.groups() if not prefix: self._show_timer.stop() self.hide() return def blacklisted(keychain): return any(fnmatch.fnmatchcase(keychain, glob) for glob in config.val.keyhint.blacklist) def takes_count(cmdstr): """Return true iff this command can take a count argument.""" cmdname = cmdstr.split(' ')[0] cmd = objects.commands.get(cmdname) return cmd and cmd.takes_count() bindings_dict = config.key_instance.get_bindings_for(modename) bindings = [(k, v) for (k, v) in sorted(bindings_dict.items()) if keyutils.KeySequence.parse(prefix).matches(k) and not blacklisted(str(k)) and (takes_count(v) or not countstr)] if not bindings: self._show_timer.stop() return # delay so a quickly typed keychain doesn't display hints self._show_timer.setInterval(config.val.keyhint.delay) self._show_timer.start() suffix_color = html.escape(config.val.colors.keyhint.suffix.fg) text = '' for seq, cmd in bindings: text += ( "" "{}" "{}" "{}" "" ).format( html.escape(prefix), suffix_color, html.escape(str(seq)[len(prefix):]), html.escape(cmd) ) text = '{}
'.format(text) self.setText(text) self.adjustSize() self.update_geometry.emit() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/lineparser.py0000644000175000017510000001650300000000000023324 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Parser for line-based files like histories.""" import os import os.path import contextlib import typing from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject from qutebrowser.utils import log, utils, qtutils from qutebrowser.config import config class BaseLineParser(QObject): """A LineParser without any real data. Attributes: _configdir: Directory to read the config from, or None. _configfile: The config file path. _fname: Filename of the config. _binary: Whether to open the file in binary mode. Signals: changed: Emitted when the history was changed. """ changed = pyqtSignal() def __init__(self, configdir, fname, *, binary=False, parent=None): """Constructor. Args: configdir: Directory to read the config from. fname: Filename of the config file. binary: Whether to open the file in binary mode. _opened: Whether the underlying file is open """ super().__init__(parent) self._configdir = configdir self._configfile = os.path.join(self._configdir, fname) self._fname = fname self._binary = binary self._opened = False def __repr__(self): return utils.get_repr(self, constructor=True, configdir=self._configdir, fname=self._fname, binary=self._binary) def _prepare_save(self): """Prepare saving of the file. Return: True if the file should be saved, False otherwise. """ os.makedirs(self._configdir, 0o755, exist_ok=True) return True def _after_save(self): """Log a message after saving is done.""" log.destroy.debug("Saved to {}".format(self._configfile)) @contextlib.contextmanager def _open(self, mode): """Open self._configfile for reading. Args: mode: The mode to use ('a'/'r'/'w') Raises: IOError: if the file is already open Yields: a file object for the config file """ assert self._configfile is not None if self._opened: raise IOError("Refusing to double-open LineParser.") self._opened = True try: if self._binary: with open(self._configfile, mode + 'b') as f: yield f else: with open(self._configfile, mode, encoding='utf-8') as f: yield f finally: self._opened = False def _write(self, fp, data): """Write the data to a file. Args: fp: A file object to write the data to. data: The data to write. """ if not data: return if self._binary: fp.write(b'\n'.join(data)) fp.write(b'\n') else: fp.write('\n'.join(data)) fp.write('\n') def save(self): """Save the history to disk.""" raise NotImplementedError def clear(self): """Clear the contents of the file.""" raise NotImplementedError class LineParser(BaseLineParser): """Parser for configuration files which are simply line-based. Attributes: data: A list of lines. """ def __init__(self, configdir, fname, *, binary=False, parent=None): """Constructor. Args: configdir: Directory to read the config from. fname: Filename of the config file. binary: Whether to open the file in binary mode. """ super().__init__(configdir, fname, binary=binary, parent=parent) if not os.path.isfile(self._configfile): self.data = [] # type: typing.Sequence[str] else: log.init.debug("Reading {}".format(self._configfile)) self._read() def __iter__(self): return iter(self.data) def __getitem__(self, key): return self.data[key] def _read(self): """Read the data from self._configfile.""" with self._open('r') as f: if self._binary: self.data = [line.rstrip(b'\n') for line in f] else: self.data = [line.rstrip('\n') for line in f] def save(self): """Save the config file.""" if self._opened: raise IOError("Refusing to double-open LineParser.") do_save = self._prepare_save() if not do_save: return self._opened = True try: assert self._configfile is not None with qtutils.savefile_open(self._configfile, self._binary) as f: self._write(f, self.data) finally: self._opened = False self._after_save() def clear(self): self.data = [] self.save() class LimitLineParser(LineParser): """A LineParser with a limited count of lines. Attributes: _limit: The config option used to limit the maximum number of lines. """ def __init__(self, configdir, fname, *, limit, binary=False, parent=None): """Constructor. Args: configdir: Directory to read the config from, or None. fname: Filename of the config file. limit: Config option which contains a limit. binary: Whether to open the file in binary mode. """ super().__init__(configdir, fname, binary=binary, parent=parent) self._limit = limit if limit is not None and configdir is not None: config.instance.changed.connect(self._cleanup_file) def __repr__(self): return utils.get_repr(self, constructor=True, configdir=self._configdir, fname=self._fname, limit=self._limit, binary=self._binary) @pyqtSlot(str) def _cleanup_file(self, option): """Delete the file if the limit was changed to 0.""" assert self._configfile is not None if option != self._limit: return value = config.instance.get(option) if value == 0: if os.path.exists(self._configfile): os.remove(self._configfile) def save(self): """Save the config file.""" limit = config.instance.get(self._limit) if limit == 0: return do_save = self._prepare_save() if not do_save: return assert self._configfile is not None with qtutils.savefile_open(self._configfile, self._binary) as f: self._write(f, self.data[-limit:]) self._after_save() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581708820.0 qutebrowser-1.10.1/qutebrowser/misc/miscwidgets.py0000644000175000017510000002347100000000000023504 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Misc. widgets used at different places.""" import typing from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QSize, QTimer from PyQt5.QtWidgets import (QLineEdit, QWidget, QHBoxLayout, QLabel, QStyleOption, QStyle, QLayout, QApplication) from PyQt5.QtGui import QValidator, QPainter from qutebrowser.config import config from qutebrowser.utils import utils from qutebrowser.misc import cmdhistory class MinimalLineEditMixin: """A mixin to give a QLineEdit a minimal look and nicer repr().""" def __init__(self): self.setStyleSheet( # type: ignore """ QLineEdit { border: 0px; padding-left: 1px; background-color: transparent; } """ ) self.setAttribute(Qt.WA_MacShowFocusRect, False) # type: ignore def keyPressEvent(self, e): """Override keyPressEvent to paste primary selection on Shift + Ins.""" if e.key() == Qt.Key_Insert and e.modifiers() == Qt.ShiftModifier: try: text = utils.get_clipboard(selection=True, fallback=True) except utils.ClipboardError: e.ignore() else: e.accept() self.insert(text) # type: ignore return super().keyPressEvent(e) # type: ignore def __repr__(self): return utils.get_repr(self) class CommandLineEdit(QLineEdit): """A QLineEdit with a history and prompt chars. Attributes: history: The command history object. _validator: The current command validator. _promptlen: The length of the current prompt. """ def __init__(self, *, parent=None): super().__init__(parent) self.history = cmdhistory.History(parent=self) self._validator = _CommandValidator(self) self.setValidator(self._validator) self.textEdited.connect(self.on_text_edited) self.cursorPositionChanged.connect(self.__on_cursor_position_changed) self._promptlen = 0 def __repr__(self): return utils.get_repr(self, text=self.text()) @pyqtSlot(str) def on_text_edited(self, _text): """Slot for textEdited. Stop history browsing.""" self.history.stop() @pyqtSlot(int, int) def __on_cursor_position_changed(self, _old, new): """Prevent the cursor moving to the prompt. We use __ here to avoid accidentally overriding it in subclasses. """ if new < self._promptlen: self.cursorForward(self.hasSelectedText(), self._promptlen - new) def set_prompt(self, text): """Set the current prompt to text. This updates the validator, and makes sure the user can't move the cursor behind the prompt. """ self._validator.prompt = text self._promptlen = len(text) class _CommandValidator(QValidator): """Validator to prevent the : from getting deleted. Attributes: prompt: The current prompt. """ def __init__(self, parent=None): super().__init__(parent) self.prompt = None def validate(self, string, pos): """Override QValidator::validate. Args: string: The string to validate. pos: The current cursor position. Return: A tuple (status, string, pos) as a QValidator should. """ if self.prompt is None or string.startswith(self.prompt): return (QValidator.Acceptable, string, pos) else: return (QValidator.Invalid, string, pos) class DetailFold(QWidget): """A "fold" widget with an arrow to show/hide details. Attributes: _folded: Whether the widget is currently folded or not. _hbox: The HBoxLayout the arrow/label are in. _arrow: The FoldArrow widget. Signals: toggled: Emitted when the widget was folded/unfolded. arg 0: bool, if the contents are currently visible. """ toggled = pyqtSignal(bool) def __init__(self, text, parent=None): super().__init__(parent) self._folded = True self._hbox = QHBoxLayout(self) self._hbox.setContentsMargins(0, 0, 0, 0) self._arrow = _FoldArrow() self._hbox.addWidget(self._arrow) label = QLabel(text) self._hbox.addWidget(label) self._hbox.addStretch() def toggle(self): """Toggle the fold of the widget.""" self._folded = not self._folded self._arrow.fold(self._folded) self.toggled.emit(not self._folded) def mousePressEvent(self, e): """Toggle the fold if the widget was pressed. Args: e: The QMouseEvent. """ if e.button() == Qt.LeftButton: e.accept() self.toggle() else: super().mousePressEvent(e) class _FoldArrow(QWidget): """The arrow shown for the DetailFold widget. Attributes: _folded: Whether the widget is currently folded or not. """ def __init__(self, parent=None): super().__init__(parent) self._folded = True def fold(self, folded): """Fold/unfold the widget. Args: folded: The new desired state. """ self._folded = folded self.update() def paintEvent(self, _event): """Paint the arrow. Args: _paint: The QPaintEvent (unused). """ opt = QStyleOption() opt.initFrom(self) painter = QPainter(self) if self._folded: elem = QStyle.PE_IndicatorArrowRight else: elem = QStyle.PE_IndicatorArrowDown self.style().drawPrimitive(elem, opt, painter, self) def minimumSizeHint(self): """Return a sensible size.""" return QSize(8, 8) class WrapperLayout(QLayout): """A Qt layout which simply wraps a single widget. This is used so the widget is hidden behind a defined API and can't easily be accidentally accessed. """ def __init__(self, parent=None): super().__init__(parent) self._widget = typing.cast(QWidget, None) def addItem(self, _widget): raise utils.Unreachable def sizeHint(self): return self._widget.sizeHint() def itemAt(self, _index): return None def takeAt(self, _index): raise utils.Unreachable def setGeometry(self, rect): self._widget.setGeometry(rect) def wrap(self, container, widget): """Wrap the given widget in the given container.""" self._widget = widget container.setFocusProxy(widget) widget.setParent(container) def unwrap(self): self._widget.setParent(None) # type: ignore self._widget.deleteLater() class PseudoLayout(QLayout): """A layout which isn't actually a real layout. This is used to replace QWebEngineView's internal layout, as a WORKAROUND for https://bugreports.qt.io/browse/QTBUG-68224 and other related issues. This is partly inspired by https://codereview.qt-project.org/#/c/230894/ which does something similar as part of Qt. """ def addItem(self, item): assert self.parent() is not None item.widget().setParent(self.parent()) def removeItem(self, item): item.widget().setParent(None) def count(self): return 0 def itemAt(self, _pos): return None def widget(self): return self.parent().render_widget() def setGeometry(self, rect): """Resize the render widget when the view is resized.""" widget = self.widget() if widget is not None: widget.setGeometry(rect) def sizeHint(self): """Make sure the view has the sizeHint of the render widget.""" widget = self.widget() if widget is not None: return widget.sizeHint() return QSize() class FullscreenNotification(QLabel): """A label telling the user this page is now fullscreen.""" def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet(""" background-color: rgba(50, 50, 50, 80%); color: white; border-radius: 20px; padding: 30px; """) all_bindings = config.key_instance.get_reverse_bindings_for('normal') bindings = all_bindings.get('fullscreen --leave') if bindings: key = bindings[0] self.setText("Press {} to exit fullscreen.".format(key)) else: self.setText("Page is now fullscreen.") self.resize(self.sizeHint()) if config.val.content.windowed_fullscreen: geom = self.parentWidget().geometry() else: geom = QApplication.desktop().screenGeometry(self) self.move((geom.width() - self.sizeHint().width()) // 2, 30) def set_timeout(self, timeout): """Hide the widget after the given timeout.""" QTimer.singleShot(timeout, self._on_timeout) @pyqtSlot() def _on_timeout(self): """Hide and delete the widget.""" self.hide() self.deleteLater() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/msgbox.py0000644000175000017510000000475700000000000022467 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2015-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Convenience functions to show message boxes.""" import sys from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QMessageBox from qutebrowser.misc import objects class DummyBox: """A dummy QMessageBox returned when --no-err-windows is used.""" def exec_(self): pass def msgbox(parent, title, text, *, icon, buttons=QMessageBox.Ok, on_finished=None, plain_text=None): """Display a QMessageBox with the given icon. Args: parent: The parent to set for the message box. title: The title to set. text: The text to set. buttons: The buttons to set (QMessageBox::StandardButtons) on_finished: A slot to connect to the 'finished' signal. plain_text: Whether to force plain text (True) or rich text (False). None (the default) uses Qt's auto detection. Return: A new QMessageBox. """ if objects.args.no_err_windows: print('Message box: {}; {}'.format(title, text), file=sys.stderr) return DummyBox() box = QMessageBox(parent) box.setAttribute(Qt.WA_DeleteOnClose) box.setIcon(icon) box.setStandardButtons(buttons) if on_finished is not None: box.finished.connect(on_finished) # type: ignore if plain_text: box.setTextFormat(Qt.PlainText) elif plain_text is not None: box.setTextFormat(Qt.RichText) box.setWindowTitle(title) box.setText(text) box.show() return box def information(*args, **kwargs): """Display an information box. Args: *args: Passed to msgbox. **kwargs: Passed to msgbox. Return: A new QMessageBox. """ return msgbox(*args, icon=QMessageBox.Information, **kwargs) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/objects.py0000644000175000017510000000303600000000000022606 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2017-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Various global objects.""" # NOTE: We need to be careful with imports here, as this is imported from # earlyinit. import typing import argparse if typing.TYPE_CHECKING: from qutebrowser.utils import usertypes from qutebrowser.commands import command class NoBackend: """Special object when there's no backend set so we notice that.""" @property def name(self) -> str: raise AssertionError("No backend set!") def __eq__(self, other: typing.Any) -> bool: raise AssertionError("No backend set!") backend = NoBackend() # type: typing.Union[usertypes.Backend, NoBackend] commands = {} # type: typing.Dict[str, command.Command] debug_flags = set() # type: typing.Set[str] args = typing.cast(argparse.Namespace, None) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/pastebin.py0000644000175000017510000000602100000000000022757 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Client for the pastebin.""" import urllib.parse from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl class PastebinClient(QObject): """A client for Stikked pastebins using HTTPClient. Attributes: _client: The HTTPClient used. Class attributes: API_URL: The base API URL. Signals: success: Emitted when the paste succeeded. arg: The URL of the paste, as string. error: Emitted when the paste failed. arg: The error message, as string. """ API_URL = 'https://crashes.qutebrowser.org/api/' MISC_API_URL = 'https://paste.the-compiler.org/api/' success = pyqtSignal(str) error = pyqtSignal(str) def __init__(self, client, parent=None, api_url=API_URL): """Constructor. Args: client: The HTTPClient to use. Will be reparented. api_url: The Stikked pastebin endpoint to use. """ super().__init__(parent) client.setParent(self) client.error.connect(self.error) client.success.connect(self.on_client_success) self._client = client self._api_url = api_url def paste(self, name, title, text, parent=None, private=False): """Paste the text into a pastebin and return the URL. Args: name: The username to post as. title: The post title. text: The text to post. parent: The parent paste to reply to. private: Whether to paste privately. """ data = { 'text': text, 'title': title, 'name': name, 'apikey': 'ihatespam', } if parent is not None: data['reply'] = parent if private: data['private'] = '1' url = QUrl(urllib.parse.urljoin(self._api_url, 'create')) self._client.post(url, data) @pyqtSlot(str) def on_client_success(self, data): """Process the data and finish when the client finished. Args: data: A string with the received data. """ if data.startswith('http://') or data.startswith('https://'): self.success.emit(data) else: self.error.emit("Invalid data received in reply!") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/quitter.py0000644000175000017510000002724500000000000022662 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Helpers related to quitting qutebrowser cleanly.""" import os import os.path import sys import json import atexit import shutil import typing import argparse import tokenize import functools import subprocess from PyQt5.QtCore import QObject, pyqtSignal, QTimer from PyQt5.QtWidgets import QApplication try: import hunter except ImportError: hunter = None import qutebrowser from qutebrowser.api import cmdutils from qutebrowser.config import config from qutebrowser.utils import log from qutebrowser.misc import sessions, ipc, objects from qutebrowser.mainwindow import prompt from qutebrowser.completion.models import miscmodels instance = typing.cast('Quitter', None) class Quitter(QObject): """Utility class to quit/restart the QApplication. Attributes: quit_status: The current quitting status. _is_shutting_down: Whether we're currently shutting down. _args: The argparse namespace. """ shutting_down = pyqtSignal() # Emitted immediately before shut down def __init__(self, *, args: argparse.Namespace, parent: QObject = None) -> None: super().__init__(parent) self.quit_status = { 'crash': True, 'tabs': False, 'main': False, } self._is_shutting_down = False self._args = args def on_last_window_closed(self) -> None: """Slot which gets invoked when the last window was closed.""" self.shutdown(last_window=True) def _compile_modules(self) -> None: """Compile all modules to catch SyntaxErrors.""" if os.path.basename(sys.argv[0]) == 'qutebrowser': # Launched via launcher script return elif hasattr(sys, 'frozen'): return else: path = os.path.abspath(os.path.dirname(qutebrowser.__file__)) if not os.path.isdir(path): # Probably running from a python egg. return for dirpath, _dirnames, filenames in os.walk(path): for fn in filenames: if os.path.splitext(fn)[1] == '.py' and os.path.isfile(fn): with tokenize.open(os.path.join(dirpath, fn)) as f: compile(f.read(), fn, 'exec') def _get_restart_args( self, pages: typing.Iterable[str] = (), session: str = None, override_args: typing.Mapping[str, str] = None ) -> typing.Sequence[str]: """Get args to relaunch qutebrowser. Args: pages: The pages to re-open. session: The session to load, or None. override_args: Argument overrides as a dict. Return: The commandline as a list of strings. """ if os.path.basename(sys.argv[0]) == 'qutebrowser': # Launched via launcher script args = [sys.argv[0]] elif hasattr(sys, 'frozen'): args = [sys.executable] else: args = [sys.executable, '-m', 'qutebrowser'] # Add all open pages so they get reopened. page_args = [] # type: typing.MutableSequence[str] for win in pages: page_args.extend(win) page_args.append('') # Serialize the argparse namespace into json and pass that to the new # process via --json-args. # We do this as there's no way to "unparse" the namespace while # ignoring some arguments. argdict = vars(self._args) argdict['session'] = None argdict['url'] = [] argdict['command'] = page_args[:-1] argdict['json_args'] = None # Ensure the given session (or none at all) gets opened. if session is None: argdict['session'] = None argdict['override_restore'] = True else: argdict['session'] = session argdict['override_restore'] = False # Ensure :restart works with --temp-basedir if self._args.temp_basedir: argdict['temp_basedir'] = False argdict['temp_basedir_restarted'] = True if override_args is not None: argdict.update(override_args) # Dump the data data = json.dumps(argdict) args += ['--json-args', data] log.destroy.debug("args: {}".format(args)) return args def restart(self, pages: typing.Sequence[str] = (), session: str = None, override_args: typing.Mapping[str, str] = None) -> bool: """Inner logic to restart qutebrowser. The "better" way to restart is to pass a session (_restart usually) as that'll save the complete state. However we don't do that (and pass a list of pages instead) when we restart because of an exception, as that's a lot simpler and we don't want to risk anything going wrong. Args: pages: A list of URLs to open. session: The session to load, or None. override_args: Argument overrides as a dict. Return: True if the restart succeeded, False otherwise. """ self._compile_modules() log.destroy.debug("sys.executable: {}".format(sys.executable)) log.destroy.debug("sys.path: {}".format(sys.path)) log.destroy.debug("sys.argv: {}".format(sys.argv)) log.destroy.debug("frozen: {}".format(hasattr(sys, 'frozen'))) # Save the session if one is given. if session is not None: sessions.session_manager.save(session, with_private=True) # Make sure we're not accepting a connection from the new process # before we fully exited. assert ipc.server is not None ipc.server.shutdown() # Open a new process and immediately shutdown the existing one try: args = self._get_restart_args(pages, session, override_args) subprocess.Popen(args) except OSError: log.destroy.exception("Failed to restart") return False else: return True def shutdown(self, status: int = 0, session: sessions.ArgType = None, last_window: bool = False, is_restart: bool = False) -> None: """Quit qutebrowser. Args: status: The status code to exit with. session: A session name if saving should be forced. last_window: If the shutdown was triggered due to the last window closing. is_restart: If we're planning to restart. """ if self._is_shutting_down: return self._is_shutting_down = True log.destroy.debug("Shutting down with status {}, session {}...".format( status, session)) if sessions.session_manager is not None: if session is not None: sessions.session_manager.save(session, last_window=last_window, load_next_time=True) elif config.val.auto_save.session: sessions.session_manager.save(sessions.default, last_window=last_window, load_next_time=True) if prompt.prompt_queue.shutdown(): # If shutdown was called while we were asking a question, we're in # a still sub-eventloop (which gets quit now) and not in the main # one. # This means we need to defer the real shutdown to when we're back # in the real main event loop, or we'll get a segfault. log.destroy.debug("Deferring real shutdown because question was " "active.") QTimer.singleShot(0, functools.partial(self._shutdown_2, status, is_restart=is_restart)) else: # If we have no questions to shut down, we are already in the real # event loop, so we can shut down immediately. self._shutdown_2(status, is_restart=is_restart) def _shutdown_2(self, status: int, is_restart: bool) -> None: """Second stage of shutdown.""" log.destroy.debug("Stage 2 of shutting down...") # Tell everything to shut itself down self.shutting_down.emit() # Delete temp basedir if ((self._args.temp_basedir or self._args.temp_basedir_restarted) and not is_restart): atexit.register(shutil.rmtree, self._args.basedir, ignore_errors=True) # Now we can hopefully quit without segfaults log.destroy.debug("Deferring QApplication::exit...") # We use a singleshot timer to exit here to minimize the likelihood of # segfaults. QTimer.singleShot(0, functools.partial(self._shutdown_3, status)) def _shutdown_3(self, status: int) -> None: """Finally shut down the QApplication.""" log.destroy.debug("Now calling QApplication::exit.") if 'debug-exit' in objects.debug_flags: if hunter is None: print("Not logging late shutdown because hunter could not be " "imported!", file=sys.stderr) else: print("Now logging late shutdown.", file=sys.stderr) hunter.trace() QApplication.instance().exit(status) @cmdutils.register(name='quit') @cmdutils.argument('session', completion=miscmodels.session) def quit_(save: bool = False, session: sessions.ArgType = None) -> None: """Quit qutebrowser. Args: save: When given, save the open windows even if auto_save.session is turned off. session: The name of the session to save. """ if session is not None and not save: raise cmdutils.CommandError("Session name given without --save!") if save: if session is None: session = sessions.default instance.shutdown(session=session) else: instance.shutdown() @cmdutils.register() def restart() -> None: """Restart qutebrowser while keeping existing tabs open.""" try: ok = instance.restart(session='_restart') except sessions.SessionError as e: log.destroy.exception("Failed to save session!") raise cmdutils.CommandError("Failed to save session: {}!" .format(e)) except SyntaxError as e: log.destroy.exception("Got SyntaxError") raise cmdutils.CommandError("SyntaxError in {}:{}: {}".format( e.filename, e.lineno, e)) if ok: instance.shutdown(is_restart=True) def init(args: argparse.Namespace) -> None: """Initialize the global Quitter instance.""" global instance qapp = QApplication.instance() instance = Quitter(args=args, parent=qapp) instance.shutting_down.connect(log.shutdown_log) qapp.lastWindowClosed.connect(instance.on_last_window_closed) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581772706.0 qutebrowser-1.10.1/qutebrowser/misc/savemanager.py0000644000175000017510000002027100000000000023446 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2015-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Saving things to disk periodically.""" import os.path import collections import typing from PyQt5.QtCore import pyqtSlot, QObject, QTimer from qutebrowser.config import config from qutebrowser.api import cmdutils from qutebrowser.utils import utils, log, message, usertypes, error from qutebrowser.misc import objects class Saveable: """A single thing which can be saved. Attributes: _name: The name of the thing to be saved. _dirty: Whether the saveable was changed since the last save. _save_handler: The function to call to save this Saveable. _save_on_exit: Whether to always save this saveable on exit. _config_opt: A config option which decides whether to auto-save or not. None if no such option exists. _filename: The filename of the underlying file. """ def __init__(self, name, save_handler, changed=None, config_opt=None, filename=None): self._name = name self._dirty = False self._save_handler = save_handler self._config_opt = config_opt if changed is not None: changed.connect(self.mark_dirty) self._save_on_exit = False else: self._save_on_exit = True self._filename = filename if filename is not None and not os.path.exists(filename): self._dirty = True self.save() def __repr__(self): return utils.get_repr(self, name=self._name, dirty=self._dirty, save_handler=self._save_handler, config_opt=self._config_opt, save_on_exit=self._save_on_exit, filename=self._filename) def mark_dirty(self): """Mark this saveable as dirty (having changes).""" log.save.debug("Marking {} as dirty.".format(self._name)) self._dirty = True def save(self, is_exit=False, explicit=False, silent=False, force=False): """Save this saveable. Args: is_exit: Whether we're currently exiting qutebrowser. explicit: Whether the user explicitly requested this save. silent: Don't write information to log. force: Force saving, no matter what. """ if (self._config_opt is not None and (not config.instance.get(self._config_opt)) and (not explicit) and (not force)): if not silent: log.save.debug("Not saving {name} because autosaving has been " "disabled by {cfg[0]} -> {cfg[1]}.".format( name=self._name, cfg=self._config_opt)) return do_save = self._dirty or (self._save_on_exit and is_exit) or force if not silent: log.save.debug("Save of {} requested - dirty {}, save_on_exit {}, " "is_exit {}, force {} -> {}".format( self._name, self._dirty, self._save_on_exit, is_exit, force, do_save)) if do_save: self._save_handler() self._dirty = False class SaveManager(QObject): """Responsible to save 'saveables' periodically and on exit. Attributes: saveables: A dict mapping names to Saveable instances. _save_timer: The Timer used to periodically auto-save things. """ def __init__(self, parent=None): super().__init__(parent) self.saveables = collections.OrderedDict( ) # type: typing.MutableMapping[str, Saveable] self._save_timer = usertypes.Timer(self, name='save-timer') self._save_timer.timeout.connect(self.autosave) self._set_autosave_interval() config.instance.changed.connect(self._set_autosave_interval) def __repr__(self): return utils.get_repr(self, saveables=self.saveables) @config.change_filter('auto_save.interval') def _set_autosave_interval(self): """Set the auto-save interval.""" interval = config.val.auto_save.interval if interval == 0: self._save_timer.stop() else: self._save_timer.setInterval(interval) self._save_timer.start() def add_saveable(self, name, save, changed=None, config_opt=None, filename=None, dirty=False): """Add a new saveable. Args: name: The name to use. save: The function to call to save this saveable. changed: The signal emitted when this saveable changed. config_opt: An option deciding whether to auto-save or not. filename: The filename of the underlying file, so we can force saving if it doesn't exist. dirty: Whether the saveable is already dirty. """ if name in self.saveables: raise ValueError("Saveable {} already registered!".format(name)) saveable = Saveable(name, save, changed, config_opt, filename) self.saveables[name] = saveable if dirty: saveable.mark_dirty() QTimer.singleShot(0, saveable.save) def save(self, name, is_exit=False, explicit=False, silent=False, force=False): """Save a saveable by name. Args: is_exit: Whether we're currently exiting qutebrowser. explicit: Whether this save operation was triggered explicitly. silent: Don't write information to log. Used to reduce log spam when autosaving. force: Force saving, no matter what. """ self.saveables[name].save(is_exit=is_exit, explicit=explicit, silent=silent, force=force) def save_all(self, *args, **kwargs): """Save all saveables.""" for saveable in self.saveables: self.save(saveable, *args, **kwargs) @pyqtSlot() def autosave(self): """Slot used when the configs are auto-saved.""" for (key, saveable) in self.saveables.items(): try: saveable.save(silent=True) except OSError as e: message.error("Failed to auto-save {}: {}".format(key, e)) @cmdutils.register(instance='save-manager', name='save', star_args_optional=True) def save_command(self, *what): """Save configs and state. Args: *what: What to save (`config`/`key-config`/`cookies`/...). If not given, everything is saved. """ if what: explicit = True else: what = tuple(self.saveables) explicit = False for key in what: if key not in self.saveables: message.error("{} is nothing which can be saved".format(key)) else: try: self.save(key, explicit=explicit, force=True) except OSError as e: message.error("Could not save {}: {}".format(key, e)) log.save.debug(":save saved {}".format(', '.join(what))) @pyqtSlot() def shutdown(self): """Save all saveables when shutting down.""" for key in self.saveables: try: self.save(key, is_exit=True) except OSError as e: error.handle_fatal_exc( e, objects.args, "Error while saving!", pre_text="Error while saving {}".format(key)) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/sessions.py0000644000175000017510000005451100000000000023027 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2015-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Management of sessions - saved tabs/windows.""" import os import os.path import itertools import urllib import typing from PyQt5.QtCore import QUrl, QObject, QPoint, QTimer, pyqtSlot from PyQt5.QtWidgets import QApplication import yaml from qutebrowser.utils import (standarddir, objreg, qtutils, log, message, utils) from qutebrowser.api import cmdutils from qutebrowser.config import config, configfiles from qutebrowser.completion.models import miscmodels from qutebrowser.mainwindow import mainwindow from qutebrowser.qt import sip _JsonType = typing.MutableMapping[str, typing.Any] class Sentinel: """Sentinel value for default argument.""" default = Sentinel() session_manager = typing.cast('SessionManager', None) ArgType = typing.Union[str, Sentinel] def init(parent=None): """Initialize sessions. Args: parent: The parent to use for the SessionManager. """ base_path = os.path.join(standarddir.data(), 'sessions') try: os.mkdir(base_path) except FileExistsError: pass global session_manager session_manager = SessionManager(base_path, parent) @pyqtSlot() def shutdown(): session_manager.delete_autosave() class SessionError(Exception): """Exception raised when a session failed to load/save.""" class SessionNotFoundError(SessionError): """Exception raised when a session to be loaded was not found.""" class TabHistoryItem: """A single item in the tab history. Attributes: url: The QUrl of this item. original_url: The QUrl of this item which was originally requested. title: The title as string of this item. active: Whether this item is the item currently navigated to. user_data: The user data for this item. """ def __init__(self, url, title, *, original_url=None, active=False, user_data=None): self.url = url if original_url is None: self.original_url = url else: self.original_url = original_url self.title = title self.active = active self.user_data = user_data def __repr__(self): return utils.get_repr(self, constructor=True, url=self.url, original_url=self.original_url, title=self.title, active=self.active, user_data=self.user_data) class SessionManager(QObject): """Manager for sessions. Attributes: _base_path: The path to store sessions under. _last_window_session: The session data of the last window which was closed. current: The name of the currently loaded session, or None. did_load: Set when a session was loaded. """ def __init__(self, base_path, parent=None): super().__init__(parent) self.current = None # type: typing.Optional[str] self._base_path = base_path self._last_window_session = None self.did_load = False def _get_session_path(self, name, check_exists=False): """Get the session path based on a session name or absolute path. Args: name: The name of the session. check_exists: Whether it should also be checked if the session exists. """ path = os.path.expanduser(name) if os.path.isabs(path) and ((not check_exists) or os.path.exists(path)): return path else: path = os.path.join(self._base_path, name + '.yml') if check_exists and not os.path.exists(path): raise SessionNotFoundError(path) return path def exists(self, name): """Check if a named session exists.""" try: self._get_session_path(name, check_exists=True) except SessionNotFoundError: return False else: return True def _save_tab_item(self, tab, idx, item): """Save a single history item in a tab. Args: tab: The tab to save. idx: The index of the current history item. item: The history item. Return: A dict with the saved data for this item. """ data = { 'url': bytes(item.url().toEncoded()).decode('ascii'), } # type: _JsonType if item.title(): data['title'] = item.title() else: # https://github.com/qutebrowser/qutebrowser/issues/879 if tab.history.current_idx() == idx: data['title'] = tab.title() else: data['title'] = data['url'] if item.originalUrl() != item.url(): encoded = item.originalUrl().toEncoded() data['original-url'] = bytes(encoded).decode('ascii') if tab.history.current_idx() == idx: data['active'] = True try: user_data = item.userData() except AttributeError: # QtWebEngine user_data = None if tab.history.current_idx() == idx: pos = tab.scroller.pos_px() data['zoom'] = tab.zoom.factor() data['scroll-pos'] = {'x': pos.x(), 'y': pos.y()} elif user_data is not None: if 'zoom' in user_data: data['zoom'] = user_data['zoom'] if 'scroll-pos' in user_data: pos = user_data['scroll-pos'] data['scroll-pos'] = {'x': pos.x(), 'y': pos.y()} data['pinned'] = tab.data.pinned return data def _save_tab(self, tab, active): """Get a dict with data for a single tab. Args: tab: The WebView to save. active: Whether the tab is currently active. """ data = {'history': []} # type: _JsonType if active: data['active'] = True for idx, item in enumerate(tab.history): qtutils.ensure_valid(item) item_data = self._save_tab_item(tab, idx, item) if item.url().scheme() == 'qute' and item.url().host() == 'back': # don't add qute://back to the session file if item_data.get('active', False) and data['history']: # mark entry before qute://back as active data['history'][-1]['active'] = True else: data['history'].append(item_data) return data def _save_all(self, *, only_window=None, with_private=False): """Get a dict with data for all windows/tabs.""" data = {'windows': []} # type: _JsonType if only_window is not None: winlist = [only_window] # type: typing.Iterable[int] else: winlist = objreg.window_registry for win_id in sorted(winlist): tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) main_window = objreg.get('main-window', scope='window', window=win_id) # We could be in the middle of destroying a window here if sip.isdeleted(main_window): continue if tabbed_browser.is_private and not with_private: continue win_data = {} # type: _JsonType active_window = QApplication.instance().activeWindow() if getattr(active_window, 'win_id', None) == win_id: win_data['active'] = True win_data['geometry'] = bytes(main_window.saveGeometry()) win_data['tabs'] = [] if tabbed_browser.is_private: win_data['private'] = True for i, tab in enumerate(tabbed_browser.widgets()): active = i == tabbed_browser.widget.currentIndex() win_data['tabs'].append(self._save_tab(tab, active)) data['windows'].append(win_data) return data def _get_session_name(self, name): """Helper for save to get the name to save the session to. Args: name: The name of the session to save, or the 'default' sentinel object. """ if name is default: name = config.val.session.default_name if name is None: if self.current is not None: name = self.current else: name = 'default' return name def save(self, name, last_window=False, load_next_time=False, only_window=None, with_private=False): """Save a named session. Args: name: The name of the session to save, or the 'default' sentinel object. last_window: If set, saves the saved self._last_window_session instead of the currently open state. load_next_time: If set, prepares this session to be load next time. only_window: If set, only tabs in the specified window is saved. with_private: Include private windows. Return: The name of the saved session. """ name = self._get_session_name(name) path = self._get_session_path(name) log.sessions.debug("Saving session {} to {}...".format(name, path)) if last_window: data = self._last_window_session if data is None: log.sessions.error("last_window_session is None while saving!") return None else: data = self._save_all(only_window=only_window, with_private=with_private) log.sessions.vdebug("Saving data: {}".format(data)) # type: ignore try: with qtutils.savefile_open(path) as f: utils.yaml_dump(data, f) # type: ignore except (OSError, UnicodeEncodeError, yaml.YAMLError) as e: raise SessionError(e) if load_next_time: configfiles.state['general']['session'] = name return name def save_autosave(self): """Save the autosave session.""" try: self.save('_autosave') except SessionError as e: log.sessions.error("Failed to save autosave session: {}".format(e)) def delete_autosave(self): """Delete the autosave session.""" try: self.delete('_autosave') except SessionNotFoundError: # Exiting before the first load finished pass except SessionError as e: log.sessions.error("Failed to delete autosave session: {}" .format(e)) def save_last_window_session(self): """Temporarily save the session for the last closed window.""" self._last_window_session = self._save_all() def _load_tab(self, new_tab, data): """Load yaml data into a newly opened tab.""" entries = [] lazy_load = [] # type: typing.MutableSequence[_JsonType] # use len(data['history']) # -> dropwhile empty if not session.lazy_session lazy_index = len(data['history']) gen = itertools.chain( itertools.takewhile(lambda _: not lazy_load, enumerate(data['history'])), enumerate(lazy_load), itertools.dropwhile(lambda i: i[0] < lazy_index, enumerate(data['history']))) for i, histentry in gen: user_data = {} if 'zoom' in data: # The zoom was accidentally stored in 'data' instead of per-tab # earlier. # See https://github.com/qutebrowser/qutebrowser/issues/728 user_data['zoom'] = data['zoom'] elif 'zoom' in histentry: user_data['zoom'] = histentry['zoom'] if 'scroll-pos' in data: # The scroll position was accidentally stored in 'data' instead # of per-tab earlier. # See https://github.com/qutebrowser/qutebrowser/issues/728 pos = data['scroll-pos'] user_data['scroll-pos'] = QPoint(pos['x'], pos['y']) elif 'scroll-pos' in histentry: pos = histentry['scroll-pos'] user_data['scroll-pos'] = QPoint(pos['x'], pos['y']) if 'pinned' in histentry: new_tab.data.pinned = histentry['pinned'] if (config.val.session.lazy_restore and histentry.get('active', False) and not histentry['url'].startswith('qute://back')): # remove "active" mark and insert back page marked as active lazy_index = i + 1 lazy_load.append({ 'title': histentry['title'], 'url': 'qute://back#' + urllib.parse.quote(histentry['title']), 'active': True }) histentry['active'] = False active = histentry.get('active', False) url = QUrl.fromEncoded(histentry['url'].encode('ascii')) if 'original-url' in histentry: orig_url = QUrl.fromEncoded( histentry['original-url'].encode('ascii')) else: orig_url = url entry = TabHistoryItem(url=url, original_url=orig_url, title=histentry['title'], active=active, user_data=user_data) entries.append(entry) if active: new_tab.title_changed.emit(histentry['title']) try: new_tab.history.private_api.load_items(entries) except ValueError as e: raise SessionError(e) def _load_window(self, win): """Turn yaml data into windows.""" window = mainwindow.MainWindow(geometry=win['geometry'], private=win.get('private', None)) window.show() tabbed_browser = objreg.get('tabbed-browser', scope='window', window=window.win_id) tab_to_focus = None for i, tab in enumerate(win['tabs']): new_tab = tabbed_browser.tabopen(background=False) self._load_tab(new_tab, tab) if tab.get('active', False): tab_to_focus = i if new_tab.data.pinned: tabbed_browser.widget.set_tab_pinned(new_tab, new_tab.data.pinned) if tab_to_focus is not None: tabbed_browser.widget.setCurrentIndex(tab_to_focus) if win.get('active', False): QTimer.singleShot(0, tabbed_browser.widget.activateWindow) def load(self, name, temp=False): """Load a named session. Args: name: The name of the session to load. temp: If given, don't set the current session. """ path = self._get_session_path(name, check_exists=True) try: with open(path, encoding='utf-8') as f: data = utils.yaml_load(f) except (OSError, UnicodeDecodeError, yaml.YAMLError) as e: raise SessionError(e) log.sessions.debug("Loading session {} from {}...".format(name, path)) if data is None: raise SessionError("Got empty session file") if qtutils.is_single_process(): if any(win.get('private') for win in data['windows']): raise SessionError("Can't load a session with private windows " "in single process mode.") for win in data['windows']: self._load_window(win) if data['windows']: self.did_load = True if not name.startswith('_') and not temp: self.current = name def delete(self, name): """Delete a session.""" path = self._get_session_path(name, check_exists=True) try: os.remove(path) except OSError as e: raise SessionError(e) def list_sessions(self): """Get a list of all session names.""" sessions = [] for filename in os.listdir(self._base_path): base, ext = os.path.splitext(filename) if ext == '.yml': sessions.append(base) return sorted(sessions) @cmdutils.register() @cmdutils.argument('name', completion=miscmodels.session) def session_load(name: str, *, clear: bool = False, temp: bool = False, force: bool = False, delete: bool = False) -> None: """Load a session. Args: name: The name of the session. clear: Close all existing windows. temp: Don't set the current session for :session-save. force: Force loading internal sessions (starting with an underline). delete: Delete the saved session once it has loaded. """ if name.startswith('_') and not force: raise cmdutils.CommandError("{} is an internal session, use --force " "to load anyways.".format(name)) old_windows = list(objreg.window_registry.values()) try: session_manager.load(name, temp=temp) except SessionNotFoundError: raise cmdutils.CommandError("Session {} not found!".format(name)) except SessionError as e: raise cmdutils.CommandError("Error while loading session: {}" .format(e)) else: if clear: for win in old_windows: win.close() if delete: try: session_manager.delete(name) except SessionError as e: log.sessions.exception("Error while deleting session!") raise cmdutils.CommandError("Error while deleting session: {}" .format(e)) else: log.sessions.debug("Loaded & deleted session {}.".format(name)) @cmdutils.register() @cmdutils.argument('name', completion=miscmodels.session) @cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('with_private', flag='p') def session_save(name: ArgType = default, *, current: bool = False, quiet: bool = False, force: bool = False, only_active_window: bool = False, with_private: bool = False, win_id: int = None) -> None: """Save a session. Args: name: The name of the session. If not given, the session configured in session.default_name is saved. current: Save the current session instead of the default. quiet: Don't show confirmation message. force: Force saving internal sessions (starting with an underline). only_active_window: Saves only tabs of the currently active window. with_private: Include private windows. """ if not isinstance(name, Sentinel) and name.startswith('_') and not force: raise cmdutils.CommandError("{} is an internal session, use --force " "to save anyways.".format(name)) if current: if session_manager.current is None: raise cmdutils.CommandError("No session loaded currently!") name = session_manager.current assert not name.startswith('_') try: if only_active_window: name = session_manager.save(name, only_window=win_id, with_private=True) else: name = session_manager.save(name, with_private=with_private) except SessionError as e: raise cmdutils.CommandError("Error while saving session: {}".format(e)) else: if quiet: log.sessions.debug("Saved session {}.".format(name)) else: message.info("Saved session {}.".format(name)) @cmdutils.register() @cmdutils.argument('name', completion=miscmodels.session) def session_delete(name, *, force: bool = False) -> None: """Delete a session. Args: name: The name of the session. force: Force deleting internal sessions (starting with an underline). """ if name.startswith('_') and not force: raise cmdutils.CommandError("{} is an internal session, use --force " "to delete anyways.".format(name)) try: session_manager.delete(name) except SessionNotFoundError: raise cmdutils.CommandError("Session {} not found!".format(name)) except SessionError as e: log.sessions.exception("Error while deleting session!") raise cmdutils.CommandError("Error while deleting session: {}" .format(e)) else: log.sessions.debug("Deleted session {}.".format(name)) def load_default(name): """Load the default session. Args: name: The name of the session to load, or None to read state file. """ if name is None and session_manager.exists('_autosave'): name = '_autosave' elif name is None: try: name = configfiles.state['general']['session'] except KeyError: # No session given as argument and none in the session file -> # start without loading a session return try: session_manager.load(name) except SessionNotFoundError: message.error("Session {} not found!".format(name)) except SessionError as e: message.error("Failed to load session {}: {}".format(name, e)) try: del configfiles.state['general']['session'] except KeyError: pass # If this was a _restart session, delete it. if name == '_restart': session_manager.delete('_restart') ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/split.py0000644000175000017510000001471700000000000022320 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Our own fork of shlex.split with some added and removed features.""" import re from qutebrowser.utils import log, utils class ShellLexer: """A lexical analyzer class for simple shell-like syntaxes. Based on Python's shlex, but cleaned up, removed some features, and added some features useful for qutebrowser. Attributes: FIXME """ def __init__(self, s): self.string = s self.whitespace = ' \t\r' self.quotes = '\'"' self.escape = '\\' self.escapedquotes = '"' self.keep = False self.quoted = False self.escapedstate = ' ' self.token = '' self.state = ' ' def reset(self): """Reset the state machine state to the defaults.""" self.quoted = False self.escapedstate = ' ' self.token = '' self.state = ' ' def __iter__(self): # noqa: C901 pragma: no mccabe """Read a raw token from the input stream.""" self.reset() for nextchar in self.string: if self.state == ' ': if self.keep: self.token += nextchar if nextchar in self.whitespace: if self.token or self.quoted: yield self.token self.reset() elif nextchar in self.escape: self.escapedstate = 'a' self.state = nextchar elif nextchar in self.quotes: self.state = nextchar else: self.token = nextchar self.state = 'a' elif self.state in self.quotes: self.quoted = True if nextchar == self.state: if self.keep: self.token += nextchar self.state = 'a' elif (nextchar in self.escape and self.state in self.escapedquotes): if self.keep: self.token += nextchar self.escapedstate = self.state self.state = nextchar else: self.token += nextchar elif self.state in self.escape: # In posix shells, only the quote itself or the escape # character may be escaped within quotes. if (self.escapedstate in self.quotes and nextchar != self.state and nextchar != self.escapedstate and not self.keep): self.token += self.state self.token += nextchar self.state = self.escapedstate elif self.state == 'a': if nextchar in self.whitespace: self.state = ' ' assert self.token or self.quoted yield self.token self.reset() if self.keep: yield nextchar elif nextchar in self.quotes: if self.keep: self.token += nextchar self.state = nextchar elif nextchar in self.escape: if self.keep: self.token += nextchar self.escapedstate = 'a' self.state = nextchar else: self.token += nextchar else: raise utils.Unreachable( "Invalid state {!r}!".format(self.state)) if self.state in self.escape and not self.keep: self.token += self.state if self.token or self.quoted: yield self.token def split(s, keep=False): """Split a string via ShellLexer. Args: keep: Whether to keep special chars in the split output. """ lexer = ShellLexer(s) lexer.keep = keep tokens = list(lexer) if not tokens: return [] out = [] spaces = "" log.shlexer.vdebug("{!r} -> {!r}".format(s, tokens)) # type: ignore for t in tokens: if t.isspace(): spaces += t else: out.append(spaces + t) spaces = "" if spaces: out.append(spaces) return out def _combine_ws(parts, whitespace): """Combine whitespace in a list with the element following it. Args: parts: A list of strings. whitespace: A string containing what's considered whitespace. Return: The modified list. """ out = [] ws = '' for part in parts: if not part: continue elif part in whitespace: ws += part else: out.append(ws + part) ws = '' if ws: out.append(ws) return out def simple_split(s, keep=False, maxsplit=None): """Split a string on whitespace, optionally keeping the whitespace. Args: s: The string to split. keep: Whether to keep whitespace. maxsplit: The maximum count of splits. Return: A list of split strings. """ whitespace = '\n\t ' if maxsplit == 0: # re.split with maxsplit=0 splits everything, while str.split splits # nothing (which is the behavior we want). if keep: return [s] else: return [s.strip(whitespace)] elif maxsplit is None: maxsplit = 0 if keep: pattern = '([' + whitespace + '])' parts = re.split(pattern, s, maxsplit) return _combine_ws(parts, whitespace) else: pattern = '[' + whitespace + ']' parts = re.split(pattern, s, maxsplit) parts[-1] = parts[-1].rstrip() return [p for p in parts if p] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/sql.py0000644000175000017510000003122700000000000021757 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2016-2020 Ryan Roden-Corrent (rcorre) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Provides access to an in-memory sqlite database.""" import collections from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtSql import QSqlDatabase, QSqlQuery, QSqlError from qutebrowser.utils import log, debug class SqliteErrorCode: """Error codes as used by sqlite. See https://sqlite.org/rescode.html - note we only define the codes we use in qutebrowser here. """ UNKNOWN = '-1' ERROR = '1' # generic error code BUSY = '5' # database is locked READONLY = '8' # attempt to write a readonly database IOERR = '10' # disk I/O error CORRUPT = '11' # database disk image is malformed FULL = '13' # database or disk is full CANTOPEN = '14' # unable to open database file PROTOCOL = '15' # locking protocol error CONSTRAINT = '19' # UNIQUE constraint failed NOTADB = '26' # file is not a database class Error(Exception): """Base class for all SQL related errors.""" def __init__(self, msg, error=None): super().__init__(msg) self.error = error def text(self): """Get a short text description of the error. This is a string suitable to show to the user as error message. """ if self.error is None: return str(self) else: return self.error.databaseText() class KnownError(Error): """Raised on an error interacting with the SQL database. This is raised in conditions resulting from the environment (like a full disk or I/O errors), where qutebrowser isn't to blame. """ class BugError(Error): """Raised on an error interacting with the SQL database. This is raised for errors resulting from a qutebrowser bug. """ def raise_sqlite_error(msg, error): """Raise either a BugError or KnownError.""" error_code = error.nativeErrorCode() database_text = error.databaseText() driver_text = error.driverText() log.sql.debug("SQL error:") log.sql.debug("type: {}".format( debug.qenum_key(QSqlError, error.type()))) log.sql.debug("database text: {}".format(database_text)) log.sql.debug("driver text: {}".format(driver_text)) log.sql.debug("error code: {}".format(error_code)) known_errors = [ SqliteErrorCode.BUSY, SqliteErrorCode.READONLY, SqliteErrorCode.IOERR, SqliteErrorCode.CORRUPT, SqliteErrorCode.FULL, SqliteErrorCode.CANTOPEN, SqliteErrorCode.PROTOCOL, SqliteErrorCode.NOTADB, ] # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-70506 # We don't know what the actual error was, but let's assume it's not us to # blame... Usually this is something like an unreadable database file. qtbug_70506 = (error_code == SqliteErrorCode.UNKNOWN and driver_text == "Error opening database" and database_text == "out of memory") # https://github.com/qutebrowser/qutebrowser/issues/4681 # If the query we built was too long too_long_err = ( error_code == SqliteErrorCode.ERROR and (database_text.startswith("Expression tree is too large") or database_text in ["too many SQL variables", "LIKE or GLOB pattern too complex"])) if error_code in known_errors or qtbug_70506 or too_long_err: raise KnownError(msg, error) raise BugError(msg, error) def init(db_path): """Initialize the SQL database connection.""" database = QSqlDatabase.addDatabase('QSQLITE') if not database.isValid(): raise KnownError('Failed to add database. Are sqlite and Qt sqlite ' 'support installed?') database.setDatabaseName(db_path) if not database.open(): error = database.lastError() msg = "Failed to open sqlite database at {}: {}".format(db_path, error.text()) raise_sqlite_error(msg, error) # Enable write-ahead-logging and reduce disk write frequency # see https://sqlite.org/pragma.html and issues #2930 and #3507 Query("PRAGMA journal_mode=WAL").run() Query("PRAGMA synchronous=NORMAL").run() def close(): """Close the SQL connection.""" QSqlDatabase.removeDatabase(QSqlDatabase.database().connectionName()) def version(): """Return the sqlite version string.""" try: if not QSqlDatabase.database().isOpen(): init(':memory:') ver = Query("select sqlite_version()").run().value() close() return ver return Query("select sqlite_version()").run().value() except KnownError as e: return 'UNAVAILABLE ({})'.format(e) class Query: """A prepared SQL query.""" def __init__(self, querystr, forward_only=True): """Prepare a new SQL query. Args: querystr: String to prepare query from. forward_only: Optimization for queries that will only step forward. Must be false for completion queries. """ self.query = QSqlQuery(QSqlDatabase.database()) log.sql.debug('Preparing SQL query: "{}"'.format(querystr)) ok = self.query.prepare(querystr) self._check_ok('prepare', ok) self.query.setForwardOnly(forward_only) def __iter__(self): if not self.query.isActive(): raise BugError("Cannot iterate inactive query") rec = self.query.record() fields = [rec.fieldName(i) for i in range(rec.count())] rowtype = collections.namedtuple('ResultRow', fields) # type: ignore while self.query.next(): rec = self.query.record() yield rowtype(*[rec.value(i) for i in range(rec.count())]) def _check_ok(self, step, ok): if not ok: query = self.query.lastQuery() error = self.query.lastError() msg = 'Failed to {} query "{}": "{}"'.format(step, query, error.text()) raise_sqlite_error(msg, error) def _bind_values(self, values): for key, val in values.items(): self.query.bindValue(':{}'.format(key), val) if any(val is None for val in self.bound_values().values()): raise BugError("Missing bound values!") def run(self, **values): """Execute the prepared query.""" log.sql.debug('Running SQL query: "{}"'.format( self.query.lastQuery())) self._bind_values(values) log.sql.debug('query bindings: {}'.format(self.bound_values())) ok = self.query.exec_() self._check_ok('exec', ok) return self def run_batch(self, values): """Execute the query in batch mode.""" log.sql.debug('Running SQL query (batch): "{}"'.format( self.query.lastQuery())) self._bind_values(values) db = QSqlDatabase.database() ok = db.transaction() self._check_ok('transaction', ok) ok = self.query.execBatch() try: self._check_ok('execBatch', ok) except Error: # Not checking the return value here, as we're failing anyways... db.rollback() raise ok = db.commit() self._check_ok('commit', ok) def value(self): """Return the result of a single-value query (e.g. an EXISTS).""" if not self.query.next(): raise BugError("No result for single-result query") return self.query.record().value(0) def rows_affected(self): return self.query.numRowsAffected() def bound_values(self): return self.query.boundValues() class SqlTable(QObject): """Interface to a SQL table. Attributes: _name: Name of the SQL table this wraps. Signals: changed: Emitted when the table is modified. """ changed = pyqtSignal() def __init__(self, name, fields, constraints=None, parent=None): """Create a new table in the SQL database. Does nothing if the table already exists. Args: name: Name of the table. fields: A list of field names. constraints: A dict mapping field names to constraint strings. """ super().__init__(parent) self._name = name constraints = constraints or {} column_defs = ['{} {}'.format(field, constraints.get(field, '')) for field in fields] q = Query("CREATE TABLE IF NOT EXISTS {name} ({column_defs})" .format(name=name, column_defs=', '.join(column_defs))) q.run() def create_index(self, name, field): """Create an index over this table. Args: name: Name of the index, should be unique. field: Name of the field to index. """ q = Query("CREATE INDEX IF NOT EXISTS {name} ON {table} ({field})" .format(name=name, table=self._name, field=field)) q.run() def __iter__(self): """Iterate rows in the table.""" q = Query("SELECT * FROM {table}".format(table=self._name)) q.run() return iter(q) def contains_query(self, field): """Return a prepared query that checks for the existence of an item. Args: field: Field to match. """ return Query( "SELECT EXISTS(SELECT * FROM {table} WHERE {field} = :val)" .format(table=self._name, field=field)) def __len__(self): """Return the count of rows in the table.""" q = Query("SELECT count(*) FROM {table}".format(table=self._name)) q.run() return q.value() def delete(self, field, value): """Remove all rows for which `field` equals `value`. Args: field: Field to use as the key. value: Key value to delete. Return: The number of rows deleted. """ q = Query("DELETE FROM {table} where {field} = :val" .format(table=self._name, field=field)) q.run(val=value) if not q.rows_affected(): raise KeyError('No row with {} = "{}"'.format(field, value)) self.changed.emit() def _insert_query(self, values, replace): params = ', '.join(':{}'.format(key) for key in values) verb = "REPLACE" if replace else "INSERT" return Query("{verb} INTO {table} ({columns}) values({params})".format( verb=verb, table=self._name, columns=', '.join(values), params=params)) def insert(self, values, replace=False): """Append a row to the table. Args: values: A dict with a value to insert for each field name. replace: If set, replace existing values. """ q = self._insert_query(values, replace) q.run(**values) self.changed.emit() def insert_batch(self, values, replace=False): """Performantly append multiple rows to the table. Args: values: A dict with a list of values to insert for each field name. replace: If true, overwrite rows with a primary key match. """ q = self._insert_query(values, replace) q.run_batch(values) self.changed.emit() def delete_all(self): """Remove all rows from the table.""" Query("DELETE FROM {table}".format(table=self._name)).run() self.changed.emit() def select(self, sort_by, sort_order, limit=-1): """Prepare, run, and return a select statement on this table. Args: sort_by: name of column to sort by. sort_order: 'asc' or 'desc'. limit: max number of rows in result, defaults to -1 (unlimited). Return: A prepared and executed select query. """ q = Query("SELECT * FROM {table} ORDER BY {sort_by} {sort_order} " "LIMIT :limit" .format(table=self._name, sort_by=sort_by, sort_order=sort_order)) q.run(limit=limit) return q ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/throttle.py0000644000175000017510000000713500000000000023026 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2018-2020 Jay Kamat # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """A throttle for throttling function calls.""" import typing import time import attr from PyQt5.QtCore import QObject from qutebrowser.utils import usertypes @attr.s class _CallArgs: args = attr.ib() # type: typing.Sequence[typing.Any] kwargs = attr.ib() # type: typing.Mapping[str, typing.Any] class Throttle(QObject): """A throttle to throttle calls. If a request comes in, it will be processed immediately. If another request comes in too soon, it is ignored, but will be processed when a timeout ends. If another request comes in, it will update the pending request. """ def __init__(self, func: typing.Callable, delay_ms: int, parent: QObject = None) -> None: """Constructor. Args: delay_ms: The time to wait before allowing another call of the function. -1 disables the wrapper. func: The function/method to call on __call__. parent: The parent object. """ super().__init__(parent) self._delay_ms = delay_ms self._func = func self._pending_call = None # type: typing.Optional[_CallArgs] self._last_call_ms = None # type: typing.Optional[int] self._timer = usertypes.Timer(self, 'throttle-timer') self._timer.setSingleShot(True) def _call_pending(self) -> None: """Start a pending call.""" assert self._pending_call is not None self._func(*self._pending_call.args, **self._pending_call.kwargs) self._pending_call = None self._last_call_ms = int(time.monotonic() * 1000) def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Any: cur_time_ms = int(time.monotonic() * 1000) if self._pending_call is None: if (self._last_call_ms is None or cur_time_ms - self._last_call_ms > self._delay_ms): # Call right now self._last_call_ms = cur_time_ms self._func(*args, **kwargs) return self._timer.setInterval(self._delay_ms - (cur_time_ms - self._last_call_ms)) # Disconnect any existing calls, continue if no connections. try: self._timer.timeout.disconnect() except TypeError: pass self._timer.timeout.connect(self._call_pending) self._timer.start() # Update arguments for an existing pending call self._pending_call = _CallArgs(args=args, kwargs=kwargs) def set_delay(self, delay_ms: int) -> None: """Set the delay to wait between invocation of this function.""" self._delay_ms = delay_ms def cancel(self) -> None: """Cancel any pending instance of this timer.""" self._timer.stop() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/misc/utilcmds.py0000644000175000017510000002324000000000000023000 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Misc. utility commands exposed to the user.""" # QApplication and objects are imported so they're usable in :debug-pyeval import functools import os import traceback from PyQt5.QtCore import QUrl from PyQt5.QtWidgets import QApplication from qutebrowser.browser import qutescheme from qutebrowser.utils import log, objreg, usertypes, message, debug, utils from qutebrowser.keyinput import modeman from qutebrowser.commands import runners from qutebrowser.api import cmdutils from qutebrowser.misc import ( # pylint: disable=unused-import consolewidget, debugcachestats, objects) from qutebrowser.utils.version import pastebin_version from qutebrowser.qt import sip @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('win_id', value=cmdutils.Value.win_id) def later(ms: int, command: str, win_id: int) -> None: """Execute a command after some time. Args: ms: How many milliseconds to wait. command: The command to run, with optional args. """ if ms < 0: raise cmdutils.CommandError("I can't run something in the past!") commandrunner = runners.CommandRunner(win_id) timer = usertypes.Timer(name='later', parent=QApplication.instance()) try: timer.setSingleShot(True) try: timer.setInterval(ms) except OverflowError: raise cmdutils.CommandError("Numeric argument is too large for " "internal int representation.") timer.timeout.connect( functools.partial(commandrunner.run_safely, command)) timer.timeout.connect(timer.deleteLater) timer.start() except: timer.deleteLater() raise @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('count', value=cmdutils.Value.count) def repeat(times: int, command: str, win_id: int, count: int = None) -> None: """Repeat a given command. Args: times: How many times to repeat. command: The command to run, with optional args. count: Multiplies with 'times' when given. """ if count is not None: times *= count if times < 0: raise cmdutils.CommandError("A negative count doesn't make sense.") commandrunner = runners.CommandRunner(win_id) for _ in range(times): commandrunner.run_safely(command) @cmdutils.register(maxsplit=1, no_cmd_split=True, no_replace_variables=True) @cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('count', value=cmdutils.Value.count) def run_with_count(count_arg: int, command: str, win_id: int, count: int = 1) -> None: """Run a command with the given count. If run_with_count itself is run with a count, it multiplies count_arg. Args: count_arg: The count to pass to the command. command: The command to run, with optional args. count: The count that run_with_count itself received. """ runners.CommandRunner(win_id).run(command, count_arg * count) @cmdutils.register() def clear_messages() -> None: """Clear all message notifications.""" message.global_bridge.clear_messages.emit() @cmdutils.register(debug=True) def debug_all_objects() -> None: """Print a list of all objects to the debug log.""" s = debug.get_all_objects() log.misc.debug(s) @cmdutils.register(debug=True) def debug_cache_stats() -> None: """Print LRU cache stats.""" debugcachestats.debug_cache_stats() @cmdutils.register(debug=True) def debug_console() -> None: """Show the debugging console.""" if consolewidget.console_widget is None: log.misc.debug('initializing debug console') consolewidget.init() assert consolewidget.console_widget is not None if consolewidget.console_widget.isVisible(): log.misc.debug('hiding debug console') consolewidget.console_widget.hide() else: log.misc.debug('showing debug console') consolewidget.console_widget.show() @cmdutils.register(maxsplit=0, debug=True, no_cmd_split=True) def debug_pyeval(s: str, file: bool = False, quiet: bool = False) -> None: """Evaluate a python string and display the results as a web page. Args: s: The string to evaluate. file: Interpret s as a path to file, also implies --quiet. quiet: Don't show the output in a new tab. """ if file: quiet = True path = os.path.expanduser(s) try: with open(path, 'r', encoding='utf-8') as f: s = f.read() except OSError as e: raise cmdutils.CommandError(str(e)) try: exec(s) out = "No error" except Exception: out = traceback.format_exc() else: try: r = eval(s) out = repr(r) except Exception: out = traceback.format_exc() qutescheme.pyeval_output = out if quiet: log.misc.debug("pyeval output: {}".format(out)) else: tabbed_browser = objreg.get('tabbed-browser', scope='window', window='last-focused') tabbed_browser.load_url(QUrl('qute://pyeval'), newtab=True) @cmdutils.register(debug=True) def debug_set_fake_clipboard(s: str = None) -> None: """Put data into the fake clipboard and enable logging, used for tests. Args: s: The text to put into the fake clipboard, or unset to enable logging. """ if s is None: utils.log_clipboard = True else: utils.fake_clipboard = s @cmdutils.register() @cmdutils.argument('win_id', value=cmdutils.Value.win_id) @cmdutils.argument('count', value=cmdutils.Value.count) def repeat_command(win_id: int, count: int = None) -> None: """Repeat the last executed command. Args: count: Which count to pass the command. """ mode_manager = modeman.instance(win_id) if mode_manager.mode not in runners.last_command: raise cmdutils.CommandError("You didn't do anything yet.") cmd = runners.last_command[mode_manager.mode] commandrunner = runners.CommandRunner(win_id) commandrunner.run(cmd[0], count if count is not None else cmd[1]) @cmdutils.register(debug=True, name='debug-log-capacity') def log_capacity(capacity: int) -> None: """Change the number of log lines to be stored in RAM. Args: capacity: Number of lines for the log. """ if capacity < 0: raise cmdutils.CommandError("Can't set a negative log capacity!") assert log.ram_handler is not None log.ram_handler.change_log_capacity(capacity) @cmdutils.register(debug=True) @cmdutils.argument('level', choices=sorted( (level.lower() for level in log.LOG_LEVELS), key=lambda e: log.LOG_LEVELS[e.upper()])) def debug_log_level(level: str) -> None: """Change the log level for console logging. Args: level: The log level to set. """ if log.console_handler is None: raise cmdutils.CommandError("No log.console_handler. Not attached " "to a console?") log.change_console_formatter(log.LOG_LEVELS[level.upper()]) log.console_handler.setLevel(log.LOG_LEVELS[level.upper()]) @cmdutils.register(debug=True) def debug_log_filter(filters: str) -> None: """Change the log filter for console logging. Args: filters: A comma separated list of logger names. Can also be "none" to clear any existing filters. """ if log.console_filter is None: raise cmdutils.CommandError("No log.console_filter. Not attached " "to a console?") if filters.strip().lower() == 'none': log.console_filter.names = None return if not set(filters.split(',')).issubset(log.LOGGER_NAMES): raise cmdutils.CommandError("filters: Invalid value {} - expected one " "of: {}".format( filters, ', '.join(log.LOGGER_NAMES))) log.console_filter.names = filters.split(',') @cmdutils.register() @cmdutils.argument('current_win_id', value=cmdutils.Value.win_id) def window_only(current_win_id: int) -> None: """Close all windows except for the current one.""" for win_id, window in objreg.window_registry.items(): # We could be in the middle of destroying a window here if sip.isdeleted(window): continue if win_id != current_win_id: window.close() @cmdutils.register() @cmdutils.argument('win_id', value=cmdutils.Value.win_id) def version(win_id: int, paste: bool = False) -> None: """Show version information. Args: paste: Paste to pastebin. """ tabbed_browser = objreg.get('tabbed-browser', scope='window', window=win_id) tabbed_browser.load_url(QUrl('qute://version/'), newtab=True) if paste: pastebin_version() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/qt.py0000644000175000017510000000201600000000000020643 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2018-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Wrappers around Qt/PyQt code.""" # pylint: disable=unused-import # PyQt 5.11 comes with a bundled sip, # for older PyQt versions it's a separate module. try: from PyQt5 import sip except ImportError: import sip # type: ignore ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/qutebrowser.py0000644000175000017510000002170200000000000022604 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Early initialization and main entry point. qutebrowser's initialization process roughly looks like this: - This file gets imported, either via the setuptools entry point or __main__.py. - At import time, we check for the correct Python version and show an error if it's too old. - The main() function in this file gets invoked - Argument parsing takes place - earlyinit.early_init() gets invoked to do various low-level initialization and checks whether all dependencies are met. - app.run() gets called, which takes over. See the docstring of app.py for details. """ import sys import json import qutebrowser try: from qutebrowser.misc.checkpyver import check_python_version except ImportError: try: # python2 from .misc.checkpyver import check_python_version except (SystemError, ValueError): # Import without module - SystemError on Python3, ValueError (?!?) on # Python2 sys.stderr.write("Please don't run this script directly, do something " "like python3 -m qutebrowser instead.\n") sys.stderr.flush() sys.exit(100) check_python_version() import argparse # pylint: disable=wrong-import-order from qutebrowser.misc import earlyinit def get_argparser(): """Get the argparse parser.""" parser = argparse.ArgumentParser(prog='qutebrowser', description=qutebrowser.__description__) parser.add_argument('-B', '--basedir', help="Base directory for all " "storage.") parser.add_argument('-C', '--config-py', help="Path to config.py.", metavar='CONFIG') parser.add_argument('-V', '--version', help="Show version and quit.", action='store_true') parser.add_argument('-s', '--set', help="Set a temporary setting for " "this session.", nargs=2, action='append', dest='temp_settings', default=[], metavar=('OPTION', 'VALUE')) parser.add_argument('-r', '--restore', help="Restore a named session.", dest='session') parser.add_argument('-R', '--override-restore', help="Don't restore a " "session even if one would be restored.", action='store_true') parser.add_argument('--target', choices=['auto', 'tab', 'tab-bg', 'tab-silent', 'tab-bg-silent', 'window'], help="How URLs should be opened if there is already a " "qutebrowser instance running.") parser.add_argument('--backend', choices=['webkit', 'webengine'], help="Which backend to use.") parser.add_argument('--enable-webengine-inspector', action='store_true', help="Enable the web inspector for QtWebEngine. Note " "that this is a SECURITY RISK and you should not " "visit untrusted websites with the inspector turned " "on. See https://bugreports.qt.io/browse/QTBUG-50725 " "for more details. This is not needed anymore since " "Qt 5.11 where the inspector is always enabled and " "secure.") parser.add_argument('--json-args', help=argparse.SUPPRESS) parser.add_argument('--temp-basedir-restarted', help=argparse.SUPPRESS) debug = parser.add_argument_group('debug arguments') debug.add_argument('-l', '--loglevel', dest='loglevel', help="Set loglevel", default='info', choices=['critical', 'error', 'warning', 'info', 'debug', 'vdebug']) debug.add_argument('--logfilter', type=logfilter_error, help="Comma-separated list of things to be logged " "to the debug log on stdout.") debug.add_argument('--loglines', help="How many lines of the debug log to keep in RAM " "(-1: unlimited).", default=2000, type=int) debug.add_argument('-d', '--debug', help="Turn on debugging options.", action='store_true') debug.add_argument('--json-logging', action='store_true', help="Output log" " lines in JSON format (one object per line).") debug.add_argument('--nocolor', help="Turn off colored logging.", action='store_false', dest='color') debug.add_argument('--force-color', help="Force colored logging", action='store_true') debug.add_argument('--nowindow', action='store_true', help="Don't show " "the main window.") debug.add_argument('-T', '--temp-basedir', action='store_true', help="Use " "a temporary basedir.") debug.add_argument('--no-err-windows', action='store_true', help="Don't " "show any error windows (used for tests/smoke.py).") debug.add_argument('--qt-arg', help="Pass an argument with a value to Qt. " "For example, you can do " "`--qt-arg geometry 650x555+200+300` to set the window " "geometry.", nargs=2, metavar=('NAME', 'VALUE'), action='append') debug.add_argument('--qt-flag', help="Pass an argument to Qt as flag.", nargs=1, action='append') debug.add_argument('-D', '--debug-flag', type=debug_flag_error, default=[], help="Pass name of debugging feature to be" " turned on.", action='append', dest='debug_flags') parser.add_argument('command', nargs='*', help="Commands to execute on " "startup.", metavar=':command') # URLs will actually be in command parser.add_argument('url', nargs='*', help="URLs to open on startup " "(empty as a window separator).") return parser def directory(arg): if not arg: raise argparse.ArgumentTypeError("Invalid empty value") def logfilter_error(logfilter): """Validate logger names passed to --logfilter. Args: logfilter: A comma separated list of logger names. """ from qutebrowser.utils import log if set(logfilter.lstrip('!').split(',')).issubset(log.LOGGER_NAMES): return logfilter else: raise argparse.ArgumentTypeError( "filters: Invalid value {} - expected a list of: {}".format( logfilter, ', '.join(log.LOGGER_NAMES))) def debug_flag_error(flag): """Validate flags passed to --debug-flag. Available flags: debug-exit: Turn on debugging of late exit. pdb-postmortem: Drop into pdb on exceptions. no-sql-history: Don't store history items. no-scroll-filtering: Process all scrolling updates. log-requests: Log all network requests. log-scroll-pos: Log all scrolling changes. stack: Enable Chromium stack logging. chromium: Enable Chromium logging. werror: Turn Python warnings into errors. """ valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history', 'no-scroll-filtering', 'log-requests', 'lost-focusproxy', 'log-scroll-pos', 'stack', 'chromium', 'werror'] if flag in valid_flags: return flag else: raise argparse.ArgumentTypeError("Invalid debug flag - valid flags: {}" .format(', '.join(valid_flags))) def main(): parser = get_argparser() argv = sys.argv[1:] args = parser.parse_args(argv) if args.json_args is not None: # Restoring after a restart. # When restarting, we serialize the argparse namespace into json, and # construct a "fake" argparse.Namespace here based on the data loaded # from json. data = json.loads(args.json_args) args = argparse.Namespace(**data) earlyinit.early_init(args) # We do this imports late as earlyinit needs to be run first (because of # version checking and other early initialization) from qutebrowser import app return app.run(args) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/resources.py0000644000175000017510000127173600000000000022253 0ustar00florianflorian00000000000000# -*- coding: utf-8 -*- # Resource object code # # Created by: The Resource Compiler for PyQt5 (Qt v5.6.0) # # WARNING! All changes made in this file will be lost! from PyQt5 import QtCore qt_resource_data = b"\ \x00\x00\x22\xfe\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x80\x00\x00\x00\x80\x08\x06\x00\x00\x00\xc3\x3e\x61\xcb\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ \x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x20\x00\x49\x44\ \x41\x54\x78\x9c\xed\x9d\x77\x7c\x5c\xd5\x95\xf8\xbf\xf7\xcd\x8c\ \xa4\x51\xef\xdd\x72\xaf\x72\x93\x3b\xc6\x18\x1b\xdb\x31\xc5\x10\ \x4c\xc0\x21\x64\x03\x84\xb0\xc9\x26\x6c\xd8\x90\x6c\x60\x7f\xd9\ \x7c\x36\x24\xd9\x92\xfc\xb2\x69\x64\x61\x37\xc1\xa1\x98\xc0\x62\ \x02\x36\x60\x83\x0d\xb8\x60\x83\xbb\x2d\xc9\xd8\x72\xc1\x45\xb6\ \xac\x66\xd5\x51\x9d\xd1\x94\x77\xf6\x8f\xc1\x46\xb2\x66\x34\x33\ \x7a\x33\x92\x9c\xf0\xfd\x7c\xfc\xb1\xf4\xde\xbb\x45\x73\xcf\xdc\ \x7b\xde\xb9\xe7\x9c\xab\xf8\x0b\x43\x44\xd4\x8b\x1f\x31\x17\x97\ \xbe\xc8\xa9\x33\xc5\xa9\xab\x31\x5d\xba\xca\xd5\x3d\x12\xef\x11\ \x15\xed\x11\x2c\xba\x07\xcd\x03\xca\xe5\x41\x69\x80\xc9\x84\x98\ \x35\x11\x4d\xa1\x6b\x0a\x97\xd5\xac\xda\xe6\x14\xc8\x4e\x93\x26\ \x15\x08\xe7\x94\xc8\x59\xb7\xc5\x74\x7c\x56\x26\xe5\x4a\x29\x19\ \xec\xbf\x31\x9c\xa8\xc1\xee\x80\x51\x36\x1e\x91\x89\x8d\x5d\xfa\ \x7d\x0e\x8f\x5a\xd2\xde\xc5\xf8\x0e\xa7\x8a\x77\xeb\xc6\xff\xae\ \x09\x19\xc2\x84\xcc\x9e\x63\x2d\xd0\xa6\x90\x23\x22\xea\x80\x86\ \xec\x12\xdd\xb4\x6b\x46\x81\xaa\x36\xda\xd6\x60\x72\xd5\x09\xc0\ \x9a\xc3\x12\x87\x47\xff\x76\x47\x97\x76\x77\x7b\x17\x13\xda\x5d\ \x44\x4b\x84\xbe\x93\x37\x8c\xd1\x49\x8c\xee\xfb\x19\x81\x93\x9a\ \xb0\x49\xd7\xf4\xcd\x89\x1d\xe6\xf7\xc7\x8e\x55\x5d\x91\xe9\x4d\ \x64\xb8\x2a\x04\xe0\x95\xdd\x62\x75\x44\xeb\xdf\x6d\x77\x69\xf7\ \x36\x77\x32\xb6\xcb\x1d\xf9\x7e\x47\x9b\x61\xd9\x58\x1d\xb3\x16\ \x42\x21\x45\x0b\xf0\x86\xd2\xe5\x15\x4f\xad\xe9\xdd\x59\xb3\x94\ \x2b\x52\xfd\x0b\x17\x43\x5a\x00\xb6\x9c\x95\xa9\x95\xcd\xf2\xcb\ \x4a\x9b\xba\xc1\xe9\x21\x94\xa1\x30\xcc\xac\x7c\x21\x3f\xc9\xd0\ \xd4\x72\x11\x58\xab\x69\xda\x1f\xa6\xe7\xa8\xb2\x30\x75\x2b\xec\ \x0c\x39\x01\x78\xfc\x71\xd1\x72\x6f\xe4\xb1\x96\x2e\x1e\x6d\xef\ \x22\x79\x20\x34\x2e\x93\x82\x11\xa9\xc2\xc8\x54\xa1\xa4\x5a\xc3\ \x66\x87\xcf\x8d\xd5\x89\x36\x87\xab\x05\xd9\x25\xf0\xf3\x19\xb9\ \xa6\x8d\x43\x4d\x89\x1c\x32\x02\xf0\x78\x99\x44\xe5\x74\xf0\x0b\ \x5b\x27\x5f\xef\x70\x12\x13\x8e\x3a\x2d\x1a\xc4\x45\x83\x08\xb4\ \x3b\xc1\xa3\x43\x42\x34\x98\xba\xcd\x25\x69\xb1\xc2\xa8\x34\x21\ \xd6\x02\x27\xeb\x14\x27\x1b\x14\xf3\x0a\x84\xac\xf8\x88\x8c\x53\ \xb1\x42\x7e\x32\x3d\xd7\xf4\xe6\x50\x11\x84\xc1\x17\x00\x11\xf5\ \x3f\x7b\xf5\xff\x6c\xb2\xab\xbf\x77\xb8\x55\x54\xb8\xaa\xcd\x49\ \x14\x66\xe5\xc9\xe5\xc1\x6e\xeb\x82\x03\x95\x1a\xf3\x86\xe9\xc4\ \x5e\xd1\x8a\x08\x1c\xae\x55\x9c\x6b\xf2\x7e\x1c\x63\xd3\x84\xac\ \x04\x21\x35\x16\xb4\x88\x7c\x42\x72\x50\x94\xfc\x70\x66\xae\xe5\ \x9d\x48\xd4\x1e\x0a\x83\x2a\x00\xcf\xee\x97\x3b\x1a\xec\xac\x6e\ \xeb\x22\x25\xdc\x75\x27\xc6\x40\x7c\x94\x20\xc0\xc4\x0c\x21\x31\ \x06\x6a\x5a\x15\x26\x0d\x32\xbb\x7d\xbb\x05\x28\xa9\x52\x54\xd8\ \x7a\x7f\x14\x23\x53\x84\x69\xb9\x91\xfc\xa2\xaa\x2d\x26\x93\x7a\ \x64\x5a\xb6\x3a\x1a\xc1\x46\xfa\xee\xc1\x60\x34\xfa\xdb\x0f\xa5\ \x40\x53\xb2\xb1\xa1\x53\x4d\x89\xd4\x2b\xdc\x25\xac\x16\x58\x34\ \xca\xff\x7a\x7e\xa2\x5e\x71\xa2\xce\xf7\xc7\x30\x31\x53\x18\x9f\ \x11\xe1\x0e\x2a\x5c\x4a\xf8\xb5\x5d\xd7\x1e\x9f\x3f\x4c\xd9\x23\ \xdb\x58\x6f\x06\x54\xb3\x06\x28\xae\x72\x2d\xf5\x08\x27\xeb\x3b\ \x22\x3f\xf8\x26\x05\xd7\x0c\xf7\x3f\xf8\x3a\xde\xe9\x3f\x3f\x49\ \xc8\x49\x14\xaf\x7e\xd0\x4d\x16\xba\xdc\x91\xed\x1f\x00\x82\x45\ \xe0\xd1\x18\x4d\xff\xe8\x50\xa5\xeb\x86\x01\x68\xb1\x07\x61\xd3\ \x73\x03\xb1\xbd\x5c\x62\x92\xa2\xf4\xc7\x75\xe1\xfb\x6a\x80\x04\ \x2f\x39\x96\x3e\x0d\x39\x1a\xde\x6f\xf9\xa7\x78\x97\x0c\x9b\x1d\ \x8a\xab\xb4\x1e\xca\xe2\x00\x30\x46\x29\x6d\x4b\x49\xa5\xe7\x69\ \x8f\xa6\x3d\x32\x2b\x57\x75\x0e\x44\xa3\x03\xb2\x04\xbc\x50\x2c\ \xb7\x35\x77\xca\x8b\x0e\x8f\x8a\xb7\x3b\xc1\x23\x5e\xe5\x4a\x8f\ \xf0\x0c\x60\x36\xc1\x92\xd1\x9f\xcc\x00\x0a\x3a\x9d\xb0\xaf\x42\ \x63\x6a\x8e\x90\x11\x37\x24\x94\x70\xdf\x28\x8e\x68\x4a\xfb\xd2\ \x40\xd8\x0f\x22\x2e\x00\xff\x7b\x58\xfe\x4d\x53\xf2\x8f\x29\x56\ \x89\x2a\x6f\x52\x54\xb5\x28\xa6\x64\x7b\x5f\xbd\x5a\xed\xd0\xee\ \x52\x34\x77\xc2\xe9\xc6\xc8\x74\xc5\xa2\x81\x28\x88\xb7\x40\xa7\ \x0b\x32\xe2\x84\xe9\xb9\x82\xc5\x14\x91\xe6\xc2\x89\x5d\x89\x7c\ \xb3\x28\xdf\xfc\x7c\x24\x1b\x89\xd8\x12\xf0\xc4\x29\x89\xb6\x34\ \xb2\x2b\x25\x46\x66\x26\xc5\x08\xc7\x2e\x2a\xaa\x5b\xbd\x83\x2c\ \x78\x25\x2f\xd1\x0a\x66\x93\x60\x56\x8a\xd3\x8d\xc6\xda\x53\x40\ \x6a\x1c\xa4\xc6\x08\xe7\x6d\x0a\xa7\xc7\x7b\xdd\xa3\x43\x6a\xac\ \x90\x99\x00\x79\x89\x42\x5c\xd8\x5e\x34\x23\x8e\x55\x94\x7a\xae\ \xb8\xda\x33\xa5\x28\x47\x7b\x54\x29\xa5\x47\xa2\x91\x88\x7c\xed\ \x9e\xfa\x40\x52\xba\x90\xb2\x9c\x44\x72\x86\x27\x0b\x3b\xce\x6a\ \x74\x76\xb3\x8a\x5f\xda\x64\x29\xab\x55\x9c\x32\xf8\xcd\x8f\x36\ \x7b\x07\x76\x44\x8a\xf7\x55\xef\x50\x95\xe2\x82\x4d\x91\x1e\x07\ \xa3\x52\x85\xcc\x38\xc1\x1c\xe1\x6f\xbb\xae\x0b\x95\xb5\xf5\x14\ \xe4\x66\x46\xa4\x7e\x85\x5a\x6f\x73\xaa\x7b\x16\x8f\x54\x8e\xf0\ \xd7\x1d\x66\x9e\xde\x27\x23\x6d\x76\x4a\xd2\xe3\x24\x69\x5a\xae\ \x50\x52\xa5\x38\x7f\xc5\x3b\x76\x51\xae\x60\xb5\xc0\x9e\xf3\x8a\ \x4b\x2b\x71\x76\xbc\x30\x2e\x53\x88\x36\x43\x79\xa3\x0a\xb8\x24\ \xe4\x26\x7a\x97\x91\x34\x2b\xa8\x6e\x8f\x96\x5d\x54\x24\x44\xc3\ \xb0\x64\x19\x10\x05\xe7\xe3\xb3\x95\xfc\x6a\xf5\x6b\x4c\x1a\x5b\ \xc0\xc3\x5f\x5d\x19\xb1\x76\x44\xa9\xed\xae\x68\x75\xfb\xbc\x34\ \xd5\x1a\xce\x7a\xc3\xba\x04\x3c\xbb\x5f\x66\x5f\x6c\xe7\x03\xb7\ \x10\x5d\x98\x25\xb8\x75\xa8\x6c\xe9\x3d\x0c\xa5\x35\x9f\x2e\x05\ \x97\xa8\x6d\x57\x34\x74\x2a\x34\xc5\xe5\xe9\xdb\x1f\x33\xf3\x84\ \x61\xdd\x76\x09\x3a\x3a\xed\xe8\x22\xb4\xb5\xdb\x49\x46\x70\xb7\ \x7b\xa8\x75\x5a\x88\x8f\x8b\x21\x3a\x3a\x8a\x28\x73\xf8\x57\xba\ \xd6\xb6\x0e\xfe\xe7\xa5\xb7\xd8\xb8\x75\x2f\xba\x2e\x4c\x1a\x5b\ \x10\xf6\x36\xba\xa3\x44\x16\x47\x39\x64\xdb\xe1\x5a\xb9\x79\x5a\ \xb6\xaa\x0b\x57\xbd\x61\xfb\x64\x5e\x3a\x28\x0b\xce\xb7\xca\x76\ \xbb\x5b\x99\xf3\x92\xbc\x26\xd8\xe2\x6a\x85\xa7\xdb\x28\x9b\x14\ \xa4\xc7\x0b\xf1\x51\x90\x1e\xeb\x1d\xe8\x23\xb5\x0a\xf7\x27\xab\ \x9b\xfb\x8a\x55\x4e\x44\xc7\xd6\xdc\x44\x53\x7d\x2d\x4d\xf5\x17\ \x69\x6a\xaa\xc7\xde\xd6\xc2\xfa\x2e\x1b\xcd\x2d\x6d\xb4\xb4\x75\ \xe0\xf1\x04\x5e\x1a\x35\x4d\x11\x67\xb5\x92\x10\x6f\x25\x3b\x23\ \x95\x9c\xcc\x54\xee\xbe\x75\x11\x23\x87\x65\x87\xfc\x77\xea\xba\ \xf0\xe6\x96\x3d\xfc\xe1\xa5\xb7\x68\x6d\x1f\x90\x37\xb5\xee\xcc\ \xf4\x78\xf4\x0f\x4b\x6a\xe4\x73\x45\x39\xea\x5c\x38\x2a\x0c\x8b\ \x00\xac\x2e\x95\xa2\xf3\xcd\x6c\xb3\xbb\x95\x19\xa0\xae\x4d\xb1\ \xfd\xac\xa2\xf5\x8a\x15\x2b\x3e\x06\x26\x65\x08\x2d\x5d\x8a\x33\ \x4d\xd0\xd8\xa9\xe8\x6e\x0c\x72\xd8\x3b\xa9\x3c\x7f\x86\xea\xca\ \x73\xd4\x5c\x38\x47\x6d\x75\x05\x2e\xa7\xd3\x70\xff\x74\x5d\x68\ \xeb\xe8\xa4\xad\xa3\x93\xea\x8b\x5e\x6d\xf3\xda\x59\x93\x43\x16\ \x80\xb2\x53\xe7\xf8\xe5\xd3\xaf\xf1\xf1\xd9\x4a\xc3\x7d\x32\xc0\ \x58\x11\x7d\xcb\xfe\x3a\x59\x30\x27\x53\xd5\x1a\xad\xcc\xb0\x00\ \x3c\x75\x40\xc6\xd7\x35\xc9\x5e\x87\x5b\x59\x2e\x5d\x73\xe9\xe0\ \xf2\xa1\xae\xb4\xd8\x61\xfb\xd9\x9e\xd6\x95\xa6\x86\x8b\x9c\x39\ \x79\x94\xb3\x27\xcb\xa8\xba\x50\x8e\xae\x47\x44\xd9\x35\x44\x73\ \x4b\x1b\xff\xfd\xa7\x8d\x6c\x7a\xff\x00\x12\x69\xf3\x65\x30\x08\ \xa3\xcd\x2e\xfd\x9d\x92\x72\xb9\xbe\x68\xa4\xb2\x19\xa9\xca\x90\ \x00\xac\xde\x2d\xa9\x4d\x76\xf6\x87\xba\x8b\xe7\xec\x72\x70\xe2\ \x68\x31\x65\xa5\x07\xa8\xaa\x38\x6b\xa4\x0b\x11\x45\xd7\x85\x77\ \x3f\x38\xc4\xef\x9e\x7f\x9d\x96\xd6\x8e\xc1\xee\xce\x95\x4c\xd5\ \xa3\x65\xdd\xa9\x53\x72\x93\x11\x37\xb4\x7e\x0b\xc0\xef\x0f\x8a\ \xa5\xdd\x2e\x47\xda\x1d\x2a\x31\xd8\x32\xcd\x4d\x0d\xec\xdb\xf9\ \x2e\x27\x8e\x16\xe3\x76\x0d\x6d\x6f\xa9\x8f\x8e\x9f\xe5\x57\xab\ \xd7\x71\xfa\x7c\xd5\x60\x77\xc5\x2f\x4a\x64\x71\x6b\x2c\x2f\xbf\ \x22\x72\xe7\x2a\xa5\x02\xa8\xce\xbe\xe9\xb7\x00\x78\xba\xe4\x40\ \x93\x5d\xe5\x06\xf3\x6c\x8b\xad\x89\x7d\x3b\xdf\xe3\x68\xc9\xde\ \x21\x39\xc5\x77\xa7\xa9\xb9\x95\x27\xff\xb4\x81\x77\x77\x1e\x1a\ \x1a\xd3\x7d\x00\x14\x72\x7b\xeb\x01\xb6\x01\xd7\xf7\xa7\x7c\xbf\ \x04\x60\xf5\x7e\xcf\x53\x15\x36\x35\x2d\xd0\x73\x2e\x97\x93\x5d\ \xdb\xde\xa6\x78\xef\x8e\x21\x3f\xf0\x6e\x8f\x87\xd7\x36\x7d\xc8\ \x1f\x5f\xd9\x4c\x67\x67\xd8\xed\x2d\x91\x45\x58\xf8\xcc\x41\xcf\ \xbf\x3f\x30\xcb\xf4\x83\x50\x8b\x86\x2c\x00\x2f\x95\xc8\xed\xa7\ \x1b\xe4\x9b\x81\x9e\x3b\x7f\xe6\x24\xef\x6d\x58\x8b\xad\xd9\xa0\ \x8d\x77\x00\xb8\x50\x53\xcf\x0f\xfe\xff\x33\x94\x5f\x30\xac\x54\ \x0f\x0a\x49\x56\x38\x51\xa7\xfd\xd3\x73\x25\xf2\xd6\xfd\x45\x6a\ \x57\x28\x65\x43\x12\x80\xff\xda\x27\x69\x17\x6c\xf2\xb2\x5b\xfc\ \xdb\xd8\x74\x8f\x87\x6d\x9b\xd7\x51\x7a\x60\x17\x5c\x05\x53\x28\ \x40\xf9\x85\x8b\x57\xed\xe0\x03\x64\x27\x0a\x4e\x0f\x2a\xd9\x2a\ \xab\x0f\x1e\x94\xa9\xa1\xb8\xa3\x87\x24\x00\xca\x23\xdb\x3b\x5c\ \xca\xef\x0e\xbb\xa3\xb3\x83\x37\xd6\x3e\xcb\x85\x73\xa7\x42\xa9\ \xf6\x33\x0c\x12\x67\xb9\xec\xd7\x30\x81\x04\xfd\xa7\xc0\x3f\x05\ \x5b\x36\x68\x97\x87\xe7\x8b\x3d\xdf\xad\xeb\x50\x53\xfc\xdd\x6f\ \xac\xaf\xe5\x4f\x7f\xf8\xe5\x67\x83\x3f\xf8\x3c\xba\xe5\x63\x59\ \x11\xec\xc3\x41\x09\xc0\x4b\x07\x25\xbd\xa6\x55\xfb\x99\xbf\x19\ \xbd\xb9\xb1\x9e\xb5\xcf\xfd\xd7\x55\xb1\xde\xff\xa5\xe3\xf2\xa0\ \x0e\xd7\xf2\xca\x1f\x3f\x94\x84\x60\x9e\x0f\x4a\x00\x5a\xdc\xb2\ \xa9\xd3\x89\xc5\xd7\xbd\xb6\x56\x1b\x7f\x5e\xf3\x14\x9d\xed\x6d\ \xa1\xf4\xf3\x33\x22\xc4\xb1\x3a\x45\x8b\x03\xab\xd3\xc4\x6b\xc1\ \x3c\x1f\x50\x00\x5e\x2c\x95\xe5\x17\xdb\xd4\x2c\x5f\xf7\xec\x9d\ \xed\xbc\xf2\xdc\x93\xb4\xda\x9a\x42\xed\xe7\x67\x84\x19\x9b\x03\ \xf6\x55\x28\xca\x3f\x89\x6d\xb8\xd8\xce\xb2\x67\x8a\x25\xa0\x6d\ \x20\xa0\x12\xd8\xd8\x2e\x6b\x74\x1f\x5a\xbf\x88\xce\x9b\xaf\x3c\ \x4b\x73\x63\xd8\x76\x26\x03\x92\x92\x94\x80\x35\x26\x8a\x96\xb6\ \x4e\x3a\x3a\x07\xdc\x83\x7a\x48\x22\x02\xa5\xd5\xbd\x7d\x2e\x74\ \x01\x5b\x3b\x2f\x03\x39\x7d\x95\xef\x53\x00\x9e\x2b\xf1\xfc\xbf\ \xb3\xf5\xca\xa7\x9b\xcb\x9e\x1d\xef\x70\xa1\xfc\x74\xa8\xfd\x0d\ \x9a\x28\x8b\x85\x59\x53\xc7\x32\xaf\x68\x22\x85\x63\x87\x33\x72\ \x78\xce\xe5\x7d\xfd\x27\x9e\x5d\xcf\x2b\x6f\xed\x8c\x58\xdb\x57\ \x13\x67\x9a\x7a\x0f\xfe\x25\x6c\x0e\xb2\xd7\x1c\x92\x47\xee\x9d\ \xa9\x7e\xed\xaf\xbc\x5f\x01\x10\x11\xf5\xcb\x9d\xf2\xcf\xbe\xee\ \xd5\xd7\x56\xb3\x77\xe7\x7b\x21\x77\x36\x18\x72\xb3\xd2\xb8\xfb\ \xd6\x45\x2c\xbb\x6e\x06\x09\x71\xb1\x11\x69\xe3\x2f\x05\xa7\xc7\ \x1b\xd8\xd2\x17\xf5\x1d\xf2\x63\x20\x74\x01\x78\xa1\x44\x7f\xbc\ \xb5\x4b\x8b\xeb\x75\x43\x84\x77\x37\xbc\x8c\xee\xe9\xd7\xde\x83\ \x5f\x92\x12\xe3\xf8\xc6\x3d\x2b\xb8\x65\xf1\x1c\x4c\x03\xec\x90\ \x7f\xb5\x52\xde\xa4\x70\x07\x18\x86\x16\x87\x4a\x78\x7a\x9f\x7c\ \xff\x6f\xe7\xaa\x5f\xf8\xba\xef\x57\x00\x1a\xda\xd5\xf7\x7c\x5d\ \xff\xb8\xac\x98\x9a\xca\xf3\xa1\xf4\x33\x20\x4b\xae\x9d\xce\xf7\ \x1e\xbc\x93\xc4\x84\xde\xf2\xf6\x19\xfe\xb1\x07\x69\xef\x6b\x76\ \xf0\x43\xc0\xa7\x00\xf8\xfc\xaa\xbd\x75\xd4\xfd\x25\xab\x85\xb8\ \x2b\x5d\xa8\x93\xa2\x75\x0e\xec\x78\x2b\xa4\x4e\xf6\x85\xa6\x69\ \x3c\xfc\xd5\x95\xfc\xf8\x91\xfb\x3e\x1b\xfc\x10\xd1\x05\x9a\xec\ \xc1\xb9\xbd\xb6\x77\x91\xf8\xdc\x01\xf9\xba\xaf\x7b\x3e\x67\x80\ \x9c\x14\xed\xde\x9c\x14\x01\x84\x56\x07\xd4\xb4\x29\xa2\x4c\x70\ \xaa\xac\x98\x9a\xba\xf0\x18\x7b\x2c\x66\x13\xff\xf2\x9d\xaf\xb0\ \x78\x5e\xc0\x4d\xc5\xcf\xb8\x02\x11\x28\xad\xea\xed\x72\xd7\x17\ \xad\x5d\xfc\x08\xf8\xc3\x95\xd7\x7b\xcd\x00\x25\x17\x64\x2c\xc8\ \xf2\x4b\xbf\x27\xc6\xc0\xf8\x0c\x6f\xf6\x8c\x57\xc3\xa4\x79\x6b\ \x9a\xe2\xc7\xdf\xbd\xff\xb3\xc1\xef\x07\xad\x0e\xd8\x75\x5e\xa3\ \xc2\x87\xb7\x75\x5f\x34\xda\xc9\x7d\xb1\x58\x16\x5e\x79\xbd\xf7\ \x0c\xa0\xe9\x0f\xe2\x23\x5e\xe0\x6c\x45\x0d\xc7\x4e\x87\x67\xed\ \xff\xd6\x57\x6e\x63\xe1\x9c\xc9\x61\xa9\xeb\xaf\x85\xa6\x4e\xaf\ \xc6\x5f\xdf\xa1\xfa\xb5\xc9\x2a\x02\xad\x4e\x7e\x06\xcc\xef\x7e\ \xbd\x87\x00\x1c\x3c\x28\x16\x41\xbf\xd7\x57\x05\x9b\xde\x3f\x10\ \x7a\xab\x3e\x98\x3f\x73\x12\x77\xdf\xba\x28\x2c\x75\x0d\x15\xd2\ \x92\x13\x99\x57\x34\x29\xa4\x32\x9d\x8e\x2e\x6c\x2d\xed\xb4\x77\ \xd8\x69\x6d\xef\xc4\x6a\x8d\x22\x3a\xca\x42\x7c\xac\x95\xcc\xb4\ \x64\x34\xed\xd3\xc9\xd9\xe1\x86\xbd\x15\x5a\xc0\x78\x89\x40\x34\ \x75\xc8\xdc\xb7\xdf\x96\xe8\x9b\x6f\xfe\xd4\x87\xb0\x87\x00\xa8\ \x1c\xcf\x8d\xa0\x7c\xfa\x4a\xef\xdc\x77\xc4\x58\xeb\x40\x42\x5c\ \x2c\x8f\xfe\xdd\x2a\xc3\xf5\x0c\x15\xcc\x26\x13\x77\xde\x7c\x1d\ \x0f\xdc\xb5\x9c\xd8\x58\xff\x69\x8d\xda\x3a\x3a\x29\x3d\x76\x96\ \xd2\xb2\xd3\x9c\xa9\xa8\xa1\xbc\xa2\x96\x46\x9b\xff\x00\x9f\xe8\ \x28\x0b\xa3\x0b\x72\x19\x33\x32\x97\x59\xd3\x26\xa1\xa7\x14\x1a\ \x1e\x7c\x80\x2e\x8f\xd2\xea\x33\xf5\xef\x00\x3f\xbf\xfc\x37\x74\ \x7f\x40\x13\xb5\xca\x97\xaf\x47\x45\xd5\x45\xaa\x2e\x36\x18\xee\ \xc0\x7d\x77\x2e\x23\x3d\x25\xc9\x70\x3d\x43\x81\x19\x85\x63\x78\ \xe4\xc1\x2f\xf8\x8d\x2d\xa8\x6f\x6a\x61\xfb\xee\x52\xb6\xed\x2e\ \xe5\xd8\xe9\xf3\xe8\x21\xc4\xc2\x77\x39\x5d\x1c\x3b\x7d\x9e\x63\ \xa7\xcf\xf3\xe6\x7b\x7b\x88\xb1\xc6\x32\xbe\xb0\x88\x19\x73\x17\ \x92\x96\x19\x7a\x30\x4b\x77\xda\x5d\xda\x03\xf8\x12\x80\xed\xe5\ \x12\x23\x4a\xbf\xcd\x57\xa1\xfd\x87\x3f\x36\xd4\x28\x78\xa7\xc9\ \x95\xcb\xaf\x35\x5c\xcf\x60\x93\x99\x96\xcc\x43\xf7\x7e\x9e\x25\ \xd7\x4e\xf7\x79\xbf\xf8\xe8\x29\xfe\xfc\xd6\x4e\x76\x1d\x3a\x16\ \x36\x3f\x48\x87\xbd\x93\xc3\x07\x77\xf1\xd1\xa1\x3d\x14\x4e\x9f\ \xc3\xa2\xe5\x9f\x27\xc6\xda\x3f\x2b\x69\x73\x27\x63\x5f\xd9\x2e\ \xf1\xab\x16\xab\x76\xe8\x26\x00\x89\xd1\xee\x45\x88\xe6\xd3\xc5\ \xbb\xec\xe3\x73\xfd\x6a\xac\x3b\x77\xdd\xb2\x90\xe8\x28\x9f\x3b\ \xca\x57\x05\x16\xb3\x89\x55\x2b\x16\x71\xdf\x9d\xcb\x88\x8d\xe9\ \xed\x14\x75\xfc\x74\x05\x4f\xbd\xb0\x81\x92\xb2\xc8\xed\x8f\x88\ \xe8\x1c\x2d\xd9\x4b\xf9\xe9\x63\xac\xb8\xf3\x5e\x86\x8d\x18\x1b\ \x72\x1d\x4e\x0f\xaa\x23\x5e\x7f\x88\x4f\x66\x81\xcb\x02\xa0\x44\ \xbb\xc9\x5f\xa1\xb2\x53\xc6\xb4\x7f\x93\x49\xe3\xc6\x45\x3e\x77\ \x94\xaf\x0a\x66\x4f\x1b\xc7\x23\x5f\xfb\x82\xcf\xf0\xef\x96\xd6\ \x0e\x7e\xff\xd2\x46\x36\x6e\xdb\x17\xd2\x34\x6f\x84\x8e\xb6\x56\ \xfe\xfc\xfc\x53\x2c\x5d\xb1\x8a\xa9\x33\xaf\x09\xb9\xbc\x03\xed\ \x0b\x5c\x29\x00\x80\x4f\x01\xe8\x74\x74\x51\x53\x67\x6c\xbf\x7f\ \xce\xd4\xf1\x57\xe5\xda\x9f\x95\x9e\xc2\xb7\xef\xbf\x9d\x45\xf3\ \xa6\xf6\xba\xa7\xeb\x3a\xaf\xbf\xb7\x87\xd5\xff\xfb\xf6\x60\x04\ \x89\xa2\xeb\x3a\xef\x6e\x58\x8b\xee\xf1\x30\x7d\xce\x82\x90\xca\ \xb6\x3b\xe4\xb2\x6b\x9f\x19\xa0\xf4\x82\xe4\xe9\xe8\x3e\xe7\x93\ \xca\xea\x7a\xc3\x01\x12\x73\x8b\x26\x1a\x2a\x3f\xd0\x98\x4d\x26\ \x56\x2e\xbf\x96\xbf\xbd\xe7\x66\xbf\xd3\xfd\xaf\x57\xaf\x0b\x9b\ \x5d\xa4\xdf\x88\xb0\xf5\xed\x57\x49\x48\x4a\x66\xf4\xf8\xe0\xed\ \x2a\xed\x4e\x15\xf3\x52\xa9\x14\xde\x33\x5d\x95\x99\x01\x3c\x9a\ \x67\x81\xf2\x93\x4e\xe1\x42\x8d\x71\xed\x7f\xe6\x94\xd0\xd7\xaa\ \xc1\xe2\x9a\xa2\x89\xfc\xc3\xd7\xee\x20\x3f\x3b\xbd\xd7\xbd\xa6\ \xe6\x56\x9e\xfa\xd3\x06\xde\x19\x42\x51\x43\x22\xc2\x5b\xeb\xfe\ \xc4\xfd\xdf\x7a\x8c\xc4\xa4\xe0\xf2\x6d\x8a\x80\xc3\xcd\x57\x81\ \x7f\x34\x03\x28\xd4\x7c\x5f\x0f\x7a\x04\x4e\x56\xb5\x18\xea\x60\ \x62\x7c\x2c\x23\xf2\xb3\x0c\xd5\x31\x10\xe4\x66\xa5\xf1\xf0\xfd\ \x2b\x59\x30\xbb\xb0\xd7\xbd\xa1\x1e\x35\xe4\x74\xd8\xd9\xbc\xfe\ \x25\x56\xdd\xf7\xad\x9e\xe9\x52\xfa\xa0\xcb\x25\x8b\xe0\xb2\x0e\ \x20\xb3\x7d\x65\x8b\xd9\x57\xa1\x38\x5d\xd3\x6e\xa8\x73\x05\xb9\ \x99\xa8\x20\x3b\x35\x58\x4c\x1e\x37\x9c\x17\x7e\xfd\x98\xcf\xb7\ \x94\xe2\xb2\xd3\xfc\x7a\xf5\x6b\x43\x3e\x70\xa4\xa2\xfc\x63\x4e\ \x96\x95\x32\x7e\x72\x51\x50\xcf\x3b\xdc\x6a\x34\x80\x59\x44\xb4\ \x92\x6a\xbd\x97\xbf\x7f\x6d\xbb\xa2\xae\x5d\x61\xef\x34\x16\x16\ \x9d\x9f\xd3\x7b\x2a\x1d\x6a\xa4\x26\xf7\xf6\xa0\xae\x6b\xb4\xf1\ \xe4\x9a\x37\xd8\xba\xab\x74\x10\x7a\xd4\x3f\x3e\xd8\xb2\x81\xb1\ \x13\xa7\xa2\x99\x02\x67\xc5\xea\x70\x92\x24\x22\xca\x5c\x5c\xc9\ \x28\xa5\x11\xdf\xfd\xa6\x47\xf7\x66\xf0\x02\x0c\x87\x71\xe7\x66\ \x0d\x7d\x01\xe8\x8e\xcb\xed\x61\xed\x86\xf7\x79\xfe\xb5\x77\xb1\ \x3b\x8c\x67\x27\x19\x48\x6c\xcd\x8d\x9c\x2c\x2b\x61\xe2\xd4\xc0\ \xaf\xdc\x4e\x0f\xea\xc5\x8f\x98\x6b\x46\xf3\x4c\x04\x45\x5b\x17\ \xd8\xec\x8a\x76\x27\x54\xb7\x7a\x7f\x07\xd0\xdd\xc6\x12\xe6\xc6\ \xf7\x61\x23\x1f\x2c\x8a\x0a\xc7\x30\x61\x54\x7e\xaf\xeb\xfb\x4b\ \x4f\xf2\x9b\x67\xd6\x51\x51\x3d\x70\x9e\xce\xe1\xe6\xd0\x9e\x1d\ \x41\x09\x00\x80\xcb\xa5\x2f\x33\x97\x56\xab\x7b\xaa\x5b\x34\x5c\ \x7e\xac\x96\x1e\xdd\x98\x00\xc4\xf8\x78\x8d\x1a\x2c\xfc\x99\x71\ \x6b\xea\x9a\xf8\xdd\x73\xeb\xd9\xb9\x7f\xd0\xb2\xb6\x87\x8d\xda\ \xea\x0a\x1a\x2e\x56\x93\x9e\x15\x38\x75\x83\xee\x61\xa2\xd9\xad\ \x6b\xa3\xfd\x0d\x3e\x80\xa6\x19\xcb\xb2\x68\x8d\x1e\xfc\xd4\x9c\ \x51\x66\x33\x77\x7f\x7e\x11\x5f\x59\xb9\x0c\x6b\xcc\xa7\xfd\xe9\ \x72\xba\x78\xf1\xf5\x6d\xbc\xf8\xfa\x56\xba\x9c\x91\xcb\x58\x12\ \x17\x6b\x65\x46\xe1\x68\x46\x0f\xcf\x25\x2b\x3d\x85\xb8\x38\x2b\ \x1d\x1d\x76\x6a\xea\x1b\x39\x72\xe2\x1c\x87\x8f\x9f\x09\xab\x15\ \xf1\xf8\x91\x62\xae\x0b\x42\x00\x5c\xa2\x46\x9a\x9d\x6e\xc9\xee\ \x2b\x5f\x64\xf7\x7d\xe9\xfe\x30\xd8\x89\x21\xe6\xcf\x9c\xc4\xc3\ \x5f\x5d\xd9\xeb\xbd\xfe\xc3\x03\x65\x3c\xf1\xdc\xfa\xcb\x59\xc3\ \xc2\x4d\x6c\x4c\x34\x8b\xae\x99\xc6\xe2\xf9\xd3\x99\x35\x65\x1c\ \x96\x3e\xd2\x95\x56\x5f\x6c\xe4\xf7\x2f\x6d\x0c\x9b\xc2\x79\xea\ \xf8\x47\x5c\xb7\x34\x70\x7c\xa8\x47\x57\xd9\xe6\xf4\x38\x1c\xcd\ \x0e\xfc\xba\x17\x2b\x83\x33\x80\xbd\x6b\x70\x14\xa9\xfc\xec\x74\ \x1e\x7e\x60\x25\xf3\x67\xf4\x74\xd4\xb8\x50\x53\xcf\x6f\x9f\x5d\ \xcf\xde\xe2\xe3\x11\x69\x37\x36\x26\x9a\x95\x37\x2e\xe0\x4b\xb7\ \x2d\x22\x39\x31\x3e\x70\x01\xbc\x36\x88\x1f\x3f\x72\x1f\xb3\xa7\ \x4e\xe0\xe7\xbf\x5f\x8b\x18\x9c\x0d\x9a\x1a\x2e\xd2\xd6\x62\x23\ \x21\x29\xb9\xcf\xe7\x9c\x3a\x49\xe6\xf1\x19\xe2\xcc\x4a\x10\x76\ \x95\xfb\xd6\x03\x2c\x16\x63\x53\xb8\xdd\x31\xb0\xe7\x28\x5a\x63\ \xa2\xb8\xf7\x8e\x65\x7c\xf1\xb6\x45\x3d\x32\x84\xda\x1d\x4e\xd6\ \xac\x7b\x8f\xb5\x6f\xbe\x8f\xd3\xa0\x62\xeb\x0b\x4d\x53\xac\xb8\ \x61\x2e\x5f\xbf\xe7\x96\xa0\x07\xfe\x4a\x96\x5e\x3f\x97\x92\x4a\ \x37\xef\x6c\x78\xd5\x70\x7f\x2a\xce\x9e\xa4\xb0\x68\x6e\x9f\xcf\ \x38\x3d\x58\xcd\x28\x52\x93\x63\x60\x6e\x81\xb0\xfb\xbc\xea\x95\ \xc3\x3f\x26\xd6\x58\x74\x4e\x93\x6d\xe0\xa2\x86\x97\x5e\x5b\xc4\ \xb7\xee\xbd\x8d\xcc\xb4\x9e\x92\xbf\x6d\xcf\x61\xfe\xeb\xf9\xd7\ \xa9\x6b\x30\x94\x52\xcf\x2f\x93\xc6\x0c\xe7\x91\x07\xef\x60\xe2\ \x18\x63\xe9\x62\x6b\x5b\x15\x53\x66\x2e\xe0\xd4\x89\x63\x9c\x3d\ \x75\xcc\x50\x5d\x35\xd5\x15\x01\x05\x40\x74\xcc\x66\x20\x16\x20\ \x3d\x4e\x58\x30\x52\x38\x5a\xab\xd1\x62\xe7\x72\x8a\x57\x6b\x3f\ \x1d\x0f\x2e\x71\xa1\x26\xf2\xaf\x54\x05\x79\x59\x7c\xe7\x81\x95\ \xcc\x99\x36\xbe\xc7\xf5\x8a\xea\x3a\x7e\xfb\xec\x7a\xf6\x95\x9c\ \x88\x48\xbb\x89\xf1\xb1\xdc\x7f\xd7\x72\xee\xbc\x69\x81\x61\x5d\ \x09\xbc\x87\x59\x29\xa5\xb1\xf8\xa6\x95\x94\x9f\x3e\x6e\x68\xbf\ \xe1\x62\xf5\x85\x80\xcf\x78\x74\x34\x33\xc2\xe5\x39\x3e\xd5\x0a\ \x0b\x47\xea\x78\xc4\x9b\xee\xb5\xb1\x13\xce\x25\xf7\x6f\x3a\xbb\ \x44\x45\x55\xbd\xa1\xf2\x7d\x91\x10\x17\xcb\x83\x77\xdf\xc4\xed\ \x9f\x9b\xdf\x23\x9c\xac\xb3\xd3\xc1\x33\x7f\x7e\x87\x57\xdf\xfe\ \x00\x77\x98\x43\xd8\xc0\xab\x18\xaf\x5c\x3e\x9f\x07\xef\xbe\x29\ \xac\xf1\x8b\xd6\x28\xef\x49\x2a\x29\x69\x99\x8c\x18\x33\x91\x72\ \x03\xb3\x40\xc3\xc5\x1a\x44\xa4\x4f\x33\xbc\x0e\xca\x0c\xf4\x5a\ \xe4\x4d\xca\x7b\xee\x5e\x4e\x22\xb8\x0a\x53\x30\xb2\x22\x5d\xa8\ \xa9\xa7\xc9\xd6\xe6\xd3\xdc\xda\x6f\x94\xe2\xb6\xa5\xf3\x7a\xad\ \xb7\x22\xc2\x7b\x1f\x14\xf3\xe4\x9a\x37\xfb\x74\xba\x34\xc2\xd4\ \x89\xa3\xf8\xee\x83\x77\x30\x66\x78\x5e\x8f\xeb\x9d\x8e\x2e\x9f\ \x5b\xc7\xa1\xa0\x8b\xf7\x20\x2b\x80\x09\x93\x8b\x0c\x09\x80\xcb\ \xe5\xa4\xa3\xad\x95\xf8\x44\xff\x7e\x18\x1e\x8f\x57\x00\xfa\x24\ \x27\x33\xb5\xdf\x9d\x00\xef\xa0\x1c\x38\x7c\x92\xe5\xd7\xfb\xb6\ \x4e\x9d\x6b\x56\xd8\x5d\xde\x24\x47\x3a\xde\x17\xd2\x40\x5b\x47\ \x0f\xde\x7d\x53\xaf\x0f\xfb\xf4\xf9\x2a\x7e\xb5\x7a\x1d\x1f\x1d\ \x8f\x4c\xea\xd9\xb4\xe4\x44\x1e\xba\xf7\x36\x96\x5d\x37\xa3\xc7\ \xb7\xca\xd6\xda\xce\x1f\x5e\x7a\x8b\x98\x98\x68\x1e\xbe\xff\x76\ \x43\x6d\x98\x14\x8c\x4b\x17\x4e\xd6\x2b\x46\x8c\x9e\xe0\xdd\xd9\ \x33\xb0\x0c\xd8\x6c\x0d\x7d\x0a\x00\x78\x77\x03\x9d\x80\xd5\xdf\ \x03\x99\x69\xc9\x44\x99\xcd\x86\x34\xe7\xad\xbb\x4a\x7a\x08\x80\ \xd3\x03\xcd\x9d\xde\x63\x62\xea\x3b\x14\xb3\xf3\x85\xd6\x2e\xaf\ \xef\xbb\x49\x11\xf0\x50\xa7\xee\x83\xdf\xd6\xd1\xc9\xea\x97\x37\ \xb1\xfe\x9d\xdd\x11\xb1\x39\xf8\x73\xfd\xf6\x78\x74\x5e\x7f\x77\ \x37\xab\x5f\xde\x44\x5b\x47\x27\xab\x6e\xe9\x15\x74\xd3\x2f\x26\ \x66\x0a\x9a\x82\x13\x24\x92\x9a\x96\x41\x53\x43\xff\x75\xa8\xd6\ \xe6\x26\x28\x18\xed\xf7\xbe\xd9\x84\x98\x51\x38\x11\xff\x02\xa0\ \x69\x1a\xa3\x86\xe7\x72\xe2\x4c\x45\xbf\x3b\xb2\xa7\xe4\x38\xe7\ \x2e\xd4\x92\x9c\x9e\x4d\x71\xb5\xd6\x2b\xa6\xed\x70\x8d\xc2\xad\ \x7f\xfa\x06\xb2\xfb\xbc\xa2\x30\x0b\xb2\x13\x04\xb7\x07\xe2\xa2\ \xb8\x7c\xc8\x53\x5d\xbb\xa2\xc3\x05\xe9\xb1\x3a\xbb\xf6\x1e\xe2\ \xc9\x35\x6f\xd2\xdc\x12\x99\x37\x8d\xa2\xc2\xd1\x3c\xf2\xb5\x2f\ \x30\xaa\xa0\x67\x92\x8d\xd2\x63\x67\xf8\xf5\x1f\xd7\x71\xe6\x7c\ \x75\x44\xda\x1d\x9f\x21\xc4\x46\xc1\x3a\x83\x02\xd0\xd9\xd1\xf7\ \x56\xbe\x02\x31\x23\x74\x00\x7d\xce\x13\xe3\x46\xe5\x19\x12\x00\ \x11\xe1\xc9\x17\x36\x70\xd3\xaa\x6f\xd0\x7a\x85\x59\x20\xca\x04\ \xc9\x56\xa1\xae\x5d\x75\x7b\x1e\x8e\xd6\x2a\x8e\x7e\xb2\x23\x19\ \x65\xc2\x7b\xca\x98\xc3\xbb\x51\x55\x5b\x55\xc1\xb6\xb7\x5f\xa5\ \x3a\xcc\x61\xea\x97\xc8\x4c\x4f\xe6\xef\xef\xbb\x9d\x1b\xae\xe9\ \x19\xbb\x58\xd7\x68\xe3\xa9\x35\x6f\xb2\x65\x57\x49\x44\xda\xed\ \xce\xb0\x24\x61\xf2\xa8\x0c\xce\x9c\xec\x7f\x1d\x0e\x47\xdf\xbe\ \x8a\x9a\x86\x6e\x16\xa1\x49\x29\xfa\x34\x1c\x4f\x1a\x33\x9c\x37\ \xdf\xdb\xd3\xff\x9e\x00\x7b\x8a\x8f\x11\x97\xfb\x21\xd3\x67\xf7\ \x74\x60\x8c\x36\x43\x8a\x15\xea\xfa\x10\x56\xa7\x07\x4e\xd4\x29\ \xec\x9d\xed\x7c\xb0\x65\x23\x47\x8a\xf7\x46\xc4\x25\xcb\xdf\x9e\ \x81\xd3\xed\x66\xed\x9b\xef\xb3\x66\xdd\x7b\x03\xba\x45\x9c\x97\ \x6e\x4c\x71\x76\xd8\x03\x08\x80\xc2\x6d\x56\x9a\x34\xd0\x47\xea\ \x57\x08\x9f\x4f\xdf\xd6\xb7\x5e\xc5\xa4\x99\x98\xd2\xcd\x95\xb9\ \xad\x0b\x4e\x06\x48\x73\xd2\xd9\xd1\xc6\x81\x5d\xdb\x39\x7c\xe0\ \x43\x9c\xce\xc8\x58\x16\xaf\x29\x9a\xc8\xc3\x0f\xac\x64\x58\x4e\ \x46\x8f\xeb\xbb\x8b\x8f\xf1\xc4\x33\xeb\xa9\xac\x35\xee\x1b\x19\ \x2a\x56\x83\x6f\x15\x81\x3e\x2b\xb3\x49\xec\x66\xd0\x1a\x7a\x1e\ \xdf\xd4\x9b\x9c\xcc\x54\xf2\xb3\xd3\x0d\x7f\x08\x22\xc2\x3b\x6f\ \xbe\xcc\xb9\x33\x27\x58\xb0\xf4\x56\x52\x52\xfd\x3b\x8b\x78\x3c\ \x6e\xce\x9d\x3e\xc1\xc7\xc7\x0e\xf3\x71\x59\x29\x2e\x57\x64\xbe\ \x79\xfe\x7c\x01\x2b\x6b\x1b\x78\xe2\xd9\xf5\xec\x3e\x64\xcc\x22\ \x67\x04\xa3\x02\xa0\x07\xc8\x1f\x13\x63\x52\x2d\x66\x90\xa0\x16\ \xf7\x85\x73\xa7\xf2\xd2\x1b\xdb\x0c\x75\xe8\x12\x27\xcb\x4a\x39\ \x79\xec\x30\xf9\x05\x23\xc9\x1d\x36\x8a\xe4\xd4\x34\x2c\x51\xd1\ \x74\x39\xec\xb4\xb5\xda\x68\xb8\x58\xc3\x85\x73\xa7\x71\x76\x45\ \xce\x01\x33\x3a\xca\xc2\xdf\xac\x5c\xc2\x97\x6f\xbf\x81\x28\xcb\ \xa7\xbe\x80\x76\x87\x93\x17\xd6\xbf\xc7\xcb\x6f\x44\x66\xcf\x20\ \x14\x4c\x86\x77\x62\xfb\x16\x00\xb3\x46\x8d\x19\xe1\x5c\x30\x95\ \x2d\x5d\x50\x14\x36\x01\x00\x40\x84\xca\xf3\x67\xa9\x3c\x3f\xf0\ \x47\xc6\x2c\x9c\x3b\x85\x6f\xdf\x77\x7b\x2f\x1b\xc7\xd6\x5d\xa5\ \x3c\xb9\xe6\x0d\xea\x1a\x23\xb3\x67\x10\x2a\x46\x37\xd2\x02\x0a\ \x80\xd2\xcf\x99\x45\xa4\x3c\x18\xaf\xdd\x71\x23\xf3\x19\x37\x32\ \x9f\x8f\xcb\x07\xf5\xc4\x2c\x43\x14\xe4\x66\xf2\x9d\x07\xee\x60\ \xce\xf4\x9e\x7b\x06\x75\x0d\x36\x7e\xfa\xbb\x17\x23\x1a\xd7\xd7\ \x1f\x3a\x0d\x0a\x80\xc9\xe4\xdf\xce\x17\x1f\x05\x09\xd1\xec\x36\ \xeb\x16\xd3\x31\xd3\x95\x07\xf6\xf9\x61\xd5\x8a\xeb\xf9\xd7\xdf\ \xbd\x68\xa8\x53\x83\x41\x54\x54\x34\xd7\x5c\xff\x39\x96\x2e\x5e\ \xcc\xc8\x3c\x8d\x2b\x75\x9e\x13\x67\x2b\x87\xdc\xe0\x03\x74\x18\ \x8c\x41\x30\x59\x7a\xba\xb9\x9b\x14\x0c\x4f\xf1\x1e\xba\x99\x62\ \x05\x37\xda\x1b\xe6\x59\x99\x94\x97\x54\xd3\x0a\x04\x3c\xfc\x69\ \xc9\xb5\x45\xfc\x71\xed\x26\xc3\xb1\x82\x03\xc9\x84\xc9\x33\x58\ \xb4\xfc\x76\xe2\x13\x93\x68\x71\xc1\xee\xf3\x30\x3a\x15\x26\x66\ \x09\xa6\xa1\x1d\xae\x60\x38\x27\xc3\x88\x34\x0b\xb3\x87\x09\x36\ \x3b\xc4\x46\x41\x6e\x82\xf7\x68\x5e\x00\x14\x2d\xb3\x73\xa8\xd4\ \x94\x52\x02\x12\x94\x37\xa4\xc5\x6c\xe2\xeb\xf7\xdc\x62\xa8\x53\ \x03\x45\x7a\x66\x0e\x5f\xbc\xff\xdb\xac\xb8\xeb\xbe\x1e\xf6\x70\ \x11\x38\x51\xeb\x66\xf3\x47\x1d\x01\x93\x2c\x0e\x36\x95\x06\xc3\ \xf2\x32\x92\xe3\xc9\x4b\x14\x0a\xb3\x84\x91\x29\xdd\x06\x1f\x50\ \x22\x47\x94\x52\xa2\x01\x88\xa8\xa0\x13\x00\x2d\xbd\xb6\x88\x19\ \x85\x63\x0c\x75\x2c\x92\x44\xc5\x58\x59\x7c\xe3\x4a\xee\xfd\xe6\ \xa3\x0c\x1b\xd9\xbb\x9f\xa7\x4f\x1c\xe5\xb9\x27\xff\x83\x63\xa7\ \xcf\x19\x3e\xb9\x3c\x92\x88\x08\x15\x55\xc6\x7c\x29\xfa\xda\x81\ \x15\xa5\xf6\xc1\x27\xa1\x61\x1a\xb2\x4b\x50\xff\x10\x4c\xa5\x4a\ \x29\x7e\xf0\xf7\x5f\xe2\xde\xef\xfd\x62\x68\xc5\xc9\x29\x45\xe1\ \xb4\xd9\x2c\x5c\x7a\x2b\x71\x09\xbd\x57\xb3\xe6\xa6\x06\xb6\xbf\ \xfd\x5a\x0f\x4f\x1b\xdb\x10\xea\xfe\x95\x9c\xa9\xa8\xa6\xdd\x60\ \x46\xf4\xb4\x14\xff\xab\xba\x20\xbb\xe0\x13\x01\x10\xdd\xb4\x0b\ \x53\xf0\x3b\x69\xd9\x19\xa9\xfc\xf4\x91\xfb\xf8\xfe\x7f\x3c\x3d\ \xe8\x5e\xbf\x00\x99\xd9\x79\xdc\x70\xf3\x9d\xe4\x0f\x1f\xd5\xeb\ \x9e\xdb\xe5\x62\xff\xae\x2d\xec\xff\x60\x2b\x6e\x77\x4f\xd7\xef\ \xba\x76\x45\x63\xc7\xd0\x88\xf2\xbd\x92\x83\x1f\x19\x3f\x7a\xa7\ \x20\xb7\x0f\x43\x9b\xd9\xb4\x07\x3e\x11\x80\x19\x05\xaa\xba\xb8\ \xca\xf3\x31\x30\x2e\xd8\xca\xe7\x16\x4d\xe0\x91\xaf\xdd\xc1\xaf\ \x56\xbf\x36\x68\xa1\xd2\x31\xd6\x58\x16\xdc\x70\x0b\xd3\x66\xcf\ \x47\xa9\xde\x46\x93\x8f\x8f\x1d\xe6\xfd\xcd\xeb\x69\x6d\x69\xf6\ \x59\x5e\x04\x0e\x56\x69\x44\x0f\x41\x19\xd8\x57\x6a\xcc\x6b\x59\ \xd3\x14\x79\xd9\x19\xfe\x6e\x97\x5d\x3a\x78\xfa\xb2\x5a\x20\xc2\ \x26\xa5\x82\x17\x00\x80\x95\xcb\xaf\xc5\x6c\x32\xf1\x9f\x4f\xff\ \x39\xa8\x63\xdc\xc3\x85\x52\x8a\x29\x33\xae\xe1\xba\x25\x2b\xb0\ \xc6\xf5\xce\x31\xdc\xdc\x58\xc7\x96\xb7\x5e\xe5\x7c\x10\x5b\x69\ \x76\x17\x98\x86\x98\x32\x58\xd7\x68\xe3\xd0\x11\x63\xaf\xa5\xf9\ \xd9\x19\x7e\x73\x32\x09\x6c\xba\xf4\xf3\xa7\x7a\xa1\xa6\x6f\x42\ \xb4\xa0\xf4\x80\xee\xdc\xba\x74\x1e\xf9\x39\xe9\xfc\xe8\x37\x2f\ \xd0\xd4\x1c\x19\x37\xac\xee\x14\x8c\x1c\xc7\xc2\xcf\xdd\x4a\x76\ \x6e\x6f\x0f\x5c\xa7\xb3\x8b\xbd\x3b\xde\xe5\xd0\x9e\xed\x78\x42\ \xf0\x05\xec\x6b\x27\x72\x30\xd8\xfc\xfe\x01\xc3\x4b\xeb\xa4\xb1\ \xfe\x3d\x94\x95\xe8\x9b\x2f\xfd\xfc\x69\x96\xb0\x0e\xf3\xfb\x6d\ \x71\x7a\x0b\xd2\xb7\x6f\x80\x2f\x8a\x0a\xc7\xf0\xe2\x6f\x1e\xe3\ \x5f\xff\x67\x23\xbb\xf7\xee\x43\x24\xbc\xb3\x81\xa6\x69\x8c\x9d\ \x30\x95\xe9\xf3\x16\x32\x6c\xb8\x6f\x0f\x97\x13\x47\x4b\xd8\xf1\ \xce\xeb\xb4\xb5\x86\x6e\xc6\xad\x6a\x1b\x3a\x6f\x03\x76\x87\x93\ \x57\x37\x7d\x60\xb8\x9e\xc2\x71\x23\xfc\xdd\xb2\x45\xb7\x98\x2f\ \x37\x70\x59\x00\xc6\x8e\x55\x5d\xc5\xd5\x9e\x37\x00\x9f\xa9\x62\ \x03\x11\x15\x1d\xcb\xb5\x37\xdd\xcd\xf8\x59\x8b\x29\xd9\xf7\x01\ \xc7\x8f\x16\xe3\x30\x90\x5b\x40\xd3\x34\xf2\x86\x8d\x64\x5c\x61\ \x11\xe3\x26\x4d\xf3\xa9\xd9\x83\xf7\xbc\xc2\x2d\x1b\x5f\xfd\x8b\ \x39\xaf\xf0\xd5\xb7\x77\x86\x25\x96\xe2\x4a\x73\x77\x37\xd6\x15\ \x16\xaa\xcb\x5b\xab\x3d\x53\xc5\xea\xf2\x8a\x28\xd5\x2f\x01\xe8\ \x74\x79\xbd\x5a\x53\xd3\xb3\x58\x72\xcb\x9d\x2c\xbe\xd1\xeb\xdb\ \x7e\xf6\xd4\x31\xea\x6b\xaa\xa8\xaf\xab\xc6\xe5\xf4\xbf\xa5\x9b\ \x98\x9c\x4a\x6a\x7a\x26\x19\xd9\x79\x0c\x1b\x3e\x9a\x61\x23\xc6\ \x60\x89\xf2\xbf\x1d\xea\xec\x72\xb0\xfb\xfd\xcd\x14\xef\xdb\x19\ \xf6\xd3\x4b\x06\x8b\x8b\x0d\xcd\xbc\xf0\xfa\x56\xc3\xf5\x14\xe4\ \x65\x91\xe7\x27\x2f\x83\xa6\xf4\xb5\xdd\x7f\xef\x21\x00\xb6\x3c\ \xd3\x3b\x49\x35\x7a\x0d\xd2\xf7\x49\x53\xbe\x48\x8a\xf1\x7a\xf6\ \x34\x7f\xf2\xea\xaa\x99\x4c\x8c\x1e\x3f\xf9\x72\xf6\x2a\x11\xa1\ \xad\xd5\x86\xdb\xe9\xc4\xe5\x72\xe2\xec\xea\x22\xc6\x1a\xeb\xfd\ \x17\x1b\x1b\x52\x08\xda\xb9\xd3\x27\xd8\xb4\xfe\x45\x3a\xda\x23\ \xaf\x73\x0c\x14\x22\xc2\xcf\xff\x7b\x6d\x58\x6c\x2b\x8b\xe6\xf9\ \x3d\xe0\xb5\xd6\x5d\x6d\xde\xde\xfd\x42\x0f\x01\x58\xac\x94\xbb\ \xb8\xca\xb3\x06\x78\x2c\xd4\x46\x35\x05\x85\x59\x3a\x1f\x9e\xf3\ \xbd\x87\xad\x94\x0a\x3a\x8b\x55\x20\xca\x4f\x1d\xfb\x8b\x1a\x7c\ \x80\x35\xeb\xde\x63\xff\x61\x03\x0e\x80\xdd\xb8\x71\xa1\x6f\x17\ \x7c\xa5\xf8\xe3\x95\x07\x4b\xf7\x1a\x2d\xd1\xb5\xd5\x04\x72\x11\ \xf2\x81\xcb\x03\xc7\xeb\xff\x7a\x0f\x7b\x0a\x72\x43\xd5\x27\x5b\ \x77\x95\xb2\xfa\xe5\xcd\x81\x1f\x0c\x82\xa9\x13\x46\x51\x90\xe7\ \x33\x2b\x9b\xb8\x4d\xda\x33\x57\x5e\xec\x35\x62\x33\x87\xa9\xd3\ \x28\xb5\xe9\xca\xeb\x81\xf8\xa8\x56\xd1\x68\x2c\x9f\xd4\x55\x4d\ \x55\x8b\xc2\xd6\x0f\xcb\xed\xd6\x5d\xa5\xfc\xf4\x89\x3f\x85\xcd\ \x98\xe6\xff\x2c\x06\xb5\x71\x76\x96\xea\xe5\x7d\xe3\xd3\x63\x40\ \x74\xcf\x2f\x95\xd2\x6e\x0e\xa5\xe1\x01\x4a\x93\x3b\x64\x71\xeb\ \xb0\xa3\x5c\xa3\xe0\x93\xbd\xf6\xa4\x18\xef\xff\xfe\x10\x11\xd6\ \x6e\xdc\xc1\x53\x2f\x6c\x08\x9b\x39\xbd\x20\x37\x93\x05\xb3\x7d\ \x67\x0c\x15\x15\xc2\xb1\x71\x33\xf3\x2d\xdb\x8a\xab\x3c\xc5\xc0\ \x8c\x60\x1b\xcf\x4d\x04\x83\x39\x25\xaf\x7a\x44\xe0\x7c\xb3\xe2\ \x7c\xb3\x57\xe7\xb9\x7e\xa4\x4e\xb2\x0f\x21\x68\xb2\xb5\xf1\x9f\ \x7f\x78\x25\xec\x39\x89\xbe\x7e\xcf\x2d\x68\x5a\x6f\x9b\x86\x52\ \xb2\x6f\x46\xae\xf2\x69\x5c\xf0\xeb\x33\xa4\x90\x9f\x08\xea\xf5\ \x60\x1b\xcf\x4e\x10\xd2\xe3\xa0\xa1\x63\xe8\x18\x55\x06\x13\x11\ \x6f\xdc\xe3\x74\xeb\xa7\x53\xa3\xcb\xed\x61\xc3\xd6\xbd\x3c\xfd\ \xd2\xdb\xb4\x75\x84\x37\xc1\xf4\xd4\x09\xa3\xb8\x7e\xae\x1f\xed\ \xdf\xc3\x4f\xfc\x95\xf3\x2b\x00\x45\x79\xe6\x37\x8a\xab\xdd\xfb\ \x11\x35\x27\x98\x0e\x98\x14\x5c\x3b\x5c\x38\x7a\x11\xce\x0c\xe1\ \x7d\xf6\x81\xa4\xa9\x53\x01\x42\x6b\x5b\x07\x9b\x77\x1c\x64\xed\ \xc6\x1d\x5c\x6c\xf0\xbd\x31\x65\x84\x28\xb3\x99\x47\xbf\x71\x97\ \xef\x50\x70\x91\x03\xd3\xf3\x4d\x7e\x75\xba\x3e\xa3\x83\x05\xf9\ \x17\x85\x0a\x5a\x3d\x55\x0a\x26\x67\x09\x4d\x9d\xea\xb2\x3d\xe0\ \xaf\x11\xb7\xcb\x45\x7d\x5d\x35\x25\x95\xe7\xd8\xb4\xf6\x38\x87\ \x8e\x9c\x8a\xa8\x8b\xf9\xd7\xee\xbe\x91\x11\x7e\x8e\xae\x11\xe4\ \x9f\xbc\x5e\x5f\xbe\xe9\x53\x00\x66\xe6\x5a\xde\x29\xae\xd6\xdf\ \x46\x24\x68\x85\x50\x29\x98\x9e\xab\xf3\xfe\x59\xcd\x48\x64\xf3\ \x55\xc7\x91\xd2\xfd\x9c\x39\x79\x14\x87\xc3\x1e\x30\x24\x2b\x9c\ \xcc\x99\x3e\x9e\x2f\xdd\x76\x83\xbf\xdb\x6b\x67\xe6\x5b\xfa\xf4\ \xe5\x0f\xf8\xe2\x2e\x1e\xf5\x0f\x78\x43\xc8\x83\x26\x29\xc6\xeb\ \x7d\xfa\xd7\x84\xd3\x61\xc7\xd6\xdc\x38\xa0\x83\x3f\x72\x58\x36\ \x8f\x7f\xe7\x5e\x9f\x8a\x9f\x40\x9b\xa6\x6b\x3e\xcf\x7f\xee\x4e\ \x40\x01\x98\x39\x4c\x9d\x56\xf0\x9b\x50\x3b\x37\x21\x43\xe8\x23\ \x35\xde\x67\x18\x24\x33\x3d\x99\x5f\xfe\xf3\x37\x48\x8c\xf7\x9d\ \xa2\x46\x09\x8f\x4f\x1f\xa6\xaa\x02\xd5\x13\x94\xe9\xce\xae\x6b\ \x8f\x03\x21\x79\x28\xc4\x98\xbd\xd9\x2e\x3e\x23\xfc\x24\x27\xc6\ \xf3\x9b\x7f\xf9\x26\x99\xe9\x7e\xf3\x00\x96\xe9\xb5\xda\xef\x82\ \xa9\x2b\x28\x01\x98\x3f\x4c\xd9\x45\xf4\x6f\x10\xa2\x89\x78\x4c\ \x9a\x10\x77\xf5\x1e\x14\x36\x24\x19\x96\x93\xc1\x53\xff\xfa\x6d\ \x9f\x07\x58\x7d\x82\x2e\x4a\xfb\xe6\x95\x36\x7f\x7f\x04\x6d\xbc\ \x9f\x99\x6f\xd9\x26\xc2\x13\xc1\x3e\x0f\xde\x0d\xa2\x49\x59\x9f\ \xcd\x02\xe1\x62\xfa\xa4\xd1\xfc\xf7\xbf\x3d\xdc\xd7\xe0\x23\xc2\ \xcf\x66\xfa\x31\xfa\xf8\x22\xa4\xdd\x9b\x44\xbb\xf6\x18\x70\x38\ \x94\x32\x79\x49\x42\x5e\xd2\x67\x42\x60\x04\xa5\x14\xab\x6e\x59\ \xc8\x6f\x7f\xf4\xad\x00\x59\x48\x65\xaf\xd4\x6a\x8f\x87\x52\x77\ \xc0\x2c\x61\xdd\x19\x3b\x56\x75\x95\xd6\xc8\x97\x75\x5d\xdf\x07\ \xf4\xf6\xc6\xf4\xc3\xf4\x5c\xa1\xb1\x43\xe1\x18\xdc\x68\xeb\xcb\ \xa4\x65\x64\x93\x9e\x61\xec\x08\xd6\x81\x62\x58\x4e\x06\x8f\xfe\ \xdd\x2a\x8a\x02\x07\xe3\x34\x98\xc4\x74\xd7\x8c\x20\xa7\xfe\x4b\ \x84\x24\x00\x00\xd3\x73\x54\xd9\xa1\x6a\xf7\x57\x94\xa8\xd7\x08\ \x9c\xd1\x0d\x00\x8b\x06\x63\xd3\x85\x23\xb5\x83\x6b\x21\x8c\x8a\ \x8e\x61\xfe\xe2\x9b\x98\x31\xe7\xba\xa0\x8e\x55\x19\x4c\xcc\x26\ \x13\x5f\xbc\x75\x11\x0f\xac\x5a\x1e\xcc\x89\xab\x02\xf2\xb5\x69\ \xf9\x2a\xe4\xd0\xed\x90\x05\x00\x60\x66\xae\x79\x7d\x71\xb5\xe7\ \x57\x08\x01\xdf\x33\x2f\x91\x95\x20\x94\xd5\x2a\x06\x2b\x8c\x64\ \xf4\xb8\x42\x96\xae\x58\x15\x30\x83\xf6\x60\x63\x36\x99\x58\xb2\ \xa0\x88\xaf\xde\xb5\xdc\xe7\xd1\x75\x3e\x11\x7e\x36\x23\xdf\xfc\ \x66\xbf\xda\xeb\x4f\x21\x80\xa2\x1c\xed\xd1\x92\x6a\x3d\x1f\xf8\ \x62\x30\xcf\xc7\x47\x79\xf3\xff\x1d\xae\x55\x03\x6a\x21\xcc\xc8\ \xca\x63\xc9\x2d\xbe\xa3\x86\x86\x12\xb1\x31\xd1\x2c\xbb\x6e\x26\ \x7f\xb3\x72\x49\xa8\xc9\x39\x5f\x2e\xca\xd3\x7e\xd8\xdf\x76\xfb\ \x2d\x00\x4a\x29\xfd\xe0\x41\xf9\x8a\x96\x23\xc9\x20\xcb\x83\x29\ \x33\x22\x55\x88\x8f\x86\xd2\x6a\xef\xd9\x44\x91\x24\x3a\xc6\xca\ \xfc\xc5\x37\x51\x34\xe7\xba\xb0\x24\x72\x8e\x14\xe3\x47\x0f\xe3\ \xf3\x4b\xaf\x61\xe9\x82\x19\xc4\x5a\x43\xcd\x09\xa4\xb6\x25\x74\ \xaa\xfb\x95\x52\xfd\x9e\x58\xfb\x2d\x00\x00\xb3\x66\x29\xd7\x87\ \xf5\x72\x57\xac\x53\xb6\x03\x33\x83\x29\x93\x1e\x27\xdc\x30\x46\ \x28\x6f\x52\x9c\xac\x57\x38\xc3\xed\xd0\xab\x14\x93\xa7\xcf\x61\ \xe1\xb2\x5b\x89\x8d\x0b\x63\x7e\xe2\x30\x61\x31\x9b\x98\x3a\x71\ \x14\xd7\xce\x2c\x64\xfe\xac\xc2\xe0\xa7\xf9\x5e\xc8\xc1\x68\x8b\ \xf6\xf9\xb1\x63\x95\xa1\x34\x22\x86\x04\x00\x60\x41\x86\x6a\x3b\ \x58\x2d\x37\x2a\xd1\x3f\x54\xe0\xd7\x19\xbd\x3b\x9a\x82\xd1\x69\ \xc2\x88\x14\xa1\xaa\x4d\x71\xba\x5e\xf5\x4a\x20\xd9\x1f\xb2\x72\ \xf2\x59\xb2\xe2\x2e\x72\xf3\x47\x18\xaf\x2c\x4c\x58\xad\xd1\x4c\ \x1a\x53\xc0\xe4\x71\x23\x99\x3c\x61\x04\xd3\xc6\x8f\xec\x91\x72\ \xb6\x5f\x28\xce\x58\x4c\xa6\x15\x85\x99\xca\x70\x4c\x93\x61\x01\ \x00\x98\x95\xab\x1a\x4a\x6a\xe4\x46\xd1\xf5\x77\x81\xa0\x93\x0a\ \x9a\x34\x28\x48\x12\xf2\x13\x85\x13\xf5\x8a\x53\x0d\xfd\xd3\x0f\ \x62\xac\xb1\x5c\xb7\x64\x05\x53\x67\xcd\xef\xd7\x29\xa5\xa9\x69\ \x19\x4c\x2e\x9a\x47\xab\xad\x89\x56\x5b\x23\x76\xbb\x1d\x67\x97\ \x23\xa8\x08\x27\xb3\xc5\x42\x74\xb4\x95\xec\x8c\x24\xb2\xd3\x12\ \xc9\x4a\x4b\x26\x2f\x27\x83\x11\x79\x59\x0c\xcb\xcd\x20\x3b\x23\ \xd5\xe7\x66\x8d\x01\x4e\x81\xb6\x6c\x4a\x96\xba\x18\x8e\xca\xc2\ \xda\xb3\x7d\x95\x92\x66\x51\x9e\x8d\xa0\xe6\xf5\xa7\x7c\x65\x8b\ \xe2\x50\x55\x60\x21\xd8\xbe\x69\x1d\x87\xf6\xee\xb8\x1c\x24\xba\ \x70\xe9\x0a\x62\x62\x83\x36\x4b\x04\x8d\xc7\xe3\xc1\xed\xea\xc2\ \xe1\x70\xe0\x71\xb9\xd0\xcc\x66\x34\xa5\x88\x8e\x89\x41\xd3\xcc\ \x58\xa2\xbc\xb1\x0c\xf1\x51\xb0\x64\x8c\x1e\xec\xb1\xbd\x06\x90\ \x83\x26\x93\xe9\x96\x69\xd9\x2a\x6c\xa7\x70\x84\x65\x06\xb8\xc4\ \xdc\x7c\xd5\x58\x56\x27\xcb\xba\x5c\xf2\x6a\xb0\x8a\x61\x77\xf2\ \x93\x84\xba\x76\xa8\xb0\x05\xfe\x24\x07\x62\xba\x37\x99\x4c\x98\ \x4c\xb1\x44\xc7\xf4\x7d\x28\x44\x7a\x9c\x0c\xc0\xe0\xab\x6d\xce\ \x18\x6d\xe5\xbc\x34\x15\xd6\x80\x88\x88\x74\xbb\xac\x4c\xa2\xba\ \x92\xf5\xe7\x81\xbb\x43\x2d\x5b\xdb\xa6\xd8\x5b\xd1\x77\xb7\x9a\ \x1a\xea\x48\x49\xcb\x18\x32\x87\x52\x8f\x48\x11\xa6\xe7\x46\xf0\ \xdd\x56\xd4\xba\x16\x97\xfa\xf2\xe2\x91\x2a\xec\x39\x4d\xc2\x3a\ \x03\x5c\xa2\xb0\x50\x39\x45\xe4\xcb\xa5\xd5\x7a\x85\xc0\xf7\x09\ \x41\xd0\x52\xac\x12\xf0\xf1\xd4\x74\xff\x9b\x21\x03\x8d\x22\xa2\ \xce\x2f\x82\xf0\xb3\xa2\x3c\xf5\x43\x23\xaf\x7a\x7d\x11\xb1\x17\ \x64\xa5\x94\x5e\x94\x67\x7a\x0c\xf4\xcf\x01\x41\x2b\x2c\xd1\x66\ \xaf\x2f\x8d\x61\x08\x64\x00\x00\x02\x6f\x49\x44\x41\x54\xc1\xd5\ \x42\x4e\x62\xdf\xfe\xff\x06\x68\x50\xba\xac\x98\x91\x6f\xfa\x41\ \xa4\x06\x1f\x22\x28\x00\x97\x98\x91\x67\xd9\x62\x12\x6d\x96\x42\ \x3e\x0c\xb6\x4c\xd4\x55\x24\x00\x63\x23\xe1\xf4\xa2\x64\xbf\xd2\ \xb4\xd9\x45\xc3\xcc\x6f\x87\xbf\xf2\x9e\x0c\x88\x89\x6c\x5a\xbe\ \xaa\xb4\xe5\x9a\x16\x0b\xfc\x18\x02\x6f\x07\x0c\xf5\x04\x8e\x97\ \xc8\x88\x0b\xfb\xb7\x5f\x80\x27\xf4\x6a\xd3\x82\xa2\x1c\x75\x2e\ \xac\x35\xfb\x61\xc0\x3f\xea\xe2\x1a\xb9\x1e\x5d\x7f\x12\x28\xf4\ \xf7\xcc\x8e\xb3\xda\x90\x76\x2b\x57\x0a\xd2\x63\x85\x19\x79\x82\ \x35\x5c\x1e\x4f\x8a\x23\x82\xf6\x50\x28\xce\x1c\xe1\x69\x76\x10\ \xd8\x2e\x62\x4e\xae\xd1\x1f\xd2\x85\x9f\x2a\xe8\x65\xaf\x1d\x8a\ \x02\x90\x11\x27\x8c\x48\x85\x84\x28\x21\x2e\x3a\xac\xb3\x54\xa7\ \xc0\x2f\x62\x6c\xda\xbf\x77\xcf\xdc\x31\x50\x0c\xea\x64\x5b\x5c\ \x21\xb9\x68\xfa\xcf\x50\x7c\xa5\xfb\xf5\x7d\x17\x14\x35\xad\x43\ \x63\x1d\x48\xb1\x7a\xdd\xda\xfa\x3a\xc5\xac\xbf\x28\xd4\x46\x8f\ \x52\x0f\xcd\xca\x55\xfd\x3f\x90\xc9\x70\x1f\x86\x00\xc5\x95\xae\ \x25\x28\xf5\x23\x50\xd7\x01\x1c\xa9\x55\x83\x1e\x5e\x66\x36\x41\ \x51\x4e\xa4\xdc\xd9\x64\xaf\x88\xfc\x73\xa0\xa0\x8d\x81\x60\x48\ \x08\xc0\x25\x8a\xab\x65\x21\x22\x3f\x28\xab\x65\xf9\x60\xe7\xf1\ \xbd\xa6\x40\xc8\x4a\x08\xf3\xe0\x2b\xd9\xaf\x3c\xfc\x78\x20\xb4\ \xfb\x60\x19\x52\x02\x70\x89\x67\x0f\xca\x3d\x2d\x0e\xfe\xa3\xd9\ \x4e\xc1\x60\x84\x97\x59\x2d\xb0\x7c\x5c\xd8\x5e\xbd\x05\xd4\x56\ \x41\x7f\x62\x66\x9e\x79\x43\xb8\x2a\x0d\x17\x43\x52\x00\x2e\xf1\ \xec\x61\x99\x67\xb7\xf3\xef\xb6\x4e\x59\xe8\x70\xab\x01\x73\xe2\ \x8b\x31\xc3\x8d\xe3\x0d\x0a\x80\xa2\x06\x61\x8d\xee\xd1\x9e\x9e\ \x55\xa0\xce\x84\xa7\x67\xe1\x67\x48\x0b\xc0\x25\x5e\x29\x93\x28\ \x87\x53\xff\x5e\xab\x5d\x7b\xc0\x66\x67\xb4\xd3\x13\xf9\x7e\xdf\ \x3c\x41\x27\x2a\x74\x91\x6b\x56\xf0\xba\xae\xf4\xb5\xad\x39\xe6\ \xad\x8b\x95\x1a\x22\x7e\xd0\xfe\xb9\x2a\x04\xa0\x3b\x1b\x0e\x4a\ \xac\x4d\xe9\x0f\xb5\x3b\xd5\xdd\xad\x0e\x55\xd8\xe9\x8a\x4c\xae\ \xe7\x25\x63\x74\x12\x82\xf3\xd0\x2a\x43\xb1\x19\xd1\x37\x47\xdb\ \xcc\x3b\x07\xe3\x55\xce\x08\x57\x9d\x00\x5c\xc9\x9a\xc3\x32\xd9\ \xe9\xd2\xbf\xea\x74\xa9\xc5\x76\x37\xa3\x3b\x9d\x2a\xc1\x65\x70\ \x86\x48\xb1\xc2\x75\xa3\xf4\xde\x66\x52\x45\x8b\x12\x39\x82\x52\ \x07\x74\xe4\xc3\x28\x93\x69\x57\xb8\x1c\x33\x06\x8b\xab\x5e\x00\ \x7a\x21\xa2\x9e\x29\x61\xa1\x86\x7e\x9d\xcb\xc5\x14\xa7\x68\xa3\ \x3c\x3a\x99\x2e\xb7\x24\xba\x45\xc5\xb8\xdd\x58\x04\x34\x5d\xc0\ \xad\x7b\x77\x94\x2d\x1a\xa2\x29\x44\x53\xe8\x9a\x26\xee\x59\xf9\ \x1c\x4e\x8e\x91\x72\x81\x0b\x4a\x28\x17\x4d\x2f\x57\x98\x8f\xcf\ \xc8\x55\x91\x39\xac\x78\x10\xf9\x3f\x06\x8e\x97\x48\x80\x7c\xb0\ \xcc\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x9a\xb3\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x02\x00\x00\x00\x02\x00\x08\x06\x00\x00\x00\xf4\x78\xd4\xfa\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ \x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x20\x00\x49\x44\ \x41\x54\x78\x9c\xec\xdd\x77\x78\x5c\xe5\x95\x3f\xf0\xef\x7b\x67\ \xd4\xbb\x2c\xc9\x2a\x96\xdc\x3b\x6e\xb2\xc1\xc6\xa6\x98\xde\xc1\ \x18\x30\xa1\xf7\x40\x1a\x09\xd9\xdd\xb4\xcd\x66\xbd\xc9\x66\xb3\ \xbf\x4d\x02\x29\xd4\xd8\xc6\x14\x63\xc0\x60\x63\xc0\x31\xee\x06\ \xf7\x22\xc9\x72\x91\x9b\x24\xcb\xb2\x7a\xd7\xf4\x76\xef\x3d\xbf\ \x3f\x06\x83\x8b\xca\x48\x9a\xb9\x77\xca\xf9\x3c\x4f\x9e\x80\x34\ \xba\xef\x41\x65\xde\x73\xdf\x7b\xde\xf3\x0a\x30\xc6\x82\xda\xda\ \xb5\x14\x63\xce\xc3\x68\x08\x8c\x54\x14\x75\x98\x42\xc8\x96\x15\ \x0c\x56\x49\xca\x50\x55\x4a\x57\x48\xa4\xc8\x2a\x25\x79\x54\x11\ \x07\xc0\x08\xc0\xa8\x10\x19\x05\xc1\xa0\x02\x92\x4a\x30\x00\x80\ \xa2\x08\x10\x20\x08\x10\xb2\x0a\x10\x01\x42\x00\x51\x12\x40\x00\ \x24\x40\x35\x18\x08\x00\x60\x14\x82\x84\x80\x4c\x44\x30\x1a\x84\ \x8b\x54\x22\x49\x12\xee\x28\x03\x39\x8d\x06\x61\x33\x08\x32\x1b\ \x25\x74\x18\x04\xb5\x19\x0d\x52\xb3\x41\xa8\x8d\x31\x46\xaa\x8f\ \x8d\x36\x9c\xca\x88\x46\x95\x29\x1f\xcd\xd7\x08\x21\xeb\xf7\x5d\ \x63\x8c\xf5\x46\xe8\x1d\x00\x63\x91\x6c\x75\x05\xe5\xbb\xad\x98\ \xe1\x94\xd5\x49\xb2\x8a\xb1\xb2\x2a\x0d\x75\xab\xc8\x71\x2b\x94\ \xee\xf6\x20\xde\xa3\x8a\x28\x8f\x1a\x5a\x7f\xa7\x42\x00\x31\x06\ \xe0\x8a\x61\x6a\x7b\x62\x0c\x1a\x20\xa8\x45\xa8\xa2\x96\x04\xea\ \x08\xa8\x05\xa8\x5a\x22\x43\x8d\x22\xa1\x76\x46\xae\x68\xd5\x3b\ \x5e\xc6\x22\x55\x48\xbd\xb1\x30\x16\x6a\x56\x10\x19\x94\x43\x98\ \xe1\xf6\xa8\x73\x5d\x0a\x0a\x3d\xaa\x34\x4a\x56\x28\xc7\xa3\x8a\ \x54\x97\x8c\x58\x97\x1c\xbe\x7f\x83\x39\xc9\x84\x99\xf9\xd4\xdb\ \xcb\x1c\x10\xa8\x20\x12\x95\x00\x55\x80\x50\x21\x84\x5a\x29\x24\ \x63\xc5\xd4\x6c\x9c\x11\x42\xa8\x5a\xc4\xca\x58\x24\x0a\xdb\x37\ \x1f\xc6\xb4\xf6\xee\x41\x2a\x94\x9d\xea\x0d\x1e\x21\xcd\x74\x7b\ \x68\xbc\x5b\x15\xb9\x76\x0f\x92\xdc\x61\x3c\xc9\xf7\x44\x08\xe0\ \xba\x51\x2a\x12\xa3\xfb\x7d\x09\x37\x80\x0a\x08\x51\x46\x44\x47\ \x25\xa2\x32\x61\x30\x1c\x3d\x99\x8d\xe3\x0b\x84\x50\xfc\x17\x29\ \x63\x91\x29\x22\xdf\x98\x18\x1b\x88\x37\x8a\x28\x3e\x5a\xc1\x6d\ \x32\x70\x9b\x4b\xa6\x19\x4e\x19\x05\x0e\x59\x24\x7a\x14\xfe\x7b\ \xba\xd0\xd0\x54\xc2\xb4\xbc\x5e\x57\x01\xfa\x46\xc0\x03\x42\x39\ \x08\xc5\x42\x42\xb1\x02\xa9\x18\x40\xc9\x8c\x5c\x61\xf7\xef\x40\ \x8c\x85\x37\x7e\xc3\x62\xac\x07\x2b\x88\x0c\x9e\xc3\xb8\x49\xf6\ \xa8\xf3\x1c\x2e\x31\xdb\xae\x60\xb8\xc5\x29\xe2\x15\x3f\xcf\x69\ \xe1\x4a\x08\xe0\xb2\x21\x84\x9c\xe4\x80\x7f\xc3\x64\x00\x47\x01\ \xec\x03\x68\x8f\xc1\x60\xd8\x3b\x79\x30\x8e\xf2\x23\x04\xc6\xba\ \xc7\x09\x00\x63\xe7\x78\xa3\x88\x0a\x62\x49\x7d\xc2\xa1\x48\xd7\ \x39\x15\x4c\xb0\x3a\x91\xee\xe6\x3b\xfb\x01\x91\x04\x70\xa9\x36\ \x49\xc0\x85\xcc\x80\xd8\x2f\x04\xed\x21\x41\x3b\xed\x46\xc3\x8e\ \x2b\x32\x85\x45\xeb\x20\x18\x0b\x56\xfc\xc6\xc6\x22\xda\xf2\xc3\ \x34\xd8\xe5\x52\x9f\x70\x79\xc4\x3c\xab\x5b\x4c\xb4\xb8\x91\x48\ \x7c\x77\xef\x77\x02\xc0\x98\x4c\xc2\xf8\x2c\x5d\xbf\xb9\x0a\x80\ \x13\x82\xb0\x03\xa0\x4d\x8a\x64\xd8\xca\xbb\x10\x58\x24\xe3\x04\ \x80\x45\x94\x65\xe5\x94\x2c\xcc\xea\x53\x76\x45\xba\xc7\xe2\xa4\ \xa9\x66\x97\x48\x50\x79\xc2\xd7\xcc\xc4\xc1\x84\xd1\x19\x41\xf3\ \x0d\x57\x09\x38\x28\x01\x1b\x09\xea\x46\x93\xdb\xb8\xe3\x9a\xe1\ \xc2\xa9\x77\x50\x8c\x69\x85\x13\x00\x16\xf6\xde\x2d\xa6\x05\x0e\ \x0f\x3d\x66\x77\x8b\x99\x66\x37\x06\xc9\xfc\x54\x58\x37\x71\x51\ \xc0\x4d\x63\x82\xf6\x07\xe0\x20\x88\xed\x12\x68\xa3\xa2\x4a\xeb\ \x67\xe4\x8b\xc3\x7a\x07\xc4\x58\x20\x71\x02\xc0\xc2\xce\x3b\x07\ \x29\x41\x52\xd5\x67\x6c\x2e\xf1\x70\x87\x03\x53\xec\x1e\x61\xd4\ \x3b\x26\xe6\x65\x94\x80\xdb\xc7\x07\x6d\x02\x70\xa1\x6a\x41\x58\ \xaf\x0a\x5a\x63\x76\x1b\x36\xf2\xea\x00\x0b\x37\x9c\x00\xb0\xb0\ \xf0\xce\x41\x1a\xae\xb8\xd5\x9f\xdb\x3c\xd2\x6d\x1d\x0e\xe4\xf1\ \x96\xbc\xe0\x14\x6d\x00\x6e\x1d\x17\x32\x09\xc0\xb9\x6c\x02\x62\ \x03\x48\xfd\x2c\x9a\x0c\x9f\x4d\xcc\x17\xed\x7a\x07\xc4\xd8\x40\ \xf1\x9b\x24\x0b\x59\xcb\x8b\x68\x9c\x83\xf0\x82\xc5\x8d\xbb\x4c\ \x76\x0c\xe6\xad\x79\xc1\xcf\xc7\xee\x80\xc1\x4e\x01\x68\x8f\x10\ \xe2\x23\xc5\x2d\xad\x98\x31\x4c\x34\xe8\x1d\x10\x63\xfd\xc1\x09\ \x00\x0b\x29\xcb\x0e\xd1\x24\x9b\x13\x0b\x2d\x76\xba\xd1\xec\x16\ \x89\x7a\xc7\xc3\xfa\xe6\xf2\x02\xc2\xe0\xa4\x90\x4f\x00\xce\xa5\ \x00\xb4\x93\x20\x56\x08\x49\x5a\x51\x98\x23\x5a\xf4\x0e\x88\x31\ \x5f\x71\x02\xc0\x82\xde\x6b\x3b\x29\xcb\x18\xa5\xfe\xc6\xea\x92\ \x16\x74\x3a\x91\xc9\xdb\xf4\x42\x53\x4e\x12\x61\x66\x41\x58\xff\ \xf0\x14\x80\xf6\x90\x10\xef\x38\xa2\xa4\xf7\xb9\xe7\x00\x0b\x76\ \x9c\x00\xb0\xa0\xf4\x46\x11\xc5\x47\xab\xea\x2f\xac\x1e\xf1\x78\ \xbb\x5d\xe4\xf3\xf2\x7e\x68\x8b\x8f\x02\xe6\x8e\x54\x11\x6d\xd0\ \x3b\x12\xcd\xd8\x01\x7c\x26\x88\x96\x95\xe7\x19\xd6\xf1\xd9\x05\ \x2c\x18\x71\x02\xc0\x82\xca\xd2\x7d\x34\xdf\xa6\xe0\x67\x26\x3b\ \x66\xb8\x14\x44\xce\x74\x11\xc6\x24\x00\x57\x0c\x57\x91\x1e\xaf\ \x77\x24\x3a\x11\x68\x00\xe1\x23\x52\xa5\x25\xd3\xf3\xc5\x21\xbd\ \xc3\x61\xec\x2c\x4e\x00\x98\xee\x76\xb4\x50\x12\xd9\xd5\x27\x0e\ \xd4\x88\x85\xad\x76\x91\xa6\x77\x3c\xcc\xbf\xc6\xea\xdf\x01\x30\ \x88\xd0\x4e\x00\x4b\x63\xa2\x0c\x1f\x4e\xcc\x12\x56\xbd\xa3\x61\ \x91\x8d\x13\x00\xa6\x9b\xa2\x7a\x1a\x27\x91\xfa\x78\xb3\x4d\x7c\ \xff\x50\xbd\x48\xb2\xba\xf5\x8e\x88\xf9\x5b\x94\x04\xdc\x34\x56\ \x85\x51\xd2\x3b\x92\xe0\x42\x80\x45\x22\xbc\x4f\x90\x5e\x2d\x1c\ \x22\x0e\xea\x1d\x0f\x8b\x4c\x9c\x00\x30\xcd\xdc\xb7\x82\x0c\x57\ \x66\x23\xfd\xca\x11\xca\x5c\x59\x48\xcf\xb6\x5a\x70\x5d\x55\x07\ \xd0\x68\xe1\x5f\xc3\x70\x35\x24\x85\x30\x63\x08\xdf\xfd\xf7\xa2\ \x98\x04\xfe\xe1\x52\xa4\x77\x67\xe7\x0b\x87\xde\xc1\xb0\xc8\xc1\ \xef\xbc\x2c\xe0\x7e\xf6\x29\x25\xa5\x27\xe1\x05\x99\xf0\x82\x42\ \x48\x89\x96\x20\x5c\x32\x10\x92\xed\x60\x22\x5c\x7c\x34\x30\x24\ \x99\x90\x12\x0b\x78\x14\xa0\xde\x02\x34\x5b\xbb\x7f\x1b\x99\x94\ \x4d\x18\x39\x88\x13\x00\x1f\x35\x43\xe0\x1f\xaa\x5b\x7a\x95\x7b\ \x0b\x30\x2d\x70\x02\xc0\x02\x66\xe1\x46\x2a\x88\x33\xe0\xbf\x9d\ \x0a\xee\x57\x08\xd1\x7a\xc7\xc3\xfa\x2f\x29\x06\x18\x93\x41\x18\ \x92\x42\x10\x17\xbc\x6b\x1c\x6a\x10\x38\xd5\xde\xf5\x5b\xc9\x94\ \x5c\xc2\xf0\x34\x4e\x00\xfa\xc8\x0d\x21\x3e\x85\x2a\x5e\x2a\x1c\ \x22\x76\xeb\x1d\x0c\x0b\x5f\x9c\x00\x30\xbf\xfb\xed\x16\x9a\x19\ \x6b\xc0\x9f\xad\x6e\xcc\x06\xf1\xef\x58\x28\x33\x1a\x80\x71\x99\ \x84\x11\x83\x08\xdd\x3d\xc6\x3f\xde\x22\x70\xbc\xf9\xe2\x1f\xb3\ \x80\xb7\xfa\x7f\x50\xa4\x56\xff\xfb\x81\x00\xed\x50\x05\x5e\x2a\ \xcc\x31\xac\x16\x42\xf0\xa2\x19\xf3\x2b\x7e\x73\x66\x7e\xb1\x70\ \x21\x49\x62\x36\xee\x89\x8e\xc2\xef\x1c\x1e\x8c\xd5\x3b\x1e\x36\ \x30\x42\x00\x43\xd3\x08\xe3\x32\x09\xb1\x3d\x1c\xa5\xe4\x56\x80\ \x2f\x4f\x49\xb0\x5f\x50\xc0\x29\x00\x4c\x08\xae\xa3\x7f\x43\x9a\ \x20\x9c\x82\x84\xbf\x75\xba\xa4\x37\xf8\x50\x22\xe6\x2f\x9c\x00\ \xb0\x01\x59\xb8\x82\xa2\xa5\x41\xf8\xbe\x00\xfe\xdd\xa3\x20\x43\ \xef\x78\xd8\xc0\x65\x25\x12\x2e\xc9\x26\x24\xc7\xf4\xfc\x3a\xb3\ \x13\x28\xaa\x95\x60\x76\x9d\xff\xf1\x28\x09\x98\x3e\x84\x90\x1d\ \x5e\x2d\x7f\x83\x45\x13\x01\xaf\x4b\x6e\xe9\x2f\xd3\x86\x8b\x4e\ \xbd\x83\x61\xa1\x8d\x13\x00\xd6\x2f\x0b\x57\x50\x74\x54\x06\x7e\ \xa0\x12\xfe\xd3\xa3\x20\x45\xef\x78\xd8\xc0\xa5\xc5\x01\xe3\xb2\ \x08\x83\x13\x7b\x9e\xb8\x89\x80\x8a\x76\x81\xe3\x4d\x02\x17\x76\ \x68\x34\x08\xe0\xaa\x11\x2a\x52\x62\x03\x18\x28\x03\x80\x4e\x00\ \x7f\x33\xca\xd2\x5f\x26\x0f\x15\x1d\x7a\x07\xc3\x42\x13\x27\x00\ \xac\x4f\xbe\x5b\x44\x51\x05\x26\xfc\x80\x80\xff\xf4\x28\x48\xd5\ \x3b\x1e\x36\x30\xb1\x46\xef\x1d\xff\xd0\x34\xf2\xe9\x59\xbd\xac\ \x02\xfb\x6b\x04\x9a\xba\xa9\xfc\x1f\x9a\x46\x98\x96\xcb\x77\xfe\ \x1a\xb2\x02\x78\x25\x46\x95\xfe\x8f\x8f\x28\x66\x7d\xc5\x09\x00\ \xf3\xc9\x77\x8b\x28\x6a\x98\x19\xdf\x53\x88\xfe\xcb\xa3\x08\x9e\ \xf8\x07\xc0\x28\x01\x83\x12\x08\x83\x13\x81\xd4\x38\x42\x8c\x01\ \x88\x36\x7a\xef\xac\x01\x40\x56\x00\x97\x02\x98\x9d\x02\x56\x37\ \xd0\x6a\x13\xe8\xe8\x62\x77\x78\xac\x11\xc8\x4b\x21\xa4\xc7\x01\ \x49\x31\x84\x18\x23\xce\xab\xd0\x97\x15\xc0\xad\x7a\xff\x5f\x56\ \xcf\xbf\x5b\x8f\x92\x80\xb8\x28\x42\x52\x2f\xcb\xfc\xe7\x72\x2b\ \xc0\xee\x6a\xa9\xcb\x58\xce\x9a\x30\x98\x30\x86\x9f\xfb\xeb\xc1\ \x0a\xe0\xcd\x28\xa3\xf4\x3f\x93\x06\x8b\x26\xbd\x83\x61\xa1\x81\ \x13\x00\xd6\xa3\x85\x5b\xc9\x68\x00\x9e\x82\x82\xff\xf1\xa8\x48\ \xd7\x3b\x9e\x50\x66\x10\xc0\xa8\x0c\xc2\xa8\x0c\x42\x54\x1f\x3b\ \xe3\xb5\x3b\x80\xbd\x67\x24\xb8\x64\xef\xbf\x8f\x1c\x44\x98\x30\ \x98\x60\xd0\xe8\x2f\xd8\xe1\x01\x76\x55\x4b\xb0\xb8\x7a\x7e\xdd\ \x88\x74\xc2\xe4\x1c\x4e\x00\x74\x64\x05\xf0\x8a\xea\x94\xfe\x30\ \x63\xa4\x30\xe9\x1d\x0c\x0b\x6e\xdc\xa0\x93\x75\x8d\x48\xfc\xd7\ \x7a\x7a\xcc\xa0\xa2\xce\xe3\xc1\xeb\x3c\xf9\x0f\x4c\x8c\x11\xb8\ \x72\xb8\x8a\xf1\x59\x7d\x9f\xfc\x01\x20\x3d\xce\xdb\x55\xef\x2c\ \x01\x68\x36\xf9\x5b\x5c\xc0\xb6\xaa\xde\x27\x7f\x00\xa8\x35\x09\ \x38\x3c\x81\x8f\x89\x75\x2b\x11\xc0\xcf\xa5\x58\xb5\xb2\xa4\x4e\ \xf9\xf9\xd6\x2a\xe2\x6a\x0c\xd6\x2d\x5e\x01\x60\x17\xf9\xaf\x4d\ \x74\x4d\x94\x84\x37\x9d\x32\x86\xe9\x1d\x4b\x38\x90\x04\x70\xd5\ \x70\x15\xa9\x71\x03\xbb\x4e\x55\x87\xc0\xc1\xfa\x6f\xff\x64\x53\ \xe3\x80\xb1\x19\x84\xec\xe4\xc0\x35\x5b\xb0\xb8\x80\x1d\xa7\xbf\ \x5d\x79\xf0\x45\x8c\x11\xc8\x4f\x21\x64\x26\x02\xf1\x51\xde\x47\ \x13\x11\x74\x0c\x70\xb0\x39\x43\xa0\x85\x95\xb9\x86\x77\xf8\x48\ \x62\x76\x21\x4e\x00\xd8\x37\x7e\xbd\x81\x46\xc7\x1b\x69\xa9\x53\ \x16\x73\xf4\x8e\x25\x9c\x0c\x4b\x23\x4c\xf5\x43\x61\x9c\xc9\x09\ \x6c\xad\xbc\x78\xf9\x20\x23\x01\x98\x59\xa0\xf6\x6b\x65\xa1\x27\ \x66\xa7\x77\xd9\xdf\xd9\x87\xc9\xbf\x3b\x06\x09\x48\x8e\xf1\xae\ \x62\x0c\x4d\x23\x3e\x1c\x48\x7b\xc7\x49\xa8\x2f\x4c\xcf\x8d\x5a\ \xa7\x77\x20\x2c\x78\x70\x02\xc0\xb0\x70\x1d\xa5\xc7\xc6\xe2\x25\ \x87\x07\x0f\x53\xf7\x0d\xdf\x58\x3f\x4d\xcf\x23\xe4\xa7\xfa\xe7\ \xb9\x78\x69\x83\xc0\xe9\x2e\xda\xee\xfa\xbb\xf8\xce\xec\x02\x76\ \xf6\xf1\xce\xdf\x57\xa9\xb1\xc0\x95\x23\x54\xcd\x1e\x61\xb0\x6f\ \x09\x88\x35\x8a\x22\x7e\x32\xa3\x40\x54\xea\x1d\x0b\xd3\x1f\xff\ \x09\x46\xb0\x85\x2b\x28\x3a\x26\x13\xbf\xf4\x28\xf8\xa5\xac\xa2\ \x0f\xf5\xe0\xac\xaf\x92\x63\x81\x84\x68\xef\x9d\xaf\xa2\x7a\x0f\ \xd2\xf1\xa8\x02\x1e\x19\x20\x01\xc4\x18\x80\xb4\x38\xc2\xc8\x0c\ \x42\x42\x54\xcf\xd7\xaa\x33\x79\x7b\xef\x77\x38\x00\x95\xbc\xcb\ \xeb\xa3\x06\x11\xc6\x64\xfa\x27\x01\x30\x3b\xbd\xcb\xfe\xee\x00\ \x2e\x18\xf3\x21\x41\xba\x72\x01\xf8\x8b\x3d\x5a\xfa\xfd\x15\x99\ \xc2\xa2\x77\x30\x4c\x3f\x9c\x00\x44\xa8\xdf\x6d\xa2\x7b\x85\x84\ \x57\xdd\x32\x32\xf5\x8e\x85\x7d\x2b\x4a\x02\xe6\x8e\x54\x91\xa0\ \xd3\xd1\x49\x76\x0f\xb0\xfd\x94\x04\x47\x00\xee\xfc\xcf\x95\x9b\ \x4c\xb8\x2c\x9f\x13\x00\x9d\xb5\x0a\x81\xff\x2e\xcf\x91\x5e\xe6\ \xfa\x80\xc8\xc4\x09\x40\x84\xf9\xf5\x17\x34\x36\x36\x16\xef\xbb\ \x3d\x98\xa6\x77\x2c\xac\x6b\xc3\xd2\x09\x53\x75\xd8\x4a\xe7\x92\ \x81\xed\x55\x12\xac\xee\xde\x5f\x3b\x50\xfe\xaa\x8b\x60\x7e\x51\ \x02\x21\xbd\x50\x98\x2b\xb6\xe9\x1d\x08\xd3\x16\x27\x00\x11\x62\ \xe1\x56\x8a\x8d\x06\xfe\xe0\x91\xf1\x23\x85\xc0\x35\xd9\x41\x4a\ \x08\xe0\x8a\xa1\x2a\x06\x25\x68\x3b\xae\xac\x00\xdb\xab\x25\x98\ \x7a\x68\xf2\xe3\x4f\x53\x72\x08\xc3\xd3\x39\x01\x08\x26\x02\x62\ \x8d\x64\x10\xcf\x4f\xc9\x16\x55\x7a\xc7\xc2\xb4\xc1\x05\x5f\x11\ \xe0\x77\x1b\xe8\x56\xa3\x8a\x5a\xa7\x07\x3f\xe1\xc9\x3f\xb8\x8d\ \xcb\x24\xcd\x27\x7f\x22\x60\x5f\xad\xd0\x6c\xf2\x17\x00\x1f\x14\ \x14\x84\x08\x74\xbb\xa2\xa8\x47\x0e\xd4\x29\xff\xb6\x95\xa8\x87\ \x33\x20\x59\xb8\xe0\x15\x80\x30\xf6\xab\x75\x94\x93\x18\x8d\x37\ \x9d\x32\x6e\xd6\x3b\x16\xd6\xbb\xa1\xa9\x84\xa9\x79\x81\xdb\xd3\ \xdf\x9d\xc3\x8d\x02\x95\x6d\xda\x8d\x9a\x16\x07\x5c\x3d\x82\x8f\ \xb6\x0f\x72\x07\x85\x24\x3d\x33\x2d\x47\xec\xd7\x3b\x10\x16\x38\ \x9c\x00\x84\xa1\x85\x0b\x49\xc2\x95\x78\x52\x22\xfc\x4d\x56\x31\ \xc0\xf6\x33\x4c\x0b\x39\x49\xde\xa2\x38\xa1\xf1\x5f\x64\x4d\xa7\ \x40\x71\x9d\xb6\x83\x4e\xc8\xf2\xdf\x8e\x05\x16\x50\x32\x80\x57\ \x0d\x06\xe9\x57\x53\xb2\x85\x4d\xef\x60\x98\xff\xf1\x23\x80\x30\ \xb3\x70\x03\x4d\x95\xae\xc0\x61\x55\xc1\x22\x9e\xfc\x43\x43\x66\ \x02\xe1\x52\x1d\x26\x7f\x93\x03\x28\xad\xd7\xfe\x1e\xa0\xc3\xa9\ \xf9\x90\xac\x7f\x8c\x00\x9e\x57\x65\xf5\x50\x49\x9d\xe7\x7a\xbd\ \x83\x61\xfe\xc7\x09\x40\x18\x39\x50\x45\xa9\x83\x93\xe8\x5d\x59\ \xc5\x04\xbd\x63\x61\xbe\x49\x8e\x05\x66\x16\x10\x24\x1d\xd6\xe2\ \xa2\x0c\xde\x53\x09\xb5\xd6\x60\x16\x68\xb1\xf1\xe2\x63\xa8\x20\ \x81\x11\x80\xb4\xa1\xa4\x56\x79\x67\x6f\x2d\x0d\xd2\x3b\x1e\xe6\ \x3f\xfc\x57\x18\x26\x8a\xeb\xe4\x3b\x04\xc4\x6b\x0e\x0f\xf2\x36\ \x96\x4b\x50\x79\x85\x35\xe8\x49\x02\xb8\x76\x94\x8a\x44\x9d\xf6\ \xfc\x9f\x65\xf3\x00\x1d\x76\xef\x21\x3e\x17\x36\xff\x91\x55\x6f\ \xb3\x21\xa7\x07\x30\xbb\xfc\x77\xd0\x4f\x4e\x32\x61\x26\xf7\x01\ \x08\x45\x4d\x20\xfa\x59\xe1\x10\xe3\x3b\x7a\x07\xc2\x06\x8e\x13\ \x80\x10\x57\x72\x86\x72\x61\xa0\xd7\x01\xba\xe3\xec\xc7\x76\x55\ \x0b\x34\x5b\xf9\x47\x1b\xec\x46\x0f\x22\x4c\xcc\x0e\xad\x49\xd0\ \xad\x00\x66\xa7\x80\xc9\xe9\x6d\x17\xec\xf0\x00\x4e\x59\xc0\xe1\ \x06\x3c\x7d\xa8\xeb\x8b\x31\x02\xb7\x8c\xe5\x42\xc0\x50\x45\x10\ \xab\xc9\x23\xbe\x3f\x63\x98\x68\xd0\x3b\x16\xd6\x7f\x3c\x4b\x84\ \xb0\xe2\x7a\xf9\x6e\x41\x62\x11\x80\xf3\x96\xe5\x0e\x35\x78\x5b\ \xc5\xb2\xe0\x76\xc3\x18\xb5\xd7\xb6\xbf\xa1\xc4\xad\x00\x76\x37\ \xd0\x66\x17\xa8\x6c\x17\xb0\xf7\xd0\x50\xc8\x20\x80\x3b\x26\x70\ \x02\x10\xe2\x5a\x05\xe8\xe9\x69\x79\xc6\x4f\xf5\x0e\x84\xf5\x0f\ \xd7\x00\x84\xa0\x5d\x35\x14\x57\x52\xa7\xfc\x55\x90\x58\x85\x0b\ \x26\x7f\x00\x01\x39\xc0\x85\xf9\x57\x7c\x34\xc2\x6a\xf2\x07\xbc\ \x67\x12\xa4\xc6\x01\x23\x07\x11\xae\x1d\xa9\x62\x70\x0f\x7b\xfd\ \xf5\x6a\x75\xcc\xfc\x2a\x83\x20\x56\x97\xd4\x2a\xef\x94\x35\x53\ \xa2\xde\xc1\xb0\xbe\xe3\x04\x20\xc4\x94\xd4\xd3\x8c\x58\x49\x2d\ \x05\xf0\x7c\x57\x9f\xb7\xb9\x81\x06\x0b\xdf\xfd\x07\x3b\x63\x98\ \xff\x88\x8c\x12\x30\x2b\x9f\x30\xa2\x9b\x6e\x7f\x63\x79\x1b\x60\ \xf8\x10\x78\xc4\xe9\x51\x8b\x8a\xeb\x69\xba\xde\xa1\xb0\xbe\x09\ \xf3\xb7\xa1\xf0\x41\x44\xa2\xb4\x41\x7d\x9e\x08\xff\x07\xa0\xcb\ \xfb\xa7\x56\x9b\x77\x4f\xb7\xbf\x0a\xb5\x58\xe0\x24\x46\x03\xd7\ \x8f\xf6\xef\x12\xb8\x4a\x00\x44\xf0\x65\xf5\x75\x66\x81\x53\x6d\ \x02\x2e\x19\x88\x8f\x26\x8c\x1c\x04\x0c\x4e\xe4\x04\x20\x0c\xc9\ \x04\xfc\xbe\x32\x57\xfa\x1d\x1f\x2e\x14\x1a\x38\x01\x08\x01\xfb\ \xea\x28\xdf\x48\xca\xbb\x10\xe2\x6a\x59\xf5\x56\x64\x3b\x65\x01\ \xab\x0b\xb0\xb8\x01\x8b\x0b\xb0\xba\x7b\x7e\xe6\xca\x82\x8b\x10\ \xc0\x8d\x63\x54\xc4\xf9\xa1\xe1\x6a\x93\x45\xe0\x58\x8b\x40\xa7\ \x03\xc8\x4f\x25\x4c\xcf\xe3\xc9\x95\xe9\x87\x84\xd8\x6a\x54\xc5\ \xa3\x53\x86\x88\x5a\xbd\x63\x61\x3d\xe3\x04\x20\xc8\x2d\xde\xa7\ \xfc\xa5\xcd\x2e\xfd\x50\x56\x61\x50\x54\xef\xb6\x2c\x16\x1e\x06\ \x7a\x22\x1e\x01\x38\xda\x28\x50\x7e\x4e\x1b\x5f\x01\xe0\x9a\x91\ \x2a\x92\x63\x07\x1e\x1f\x63\xfd\x26\x60\x12\xa0\xef\x4f\xcb\x35\ \x2e\xd7\x3b\x14\xd6\x3d\x4e\x00\x82\xd4\x3b\x15\x94\x65\x6d\xc5\ \xf6\x46\x33\xc6\xe8\x1d\x0b\x0b\x9c\x59\x05\xd4\xaf\x83\x71\x88\ \x80\xd2\x06\x81\xea\x8e\x8b\xff\x84\xa7\xe6\x12\x86\xa5\xf1\x2a\ \x00\x0b\x0a\x6f\xc6\x44\x49\x3f\x9e\x98\x25\xac\x7a\x07\xc2\x2e\ \x16\x6c\x8f\x0b\x19\x80\x77\x8a\xe9\xa6\xa6\x3a\x54\xfb\x63\xf2\ \x8f\x36\x00\x19\x09\x84\xdc\x64\xc2\xa0\x78\x68\xde\x6e\x96\xf5\ \x6c\x7f\xad\x40\x53\x17\x45\x9b\x66\x17\x50\xde\x26\x50\xd3\x29\ \x40\x17\xcc\xe5\x44\xc0\x81\xfa\xae\x27\x7f\x00\x88\x0d\xb3\xdd\ \x05\x2c\xa4\x3d\xe9\xf4\xa8\x45\x25\x0d\xc4\xdd\x49\x83\x10\x4f\ \x07\x41\x66\xd1\x3e\xfa\x53\xa3\x05\x3f\xf5\x28\xdf\xfe\x6c\xa2\ \x24\x20\x3e\x06\x88\x96\x08\x06\x01\x98\x7c\xe8\xc8\x96\x95\x48\ \x18\x9b\x41\x48\xbf\x60\xd2\xb7\xb9\x81\xf2\x56\x81\x33\x9d\xe2\ \xa2\x6e\x81\x67\x5f\x77\xe1\x84\xc3\x02\x4b\x08\x60\x64\x3a\x21\ \x2f\x85\x20\xab\x02\xa7\xda\x81\x46\xb3\xc0\xd9\x1f\xc3\xf0\x74\ \xc2\xe4\x1c\xef\x29\x81\x1e\x15\x28\xea\x26\x69\x00\xbc\xdb\xf0\ \xae\x1e\xae\x72\xa2\xc7\x82\x0a\x01\x16\x41\xf4\x64\xe1\x10\xe3\ \xc7\x7a\xc7\xc2\xbe\xc5\x6f\x13\x41\x62\x61\x19\x25\xa6\x77\xe2\ \xab\x56\x1b\x0a\x25\xe1\x9d\xc0\x73\x92\x80\xf4\x78\x42\x62\xf4\ \xf9\x93\x38\x11\xb0\xb5\x52\x82\xd9\x75\xf1\x75\x84\x00\xa6\xe6\ \x10\x86\xf6\xb2\x04\xec\x94\x81\x3a\x93\x80\xd9\xe5\xed\xca\x96\ \x1e\x0f\x0c\x8a\x23\x18\x25\xa0\xc6\x2c\x70\xa8\x5e\x70\xbd\x41\ \x10\xc9\x49\x22\x24\xc7\x02\xa7\x3b\x44\xb7\x7d\x1e\xd2\xe2\x80\ \xcb\x87\xaa\x88\x36\x68\x1b\x1b\x63\x3e\x22\x00\xff\x57\x91\x2b\ \xfd\x3b\xef\x12\x08\x0e\x9c\x00\x04\x81\x37\x8a\xa8\xd0\x64\xc3\ \x97\x1e\x05\x49\xa3\x06\x79\x9f\xdf\x46\xf5\xf0\x26\xee\x51\x81\ \xf5\x27\xa4\x2e\x27\xe8\x58\x23\x70\xb3\x1f\x5a\xac\x36\x98\x05\ \xf6\xd6\xf0\xaf\x47\x28\x30\x48\xc0\x98\x0c\xc2\xe8\x0c\x7d\x0e\ \x15\x62\xac\x4f\x04\x7d\x69\x90\x0c\xf7\x4f\xc9\x16\xcd\x7a\x87\ \x12\xe9\xb8\x06\x40\x67\xaf\xef\xa5\xe7\x9a\x3b\x69\xbf\x00\x92\ \xae\x19\xa9\x62\x74\x46\xcf\x93\x3f\x00\x94\x35\x75\x7f\x77\xee\ \x94\x81\x56\xfb\xc0\xe3\xca\x49\x26\x24\xc7\x0c\xfc\x3a\x2c\x70\ \xa2\x24\x60\x74\x06\xe1\x86\xd1\x2a\xc6\x66\xf2\xe4\xcf\x42\x04\ \x89\xb9\x8a\xa2\x16\x95\xd6\xd0\x65\x7a\x87\x12\xe9\xf8\x2d\x43\ \x47\xaf\xed\x52\x96\x37\x5a\xa5\x07\x0c\x12\x70\xcd\xa8\xde\xfb\ \xc2\x77\x38\x80\x63\xcd\xbd\x1f\xf4\x93\x12\x07\xcc\xf5\xc3\x73\ \xe0\x9e\x0a\xcd\x98\x3e\x84\x00\x32\xe2\x09\x05\xa9\x40\x6e\x32\ \xc1\xc0\x29\x3c\x0b\x5d\x2e\x21\xe8\x87\xd3\x72\x8d\x8b\xf5\x0e\ \x24\x52\xf9\xa1\x0d\x09\xeb\xab\xa5\x07\x28\xd5\x62\xa7\xfd\x0d\ \x56\x31\x0a\x00\x26\x66\x53\xb7\x93\x3f\x01\x68\xb6\x08\x9c\x68\ \x15\x68\xf7\xf1\xce\xde\xe4\xf0\x36\x08\x1a\xe8\x1d\xbc\xca\x35\ \x00\x3e\x33\x4a\xde\xa5\x78\xb7\xe2\xff\x22\x4a\x01\x6f\x71\x5f\ \x4e\x12\x61\x48\x2a\x21\x9e\xab\xfc\x59\x78\x88\x21\x12\x8b\x4a\ \x6a\x95\xab\x9c\x24\x3d\x3b\x3b\x5f\x38\xf4\x0e\x28\xd2\x70\x02\ \xa0\xb1\xa5\xfb\xe8\xd2\x16\x33\x7d\x69\x75\x89\x78\x00\x18\x94\ \x00\x0c\x4b\xed\x7a\xc6\x30\xbb\x80\x92\x3a\x09\x9d\x7d\xfc\xb3\ \x48\x8e\xf5\xd6\x02\x0c\x84\xa2\x02\xcd\x36\xbe\xfb\xef\x4e\x72\ \x0c\x90\x9b\x42\xc8\x48\x20\xa4\xc6\x7a\x13\x00\xc0\xfb\x7d\xdb\ \x50\x2e\x0d\xf8\x40\x26\xa3\x01\xc8\x88\x23\x64\x27\x01\xd9\xc9\ \x34\xe0\x9f\x27\x63\x41\x4b\xe0\x91\x58\xa1\x8c\xde\xd7\x4c\x77\ \x5f\x96\x25\x1a\xf5\x0e\x27\x92\xf0\x3b\xbc\x86\x16\xef\xa7\x47\ \x9a\x2c\x58\xea\x92\x61\x00\xbc\xcf\x70\xe7\x8e\x54\xbb\x3c\x19\ \xad\xb2\x4d\xa0\xac\xe9\xe2\xad\x7a\xbe\xb8\x66\xa4\x8a\x14\x1f\ \x3a\xc1\xc9\x2a\x70\xaa\x5d\xa0\xc9\x2a\x10\x25\x08\x83\x12\x80\ \xac\x04\x6f\x0d\xc2\xb1\x16\xef\x1e\x74\xf6\x2d\x83\x00\x86\xa4\ \x7a\x0f\xb8\xe9\xee\xfb\x7b\xb8\x51\xa0\xb2\xad\xef\xdf\x37\xa3\ \xe4\xdd\xf1\x91\x11\x0f\x64\x26\x7a\x93\x0a\xde\xca\xc7\x22\x4c\ \x1d\x48\xba\xad\x70\x88\x38\xa8\x77\x20\x91\x82\xdf\x62\x34\xf2\ \xda\x5e\xfa\x7d\x93\x19\xbf\x3a\x3b\xa1\x0b\x01\xcc\xcc\xbf\xb8\ \x0b\x1c\x01\x38\xd4\x20\x50\xd5\xde\xbf\x1f\x4d\x5a\x1c\x70\xf5\ \x88\xde\xd7\xee\x9d\x32\xb0\xbb\x5a\x82\xc9\xd9\xaf\x61\x22\x4a\ \x7c\x94\x77\x2f\xfe\xd0\x34\xea\x76\x8b\x9d\xc5\xe5\xfd\xb9\xb5\ \xf8\xb8\x6a\x22\x09\xef\xd6\xcb\x8c\x04\x42\x56\x02\x21\x35\x0e\ \x5c\xc4\xc7\x22\x1e\x01\x16\x08\x75\xc1\xf4\xdc\xa8\x75\x7a\xc7\ \x12\x09\xf8\x2d\x27\xd0\x88\xc4\x2b\xbb\x69\x65\xb3\x55\xdc\x7d\ \x76\xaa\x17\x02\x28\xcc\x23\xe4\xa7\x5c\x7c\x7b\xdf\xdf\x3b\xc8\ \xb3\x86\xa5\x13\xa6\xe6\xf4\xbc\x6c\xe0\x51\x80\x6d\x55\x12\x2c\ \x5d\xf4\x11\x60\xdf\xca\x88\xf7\x9e\x5c\x97\x9d\x44\xdd\xde\x8d\ \x13\x80\x63\x4d\x02\x15\xad\x02\xbd\xa5\x5d\x42\x00\x99\x09\x84\ \xbc\x64\xef\x2e\x0b\xde\xaf\xcf\x58\x97\x64\x12\xf8\xc1\xf4\x5c\ \xc3\x3f\xf4\x0e\x24\xdc\xf1\x93\xc5\x00\xfa\xdb\x5a\x8a\xa1\x1d\ \xd8\xdb\xe4\x10\x53\xce\x7e\x4c\x00\x98\x92\xdd\xf5\xe4\x5f\xd5\ \x31\xb0\xc9\x1f\x80\x4f\x93\x4a\x69\x83\xe0\xc9\xbf\x0b\xdf\x14\ \xdb\x25\x7b\x5b\x27\x27\x76\x79\xe8\xf2\xf9\xaa\xda\x05\x4e\xb6\ \xf6\xfe\x33\x1b\x92\x42\x98\x98\x4d\x7e\x39\xfd\x8f\x69\xab\xe4\ \x48\x39\x52\x93\x13\x31\xa2\x20\x47\xef\x50\x22\x85\x51\x10\xde\ \x28\xa9\x53\x26\x4e\xcb\x95\x7e\x22\x84\xe0\xde\xa4\x01\xc2\x6f\ \x47\x01\xf2\xca\x56\xca\x76\x4a\x28\x35\x39\x30\xf8\xdc\x8f\x4f\ \xc8\x26\x0c\x4b\xbf\xf8\xf7\xd9\xec\x04\x0e\x37\x0c\x7c\x41\xc6\ \xdd\x4b\xf1\x59\xbd\x59\xa0\xce\xc4\x0b\x3f\x67\x89\xaf\x97\xe2\ \x73\x93\xbc\x93\x7e\x5c\x1f\x2b\xec\x9b\x7b\x39\xe2\x24\xda\x00\ \x4c\xc9\x25\xe4\x25\xf3\x7b\x58\xa8\x69\xed\x30\xe1\xb5\x65\x6b\ \xb0\x61\x5b\x31\x5e\xfc\xf5\x73\x9c\x00\x68\xef\xf9\xd2\x7a\x35\ \x7b\x6b\x15\x3d\x76\xcd\x70\xc1\x0f\x2b\x03\x80\x13\x80\x00\x78\ \x79\x37\x15\x5a\x5c\xd8\x6e\x77\x21\xfe\xdc\x8f\x8f\xcd\x24\x8c\ \x1e\x74\xf1\x44\x40\x00\x0e\x36\x4a\xfd\x2a\xf8\xbb\x90\xc9\x29\ \xbe\xbe\xe2\xc5\xdc\x0a\x70\x70\x80\x49\x86\x10\xde\x67\xe2\xb1\ \x51\x40\xb4\x81\x10\x63\xf0\x16\xb0\x49\xc2\xfb\x3f\xa7\xec\x3d\ \x6f\xa0\xdd\x2e\xa0\x04\xe9\x9c\x27\xce\x69\xb5\x9c\x93\x44\x88\ \x19\xc0\x5f\x41\x4f\x4d\x9b\x06\x27\x11\xa6\xe5\x72\x05\x7f\xa8\ \x91\x15\x05\xab\xd6\xed\xc4\xa2\x0f\xd6\xc2\xe1\xe0\xa5\x32\x3d\ \x11\xb0\x20\x25\x5a\xc9\x2b\xaa\xa7\x79\x33\x72\x45\xab\xde\xf1\ \x84\x1b\x7e\x6b\xf2\xb3\xc5\x7b\xe9\xb6\x46\x2b\x56\xbb\x95\xf3\ \xbf\xb7\x79\x29\x84\xf1\x59\x5d\xcf\x88\x8d\x66\x81\x36\x9b\x7f\ \xc6\xef\x74\x7a\x27\xe1\xae\x26\x9d\xb2\xa6\xee\xfb\xc8\xf7\x26\ \x27\x99\x30\x22\xdd\x5b\xa9\x6e\xf0\x21\x87\x70\x78\x08\x1b\xcb\ \xfd\x93\xd4\xf8\x4b\x52\x0c\x50\x90\xe6\x7d\xfc\xe2\xaf\x49\x39\ \x33\x11\xa8\xe9\x3c\xff\x63\x71\x51\xc0\xe4\x6c\x42\x0e\xdf\xf5\ \x87\x9c\x92\xb2\x0a\xbc\xb4\x78\x25\xaa\x6a\x78\x37\x5a\xf0\x10\ \x73\x24\x52\x77\x1f\xa8\xa1\x5b\xa7\xe5\x8b\x72\xbd\xa3\x09\x27\ \x9c\x00\xf8\xd1\x92\xbd\xf4\x5c\xbd\x05\xaf\x7a\xd4\xf3\x8b\x2b\ \xe3\xa2\x80\x29\x3d\x14\xe6\xf9\xf2\x0c\xd9\x57\x44\x40\x45\xab\ \xc0\x25\xd9\xe7\x8f\x57\x6f\xee\x7f\x57\xbf\x31\x19\x84\x09\x83\ \xfb\x36\x99\x35\x5a\xfb\xb7\x85\x31\x10\x52\xe3\x80\x71\x99\x84\ \xc1\x49\xe4\xf7\xaa\xd7\xfc\x64\x82\x65\x90\xf7\xbf\x37\x3e\x8a\ \x90\x9f\xea\xed\x0f\xc0\x0d\xfa\x42\x4b\x4b\xbb\x09\xaf\xbf\xb7\ \x06\xeb\xbf\x2a\xd2\x3b\x14\xd6\xb5\x51\x24\xa9\xdb\x8b\xea\xe9\ \xd6\x19\xb9\xa2\x44\xef\x60\xc2\x05\x27\x00\x7e\xb2\x68\xaf\xf2\ \xef\x35\x66\xfa\x6f\x95\x2e\x9e\x62\xa6\xe5\x76\x5f\xf1\xdd\xee\ \xf0\xb6\xf8\xf5\xa7\x53\x6d\x02\x19\x09\xf8\x66\x8b\x61\xab\x4d\ \xa0\xa4\xbe\xff\x53\x5f\xbd\x59\xc0\x68\xf0\xf6\x08\x48\xe9\x65\ \x7f\xba\x43\xf6\x8e\x5f\x31\xc0\x62\x46\x7f\x88\x31\x7a\x4f\x46\ \x0c\xe4\x9d\xb8\x10\xde\x4e\x8e\x13\xbb\x79\xec\xc2\x82\xdb\xd9\ \xe5\xfe\xc5\xef\xaf\x85\xdd\xc9\xcb\xfd\x41\x6e\xb0\x04\x75\xcb\ \x81\x3a\xba\x6d\x5a\x9e\xd8\xa9\x77\x30\xe1\x80\x13\x00\x3f\x78\ \x6d\x37\xfd\xb5\xd6\x84\xe7\xbb\x9a\x02\xf2\x92\x09\x59\x89\xdd\ \x4f\x0e\x35\x01\xe8\xb5\xaf\x02\xd8\x5b\x23\x90\x9d\xe4\x3d\xed\ \xa9\xc1\xdc\xfb\x16\xb5\x9e\x58\xdd\xc0\xd1\x26\x81\xa3\x10\x30\ \x4a\x40\x42\x0c\x90\x18\x45\x88\x32\x7a\x9b\x19\xa9\x04\xb8\x64\ \xc0\xec\x14\x5d\x1e\x51\xac\x87\xac\x44\xc2\xf4\xbc\x81\x3d\xdf\ \x67\xe1\xad\xf8\x70\x39\x5e\x5c\xb2\x12\xd5\xb5\x4d\x7a\x87\xc2\ \x7c\x45\x48\x21\xa8\x1b\x0e\xd4\x79\xe6\x4d\xcb\x8b\xda\xa8\x77\ \x38\xa1\x8e\xdf\x1e\x07\xe8\x95\xdd\xf4\x41\xa3\x05\xf7\x77\x35\ \xc5\x4b\x02\x17\x2d\xc5\x9f\x8b\x08\xa8\x33\x07\xe6\x4e\x99\xc8\ \x3b\xf1\xfb\x9b\xac\x7a\xcf\x1a\x30\x39\xf4\xbf\xc3\xef\x4e\x7e\ \x2a\xa1\x30\xb7\xfb\xbd\xfb\x2c\xb2\x35\xb7\x75\xe2\x8d\xe5\xff\ \xe4\xe5\xfe\xd0\x15\x4f\x90\x3e\x2f\xae\x97\x1f\x98\x9e\x6b\xfc\ \x44\xef\x60\x42\x19\x27\x00\x03\xf0\xf2\x2e\x75\x53\x93\x05\xd7\ \x75\xf7\xf9\x82\xd4\x9e\xb7\x95\x75\x3a\xbd\x95\xf9\xcc\x7f\xd2\ \xe2\x80\x69\x79\xfe\x7f\xd6\xcf\x42\x9f\xcb\xed\xc1\x7b\xab\x37\ \x63\xd9\x27\x5b\xe0\xf6\x78\xf4\x0e\x87\x0d\x4c\x8c\x80\xf8\xb0\ \xb8\x56\x7e\x78\xfa\x10\xe3\x0a\xbd\x83\x09\x55\x9c\x00\xf4\x07\ \x91\x78\x65\x17\xbe\x6a\xb2\xe2\xca\x9e\x5e\x36\x2a\xa3\xe7\xe7\ \xc2\x81\xb8\x43\x8f\x64\x02\xc0\xd4\x5c\xd5\xa7\x02\x3c\x55\x25\ \x58\x6d\x76\x98\x6d\x0e\x58\xac\x76\xd8\xec\x4e\xd8\x1d\x2e\xb8\ \x3c\x1e\x38\x1c\x2e\xd8\x9d\x2e\xb8\xdd\x1e\xd8\xbe\xde\x06\xa6\ \x28\xca\x45\xcf\x88\xdd\x6e\x0f\xdc\xee\xf3\x27\x12\xa7\xcb\x03\ \x83\x41\x42\x94\xf1\xdb\xa2\x8f\xa8\xe8\x28\xc4\x44\x7b\x33\xc1\ \x98\xa8\x28\x44\x47\x47\x21\x3e\x2e\x1a\x46\x83\x01\x49\x09\xf1\ \x30\x18\x24\x24\xc4\xc7\x21\x39\x29\x1e\x49\x09\x71\x48\x4a\x88\ \x43\x72\x62\x02\xe2\xe3\x06\x78\x9c\x23\xfb\xc6\xf6\xfd\x47\xf0\ \xf7\xb7\x56\xa3\xbe\xa9\x4d\xef\x50\x98\xbf\x10\xa2\x84\x10\xcb\ \x4b\xea\xe4\xc4\xc2\x3c\xe3\x9b\x7a\x87\x13\x8a\x38\x01\xe8\x2b\ \x22\xf1\xb7\x5d\xd8\xd9\x64\xc3\xe5\x3d\xbd\x2c\x2d\x0e\x3d\x76\ \x92\x73\x78\xd0\xef\x7e\xff\xfd\x21\x04\x90\x14\x0d\x24\xc6\x10\ \x12\xa2\xbd\x8f\x27\x5c\x32\xd0\xe9\x14\x30\x39\xfd\x7f\x84\xad\ \x1e\x12\x25\x3b\xda\x9a\x3b\x50\x61\xb2\xa2\xad\xc3\x8c\x4e\xb3\ \x15\x6d\x9d\x16\xb4\x77\x9a\xd1\x69\xb2\xa2\xc3\x6c\x83\xd9\x6a\ \x83\xd9\x62\x87\xd5\x1e\xfc\x27\x8f\x1a\x0c\x12\x92\x13\x13\xbe\ \x4e\x0a\xe2\x91\x9c\x18\x87\xa4\xa4\x78\xa4\xa7\x24\x21\x2b\x23\ \x0d\x99\x83\x52\x90\x91\x96\x8c\xec\xcc\x74\xa4\xa7\x24\xc1\x60\ \xe0\xbd\x07\x17\xaa\x69\x68\xc1\x5f\x97\x7e\x82\x3d\x25\xc7\xf4\ \x0e\x85\x05\x86\x01\x10\x8b\x8b\xeb\x95\xe4\xe9\xb9\x86\xbf\xe8\ \x1d\x4c\xa8\xe1\x04\xa0\x0f\x16\x2e\x24\x29\x7d\x17\xed\x6f\xb5\ \x89\xc2\xde\x5e\xdb\x53\xe5\xb9\x47\x05\xf6\xd4\x48\xf0\x0c\xa4\ \x32\xcf\x07\xd1\x06\x6f\x33\x9a\xc1\x89\xde\xa2\xb8\xae\x76\x22\ \xd4\x98\xbc\xcd\x81\xe4\x10\x78\x14\xe1\xb0\xd9\xd0\xd1\xde\x02\ \x53\x47\x2b\x4c\x1d\xed\xb0\x58\x3a\x61\x31\x75\xc2\xd4\xd1\x06\ \x8b\xb9\x13\x6e\x57\x78\x35\x0b\x53\x14\x15\x1d\x26\x0b\x3a\x4c\ \x96\x5e\x5f\x2b\x49\xd2\xd7\x89\x41\x2a\x86\xe4\x64\x60\x58\x5e\ \x36\x0a\xf2\xb2\x30\x2c\x2f\x0b\xf9\xb9\x59\x11\x97\x1c\x38\x5d\ \x6e\xbc\xb3\x6a\x23\x3e\xf8\xf4\x4b\xb8\xe5\x01\x9e\xcd\xcc\x82\ \x9d\x10\x84\x97\x4a\xea\x94\xe4\xc2\x3c\xc3\x6f\xf5\x0e\x26\x94\ \x70\x02\xe0\xa3\xfb\x56\x90\x21\x3d\x17\xc5\xad\xb6\x6f\xfb\xfa\ \xf7\xa4\xab\x23\x7e\x01\xef\x79\xf1\xbb\xab\x25\x98\xfc\x74\x03\ \x2a\x7d\xdd\x99\x2f\xda\x08\x44\x49\x84\x68\x23\x90\x12\xeb\x6d\ \xd8\x93\xd6\xc3\x96\x3d\xb7\x02\x1c\x69\x14\x38\x13\x64\x47\xfe\ \x7a\x3c\x6e\xb4\xb5\x34\xa2\xbd\xa5\x09\x1d\x6d\x2d\xe8\x68\x6b\ \x41\x67\x7b\x2b\x3a\xda\x5b\xe0\x72\x06\xff\x5d\xbb\x5e\x54\x55\ \x45\x6b\x87\x09\xad\x1d\x26\x1c\x2d\xaf\x3e\xef\x73\x7f\xff\xaf\ \x1f\x60\xda\xc4\x51\x3a\x45\xa6\xbd\xad\x7b\x0e\xe2\xe5\xb7\x3e\ \x45\x53\x6b\x87\xde\xa1\x30\x6d\xfd\x57\x49\x9d\x12\x5f\x98\x67\ \xf8\x85\xde\x81\x84\x0a\x4e\x00\x7c\xb0\x70\x2b\x19\x53\x8d\x28\ \x6d\xb5\x63\xa2\xaf\x5f\xd3\x55\x71\x9f\x4b\x06\xf6\xd5\x48\x68\ \xb7\x0f\x3c\x26\x21\xbc\xcd\x85\xf2\x53\x7d\xeb\xcc\x77\x96\x42\ \x5f\x1f\x60\xd3\x22\x74\x2d\x40\x54\x55\x15\x1d\xad\xcd\x68\x6d\ \x6e\x40\x4b\x53\x03\xda\x5a\x1a\xd0\xda\x54\x8f\xce\x8e\x36\x50\ \x38\x3c\x8f\x60\x9a\x3b\x53\xd7\x84\x17\x97\xac\x42\xd1\xa1\x93\ \x7a\x87\xc2\xf4\xf3\xf3\x92\x3a\xc5\x55\x98\x67\xf8\x4f\xbd\x03\ \x09\x05\x9c\x00\xf4\x62\xe1\x42\x92\x52\xa2\x51\xd2\x6e\xf3\x7d\ \xf2\x07\x80\xca\x36\x81\x9c\x24\x6f\xcb\x59\x22\xa0\xd6\x2c\x50\ \xd6\x28\xe0\xf4\xd3\x6a\x64\x66\x02\x61\x58\x9a\xef\x13\xa5\x4b\ \x06\xaa\x3b\x05\xaa\xda\x05\x1c\x1a\x17\x40\x13\xa9\x68\x6f\x6d\ \x46\x53\x5d\x0d\x1a\xeb\x6b\xd0\xd4\x50\x83\xe6\x86\x5a\x78\x3c\ \x6e\x6d\x03\x61\x61\xc9\xe9\x72\x63\xf9\xa7\x5b\xb0\x6c\xd5\x66\ \x5e\xee\x67\x00\xf0\x9b\x92\x7a\xc5\x51\x98\x6b\xf8\x5f\xbd\x03\ \x09\x76\x9c\x00\xf4\x84\x48\xa4\xed\xc4\xae\x36\x1b\x26\xf5\xf5\ \x4b\xad\x2e\x60\x63\xb9\x84\xf4\x38\x82\xd5\x25\xe0\xf0\xf3\xfb\ \x92\x2f\x8f\x74\x55\x00\x2d\x56\x81\x5a\x13\x50\x6f\xd2\xee\x70\ \x1e\xbb\xd5\x82\xda\x33\xa7\x50\x7f\xe6\x14\x1a\xeb\x6a\xd0\xdc\ \x58\x0b\xb7\x3b\x48\x3a\x04\xb1\xb0\xb2\x69\xe7\x01\xbc\xf2\xf6\ \xa7\x68\x69\x37\xe9\x1d\x0a\x0b\x26\x84\x3f\x94\xd4\x2a\xee\xc2\ \x21\x86\x17\xf5\x0e\x25\x98\x71\x02\xd0\x83\xbf\xed\xa4\xaf\x5a\ \xed\x62\x66\x7f\xbf\x5e\x51\x81\x16\x5b\x60\x9e\xb1\xb7\x58\x05\ \x2c\x2e\x42\xd2\x05\x3b\xc5\x3c\x8a\xf7\x24\xbe\x06\x8b\xb7\x85\ \xaf\x16\xcb\xfc\x56\x8b\x09\x75\x67\xaa\x50\x57\x7d\x0a\x75\x35\ \xa7\xd0\xd4\x50\x1b\x1e\xdb\x0a\x58\xd0\x3a\x53\xdf\x8c\xbf\x2c\ \x59\x85\x7d\x07\x4f\xe8\x1d\x0a\x0b\x56\x02\x7f\x3a\x50\x2f\x5b\ \xa6\xe5\x1a\x17\xe9\x1d\x4a\xb0\xe2\x04\xa0\x1b\xaf\xec\xa6\x8d\ \x4d\x96\x9e\xf7\xf9\xeb\x49\x56\x81\x2f\x2b\x25\x64\x24\x7a\x9b\ \x0d\x39\x3d\x80\xc3\xe3\x6d\xc5\x1b\xe8\xb9\xd7\x6e\xb5\xe0\x74\ \xe5\x71\x54\x55\x1c\x47\xcd\xe9\x72\x58\xcd\x7c\xf7\xc5\xb4\x61\ \xb7\x3b\xb1\x64\xc5\x3a\xac\xfc\x62\x07\x64\x25\x04\xb6\xae\x30\ \x3d\x09\x22\xf1\x5a\x71\xad\x6c\xe2\x66\x41\x5d\xe3\x04\xa0\x0b\ \xaf\xec\xa2\x8f\x9a\x2c\xb8\x5e\xef\x38\x7a\xa3\x10\xd0\x64\x09\ \x7c\x15\xbf\xaa\xaa\x68\x69\xac\x43\xe5\xc9\x23\xa8\x3c\x51\x86\ \xe6\x86\x5a\x2e\xd4\x63\x9a\xdb\x59\x54\x86\x3f\x2f\xfe\x18\xcd\ \xad\x9d\xbd\xbf\x98\x31\x2f\x83\x90\xc4\xb2\xe2\x3a\xd9\x31\x3d\ \xcf\xf8\xb9\xde\xc1\x04\x1b\x4e\x00\x2e\xf0\xf7\x5d\xca\xe2\x26\ \x2b\xee\xd5\x3b\x0e\xbd\x39\xec\x56\x54\x1c\x3b\x8c\x8a\xe3\x87\ \x71\xe6\x74\x39\x3c\x6e\x2e\xd8\x63\xfa\xa8\xa8\xae\xc3\x8b\x8b\ \x57\xe1\xd0\xb1\x53\x7a\x87\xc2\x42\x11\x21\x4a\x40\x7c\x5c\x5c\ \xef\xb9\x6b\x7a\x6e\xd4\x3a\xbd\xc3\x09\x26\x9c\x00\x9c\xe3\x95\ \x3d\xca\x1f\x9a\xcd\xd2\x53\x7a\xc7\xa1\x17\xb3\xa9\x03\xa7\x2b\ \x8e\xa1\xf2\xf8\x11\x54\x55\x1c\x83\xaa\x06\xb8\x53\x11\x63\x3d\ \xb0\xda\x1d\x58\xf2\xc1\x3a\xac\x5a\xbf\x03\x8a\xc2\xbf\x8b\x6c\ \x40\xa2\x05\x49\x2b\x4b\xea\xe9\x96\xc2\x5c\xb1\x4d\xef\x60\x82\ \x05\x27\x00\x5f\x5b\xbc\x87\x7e\x5c\x6b\xc6\x2f\x22\x6d\x61\xbb\ \xa3\xad\x05\x27\x8e\x1c\x40\xf9\xf1\x43\x68\xaa\xaf\xd1\x3b\x1c\ \xc6\x40\x44\xf8\xe2\xcb\xfd\x78\x6d\xd9\x1a\x9f\xba\x20\x32\xe6\ \xa3\x78\x40\xfd\xac\xa8\x9e\xae\x9d\x91\x2b\x4a\xf4\x0e\x26\x18\ \x70\x02\x00\xe0\xf5\xfd\x74\x57\xbd\x09\x2f\x45\xca\x3d\x86\xcb\ \x69\xc7\x89\xb2\x52\x94\x95\xee\x47\x5d\x4d\x15\x57\xec\xb3\xa0\ \x71\xb2\xaa\x16\x2f\x2d\x5e\x85\xc3\x27\xaa\xf4\x0e\x85\x85\x23\ \x42\x8a\x04\x75\x7d\x71\x0d\x5d\x3e\x3d\x5f\x54\xe8\x1d\x8e\xde\ \x22\x3e\x01\x78\x7d\x2f\xcd\x68\x35\x63\xa5\xac\x86\xf7\x09\xb2\ \x8a\x22\xe3\x74\xc5\x71\x1c\x3d\xb8\x1f\x15\xc7\x0f\x43\xe1\x0a\ \x6a\x16\x44\x2c\x36\x3b\xde\xfc\x70\x3d\x56\xae\xdb\xc1\x8f\x9e\ \x58\xa0\x65\x08\x49\xfd\xec\x50\x35\xcd\x99\x3c\x54\x44\x74\xbf\ \xe8\x88\x4e\x00\x5e\xd9\x47\xf9\xed\x36\x6c\x73\x2b\xe8\xe2\x98\ \x9c\xf0\xd0\xdc\x58\x87\x83\xfb\x77\xe2\xf8\x91\x12\xee\xa5\xcf\ \x82\x8e\xaa\x12\xd6\x6c\xde\x83\xd7\x97\xff\x13\x66\x8b\x4d\xef\ \x70\x58\xe4\x18\x2f\x1b\x95\xd5\xe5\xe5\x74\xe3\xe8\xd1\x22\x62\ \xbb\x94\x45\x6c\x02\xb0\x70\x2d\x25\x5b\xed\x28\x75\x7a\x10\xa7\ \x77\x2c\xfe\xa6\x28\x0a\x2a\x8e\x1f\xc6\xa1\xe2\x5d\xa8\xae\xe4\ \x46\x29\x2c\x38\x9d\x3c\x55\x8b\x3f\x2f\x5a\x89\xb2\xf2\xd3\x7a\ \x87\xc2\x22\x92\xb8\xca\x12\xaf\xbe\x45\x44\x0f\x0a\x21\x22\xf2\ \x39\x68\x44\x26\x00\xf7\xad\x20\x43\x42\x22\x4a\x2c\x4e\xa4\xeb\ \x1d\x8b\x3f\x59\xcd\x26\x1c\x2a\xd9\x85\x03\x7b\x77\xc0\x61\xb7\ \xea\x1d\x0e\x63\x5d\x32\x5b\xed\x58\xba\x82\x97\xfb\x59\x50\xf8\ \x4e\x49\xbd\x7a\x02\xc0\x42\xbd\x03\xd1\x43\x44\x26\x00\x73\x72\ \x68\x7d\xbb\x43\x8c\xd4\x3b\x0e\x7f\xa9\xa9\xaa\x40\xd1\xae\x2d\ \x38\x55\x7e\x94\x1b\xf4\xb0\xa0\xa5\xaa\x2a\x56\x6f\xdc\x8d\x45\ \xcb\xd7\xc2\x62\xf3\xc3\x91\x98\x8c\xf9\x81\x00\x7e\x73\xa0\x56\ \xae\x9a\x36\xc4\xf8\xb6\xde\xb1\x68\x2d\xe2\x12\x80\x57\x77\xd1\ \xff\x35\x5a\x71\x9d\xde\x71\x0c\x18\x11\x2a\x4f\x96\x61\xdf\xf6\ \x4d\xde\x4a\x7e\xc6\x82\xd8\xf1\xca\x33\x78\x71\xd1\x2a\x1c\xad\ \xa8\xd6\x3b\x14\xc6\x2e\x24\x48\x12\x8b\x4a\xea\x3c\x75\x85\x79\ \x51\x9b\xf4\x0e\x46\x4b\x11\x95\x00\xbc\xb6\x87\x9e\x6c\x32\xe3\ \xdf\xf4\x8e\x63\x20\x54\x45\xc1\xf1\xc3\x25\xd8\xbb\x63\x13\xda\ \x5a\x1a\xf5\x0e\x87\xb1\x1e\x99\x2d\x36\x2c\xfd\x68\x03\x56\xae\ \xdb\x0e\x55\xe5\xd5\x29\x16\xa4\x08\x51\x80\xb4\x72\x4f\x0d\x5d\ \x39\x2b\x5f\x1c\xd2\x3b\x1c\xad\x44\x4c\x02\xf0\xc6\x1e\xba\xa2\ \xd9\x8a\x45\xa1\xfa\xc4\x51\xf6\x78\x70\xa8\x64\x17\x8a\x76\x6e\ \x85\xd9\x14\xd1\x3b\x57\x58\x08\x50\x14\x15\x2b\xd7\xed\xc0\x92\ \x0f\xd7\xc1\x66\xe7\xdd\x27\x2c\x24\x24\x1f\x6f\xa6\x6d\x6f\x14\ \xd1\x4d\xcf\xce\x10\x7b\xf5\x0e\x46\x0b\x11\x91\x00\xbc\xb1\x8d\ \x72\x3a\x1c\xd8\xe8\x51\x21\xe9\x1d\x4b\x5f\x11\x11\x4e\x1e\x3d\ \x88\x6d\x1b\x3f\x83\xa9\xa3\x4d\xef\x70\x18\xeb\x55\xe9\xd1\x4a\ \xbc\xb4\x64\x15\x2a\xab\xeb\xf5\x0e\x85\xb1\x3e\x69\x30\x8b\x14\ \x83\x84\xcd\x4b\xb7\xd2\x90\x27\xae\x11\x61\x7f\xea\x54\xd8\x27\ \x00\x0b\xb7\x92\xd1\x02\x2a\xb2\x7b\x44\xac\xde\xb1\xf4\x55\x75\ \xe5\x09\x6c\x5d\xbf\x1a\xad\x4d\xfc\x46\xca\x82\x5f\x5b\xa7\x19\ \xaf\xbe\xfb\x39\x36\x6c\x2b\xe6\x62\x54\x16\x72\x08\x80\x4b\x06\ \x54\x42\x42\x5c\x02\xf6\x02\x18\xab\x77\x4c\x81\x16\xf6\x09\xc0\ \xa0\x18\xac\x69\xb1\x8a\x5c\xbd\xe3\xe8\x8b\xba\x33\xa7\xb0\x7d\ \xd3\xe7\xa8\xad\xe6\xd3\xcf\x58\xf0\x3b\xbb\xdc\xbf\xf8\xc3\x2f\ \x60\xb7\x3b\xf5\x0e\x87\xb1\x7e\x11\x00\x06\xc5\x13\x5a\x6c\x02\ \x2d\x36\x8c\x79\x63\x2f\xbd\xf7\xec\x4c\xf1\x90\xde\x71\x05\x52\ \x58\x27\x00\xaf\xec\xa4\x9f\x35\x5b\x71\x93\xde\x71\xf8\xaa\xb5\ \xa9\x1e\x5f\xae\xff\x14\xa7\x2b\x8f\xeb\x1d\x0a\x63\x3e\x29\x3e\ \x5c\x8e\x97\xde\x5c\x85\xd3\x35\x5c\x90\xca\x42\xdf\x98\x4c\xa0\ \xe5\xeb\x86\x94\x0d\x26\x3c\xf8\x66\x11\x6d\x7d\x72\x86\x58\xac\ \x6f\x54\x81\x13\xb6\x09\xc0\x2b\xbb\x68\x76\xab\x0d\x7f\x08\x85\ \x85\x48\x8f\xdb\x8d\xfd\xbb\x36\x63\xef\xb6\x8d\xdc\xa3\x9f\x85\ \x84\xd6\x0e\x13\x5e\x5b\xb6\x86\x97\xfb\x59\x58\xc9\x4c\x20\x64\ \x27\x01\x8d\x16\x01\x15\x40\xa3\x19\xaf\xff\x63\x1f\xed\xfe\xee\ \x65\xa2\x4c\xef\xd8\x02\x21\x2c\x13\x80\x85\xbb\x28\xdd\xe2\xc2\ \x06\x85\x82\xbf\xe8\xaf\xf2\xc4\x11\x6c\xfa\xe7\x47\xb0\x98\xc2\ \xbe\xde\x84\x85\x01\x59\x51\xb0\x6a\xdd\x4e\x2c\xfa\x60\x2d\x1c\ \x8e\x88\x6d\xa1\xce\xc2\xd8\xf8\x2c\x42\xa3\xc5\x7b\x36\x9c\x53\ \x86\xc1\xe2\xc4\x8e\x85\x55\x94\xb3\x70\xb8\x08\xbb\xe7\x5b\xe1\ \x97\x00\x10\x89\xa4\x6d\xb4\xd7\xe4\x11\x09\x7a\x87\xd2\x93\xf6\ \xd6\x66\x6c\x59\xbb\x92\x97\xfb\x59\xc8\x28\x39\x52\x8e\x17\x97\ \xf0\x72\x3f\x0b\x6f\x29\xb1\x40\x72\x2c\x60\xfe\x7a\xba\x37\x39\ \x91\x9a\xd3\x84\x2d\x00\x66\xeb\x1a\x58\x00\x84\x5d\x02\xf0\xb7\ \x5d\xea\xbb\xad\x2e\x69\x94\xde\x71\x74\xc7\xe3\x71\x63\xe7\x96\ \xb5\x28\xd9\xbb\x0d\x2a\x2f\xf7\xb3\x10\xd0\xd8\xd2\x8e\xbf\xbd\ \xb5\x1a\xdb\xf6\x1e\xd6\x3b\x14\xc6\x34\x91\x10\x4d\x30\x3b\xbf\ \x3d\x21\xbe\xd1\x82\xcb\xdf\x2b\x51\x7e\xfb\x50\xa1\xe1\x37\x3a\ \x86\xe5\x77\x61\x95\x00\xbc\xbe\x87\x1e\x6e\xb0\x20\x68\xab\x36\ \x1b\x6a\xab\xb1\x76\xd5\x32\x74\xb4\x35\xeb\x1d\x0a\x63\xbd\x3a\ \xbb\xdc\xbf\xf8\xfd\xb5\xb0\x3b\x79\xb9\x9f\x45\x0e\xf9\x82\x7b\ \x33\x22\xa0\xba\x53\xfa\xf5\xea\x13\xb4\x71\xde\x58\xb1\x5d\x9f\ \xa8\xfc\x2f\x6c\x12\x80\xd7\x0e\xd0\xb0\xb6\x0e\x5a\x4a\x24\x7a\ \x7f\xb1\xc6\x14\x45\xc1\xae\xad\x5f\x60\xdf\x8e\xcd\x20\x0a\xd5\ \x5e\x84\x25\xdb\xf2\xe7\x00\x00\x20\x00\x49\x44\x41\x54\x2c\x92\ \xd4\x37\xb5\xe1\x85\xdf\xbe\x8e\xba\xa6\x56\xbd\x43\x61\x4c\x73\ \x2e\xf9\xe2\x79\xc4\x29\x43\x9c\x6e\xa6\x2f\xf6\x94\x53\xee\xac\ \xd1\xc2\xac\x43\x58\x7e\x17\x1e\x09\x00\x91\x70\xee\xa0\xed\x2e\ \x59\x04\xdd\x7f\x4f\x6b\x73\x03\xd6\xae\x5a\x86\xe6\x86\x5a\xbd\ \x43\x61\xcc\x67\x4d\xad\x1d\x3c\xf9\xb3\x88\xa5\x74\xb3\xb1\xa5\ \xdd\x21\x12\x8e\x76\xd2\x66\x00\x97\x6a\x1a\x50\x80\x04\x7d\x95\ \xbc\x2f\x5e\xdd\xa3\x2e\xe9\x70\x88\x21\x7a\xc7\x71\x2e\x22\x15\ \xfb\x76\x6c\xc2\xbb\xaf\xff\x89\x27\x7f\xc6\x18\x0b\x21\xe9\xf1\ \xdd\x6f\x6d\xad\xee\x14\x33\x3e\x2c\x55\xfe\x43\xc3\x70\x02\x26\ \xe8\xee\x98\xfb\xea\xd5\x1d\x74\x4b\xb3\x95\x9e\xd0\x3b\x8e\x73\ \x59\xcd\x26\x7c\xfe\xd1\x5b\xa8\x3b\xc3\x9d\xfc\x18\x63\x2c\xd4\ \x4c\xca\x26\x38\x3d\x40\x8b\xed\xdb\x47\x01\x51\x12\x90\x18\xe3\ \x2d\x10\x54\x20\x7e\x55\x54\x4f\x6f\xcf\xc8\x15\x67\x74\x0c\x73\ \xc0\x42\x3a\x01\xf8\xdf\x8d\x94\x62\xf2\xd0\x4a\x35\x88\x9e\xfb\ \xd7\x54\x57\x62\xcd\x47\x6f\xc1\x66\x09\x8b\x47\x44\x8c\x31\x16\ \x71\xa2\x0d\xc0\x9c\x61\x04\x97\x4c\xf0\xa8\x80\x51\x02\x62\xcf\ \x9f\x2d\x63\x41\xea\xbb\x2b\x88\xae\x5d\x20\x44\xc8\x6e\xe7\x0a\ \xe9\x47\x00\xb1\x71\xd8\xea\xf0\x88\x38\xbd\xe3\x00\x00\x10\xa1\ \x78\xcf\x57\xf8\xe8\xed\x57\x78\xf2\x67\x8c\xb1\x30\x10\x63\x04\ \x12\xa3\x2f\x9a\xfc\xbf\x26\xae\x1a\x5d\xaf\xfe\x54\xeb\x98\xfc\ \x29\x64\x13\x80\xd7\xf6\xd0\xbf\xb4\xdb\x31\x4d\xef\x38\x00\xc0\ \xe3\x76\xe1\xf3\x15\x6f\x61\xeb\x17\xab\x78\x6f\x3f\x63\x8c\x45\ \x08\x12\xf8\xfd\x81\x06\x0a\xd9\x82\xc0\x90\x4c\x00\xfe\xbe\x97\ \x86\xb7\x59\xf1\xff\xf4\x8e\x03\x00\xda\x5b\x9a\xf0\xee\x3f\xfe\ \x8c\x13\x47\x4b\xf5\x0e\x85\x31\xc6\x98\x96\x08\x51\xa4\xaa\x6f\ \x17\xd5\x53\xbc\xde\xa1\xf4\x47\x48\x26\x00\xb2\x07\x5b\xdc\x2a\ \x0c\x7a\xc7\x51\x7e\xfc\x30\xde\x7d\xe3\x4f\x68\x6f\x69\xd2\x3b\ \x14\xc6\x18\x63\xfa\x18\x2f\x41\xfd\x93\xde\x41\xf4\x47\xc8\x25\ \x00\x6f\xec\xa6\xbf\x77\xd8\x31\x4c\xef\x38\x0e\x17\xef\xc6\xe7\ \x1f\xbe\x09\x8f\xc7\xad\x77\x28\x8c\x31\xc6\xf4\x44\xf8\x5e\x49\ \x9d\x7c\xa7\xde\x61\xf4\x55\x48\x25\x00\x4b\xf7\xd0\xac\x26\x3b\ \x7e\xa0\x6b\x10\x44\xd8\xf5\xe5\x17\x58\xff\xd9\x07\x50\x55\xee\ \xea\xc7\x18\x63\x0c\x00\xc4\xe2\x7d\xcd\x94\xad\x77\x14\x7d\x11\ \x32\x09\xc0\xc2\x85\x24\xb5\xbb\xf1\xb9\xac\x40\xb7\x3d\x7f\x8a\ \xa2\x60\xcd\xaa\x77\xb1\x6b\xeb\x3a\xbd\x42\x60\x8c\x31\x16\x9c\ \x32\xa3\x3c\xb4\x48\xef\x20\xfa\x22\x64\x12\x80\xc1\x37\x61\x89\ \xc9\x89\x0c\xbd\xc6\xf7\xb8\x5d\xf8\xe4\xbd\x7f\xe0\xf8\xa1\x62\ \xbd\x42\x60\x8c\x31\x16\xc4\x08\x74\xfb\xfa\x93\xca\x8f\xf4\x8e\ \xc3\x57\x21\x91\x00\x2c\xda\x43\x93\x5b\xed\x78\x4c\xaf\xf1\xad\ \x16\x13\xde\x5b\xf4\x12\x4e\x57\x1e\xd7\x2b\x04\xc6\x18\x63\x21\ \xe0\x70\xbd\x78\xe9\xbd\x03\x34\x55\xef\x38\x7c\x11\x12\x09\x80\ \xc5\x8d\x7f\x7a\x74\x5a\xfa\xb7\x5b\x2d\xf8\xe8\x9d\x57\xd1\xda\ \xdc\xa0\xc7\xf0\x8c\x31\xc6\x42\x44\x87\x03\x30\xbb\x85\xa1\xc5\ \x8e\x35\x7a\xc7\xe2\x8b\xa0\x4f\x00\x5e\xdf\xad\xfc\xae\xd3\x09\ \x5d\x0e\xfa\xb1\xdb\x2c\xf8\xf0\xed\x97\xd1\xd6\xdc\xa8\xc7\xf0\ \x8c\x31\xc6\x42\xc8\xa9\x76\xef\x7d\x6a\xbb\x0d\x79\x6f\x15\x29\ \x7f\xd4\x39\x9c\x5e\x05\x75\x02\xf0\xda\x2e\xca\x6b\xb5\x4b\xbf\ \xd4\x63\x6c\xbb\xcd\x82\x0f\x97\xf2\xe4\xcf\x18\x63\xac\x77\x76\ \x37\x50\x67\xfa\x76\xa1\xba\xce\x24\x7e\xfa\xfe\x01\x1a\xa6\x5f\ \x44\xbd\x0b\xea\x04\x40\x56\x69\xbd\x5b\xd1\xbe\xe1\xcf\x37\x93\ \x7f\x0b\x4f\xfe\x8c\x31\xc6\x7a\x57\xd6\x24\xa0\x9e\x73\x8a\xb0\ \x4b\x11\x52\x9b\x13\xff\xd4\x2f\xa2\xde\x05\x6d\x02\xb0\x78\x2f\ \x3d\xdd\x6a\x17\x13\xb5\x1e\xd7\x61\xb7\x62\xc5\x5b\xaf\xf0\xe4\ \xcf\x18\x63\xcc\x27\xed\x0e\xa0\xde\x7c\x71\x99\x5a\x8b\x05\x13\ \x96\x16\x51\xd0\xee\x0a\x08\xca\x04\xe0\x8f\x07\x29\xa1\xc5\x8e\ \x97\xa9\xf7\x97\xfa\x95\xcb\x69\xc7\x87\x4b\x5f\xe6\x82\x3f\xc6\ \x18\x63\x3e\x21\x02\x0e\x37\x48\xe8\x6a\xbe\x22\x00\xcd\x56\xfc\ \x71\xc9\x0e\x4a\xd2\x3a\x2e\x5f\x04\x65\x02\x90\xe2\xc2\x27\x0e\ \x0f\x62\xb4\x1c\x53\x55\x14\x7c\xfa\xe1\x52\x9e\xfc\x19\x63\x8c\ \xf9\xec\x68\xb3\x40\x87\xa3\xfb\xcf\xdb\xdc\x88\xf1\x18\xb1\x52\ \xbb\x88\x7c\x17\x74\x09\xc0\xf2\x52\xba\xa2\xc1\x8c\x1b\x34\x1d\ \x94\x08\xeb\x3e\x7b\x1f\x67\x4e\x9d\xd4\x74\x58\xc6\x18\x63\xa1\ \xab\xbc\x4d\xa0\xa2\xb5\xf7\x1d\xea\x4d\x16\xdc\xf0\xee\x01\x9a\ \xad\x41\x48\x7d\x12\x74\x09\x40\xab\x15\x2b\x14\x8d\x5b\xec\xef\ \xfa\x6a\x1d\x8e\x96\xee\xd7\x76\x50\xc6\x18\x63\x21\x49\x56\x81\ \xd2\x7a\x81\xb2\x46\xd1\xe5\xd2\xff\x85\x14\x02\xda\xec\x58\x11\ \xf0\xc0\xfa\x28\xa8\x12\x80\xb7\x8b\x94\x9f\xb7\xda\x91\xa3\xe5\ \x98\xc7\x8f\x94\x60\xd7\x97\xeb\xb5\x1c\x92\x31\xc6\x58\x08\x52\ \x01\x54\x77\x0a\x6c\xae\x90\x70\xba\xa3\x6f\xbd\xe9\xda\x6d\xc8\ \x7b\xa7\x44\xf9\x45\x60\x22\xeb\x9f\xa0\x49\x00\xde\x39\x48\x09\ \x8d\x56\xf1\x5b\x2d\xc7\xac\xa9\xae\xc4\x17\xab\xde\xf3\x56\x71\ \x30\xc6\x18\x63\x5d\x70\xc9\xc0\x89\x16\x81\x8d\x27\x24\x1c\xa8\ \x13\x70\x78\xfa\x77\x9d\x46\xb3\xb4\xf0\x8d\x22\x8a\xf7\x6f\x74\ \xfd\x67\xd4\x3b\x80\xb3\x5c\x6e\xac\xb0\xb9\x45\xb4\x56\xe3\xb5\ \xb7\x34\x61\xf5\xf2\xc5\x50\x14\x59\xab\x21\x19\x63\x8c\x85\x10\ \xbb\x07\x28\x6f\x15\x38\xd3\x21\xa0\xf8\xe1\x3e\xd1\xea\x46\x4c\ \xaa\x82\xe5\x00\xe6\x0d\xfc\x6a\x03\x17\x14\x2b\x00\x2b\xca\x68\ \x6a\xa3\x19\xb7\x68\x35\x9e\xdb\xed\xc2\xea\x0f\x97\xc0\xe5\xb4\ \x6b\x35\x24\x63\x8c\xb1\x10\xe1\x92\x81\x92\x3a\x81\x4d\x27\x25\ \x54\xb5\xfb\x67\xf2\x3f\xab\xc9\x8a\x3b\x97\x1d\xa2\x49\xfe\xbb\ \x62\xff\x05\xc5\x0a\x40\xab\x05\x1f\x68\x76\xd8\x0f\x11\xd6\x7f\ \xb2\x1c\xed\x2d\x4d\x9a\x0c\xc7\x18\x63\x2c\x74\xb4\xd8\x04\xf6\ \xd7\x08\xb8\x95\xc0\x5c\xdf\xa3\x40\x74\x58\xf0\x31\x80\xb1\x81\ \x19\xc1\x77\xba\xaf\x00\x2c\x3f\x40\xf3\x9a\x2c\xda\x7d\x23\xf6\ \xef\xda\x82\x13\x47\x4b\xb5\x1a\x8e\x31\xc6\x58\x88\x68\xb6\x0a\ \xec\x3e\x1d\xb8\xc9\xff\xac\x56\x1b\xc6\xbc\xb9\x8f\xee\x0f\xec\ \x28\xbd\xd3\x7d\x05\xa0\xc5\x8e\x7f\x68\x55\x83\x57\x77\xe6\x14\ \xb6\x6f\x0a\x89\x53\x1a\xd9\x05\x24\x49\x60\x50\x6a\x32\x72\xb2\ \x06\x21\x2d\x35\x11\xc9\x89\xf1\x48\x4a\x8c\x47\x72\x42\x3c\x84\ \x00\x0c\x06\x03\x14\xc5\xfb\x57\x5b\x79\xa6\x01\x1b\xb6\x15\xeb\ \x1c\x31\x63\x2c\x94\x58\x5d\xc0\xfe\x1a\x01\x2d\x76\xa1\x13\x80\ \x0e\x27\x5e\x05\xf0\xa1\x06\xc3\x75\x4b\xd7\x04\x60\x59\x89\xf2\ \x8b\x93\xad\xc8\xd4\x62\x2c\xb7\xd3\x81\xb5\x2b\xdf\x85\xaa\x6a\ \xdc\x64\x80\xf5\x89\x10\x02\xf9\x39\x99\x18\x3b\x72\x08\x46\xe4\ \xe7\x60\x58\x7e\x36\x86\xe7\x67\x63\x70\x46\x1a\xa2\x8c\xbe\x9d\ \x0b\xb5\xff\xe0\x49\x4e\x00\x18\x63\x3e\x53\x09\xd8\x73\x46\x82\ \x47\xc3\xe9\xc1\xe4\x44\xfa\xd2\x62\xfa\xd5\x13\xd3\xc5\xff\x68\ \x37\xea\xf9\x74\x4b\x00\x16\x12\x19\x1b\xb7\xe3\x37\x5a\x8d\xb7\ \xe1\xf3\x15\x30\x75\xb6\x6b\x35\x1c\xf3\x91\xc1\x20\x61\xc2\xa8\ \xa1\x28\xbc\x64\x34\xa6\x8c\x1f\x81\xf1\xa3\xf3\x91\x94\x10\x34\ \xbb\x64\x18\x63\x11\xe0\x78\x8b\x80\xd5\xad\xfd\xb8\x2d\x16\xfc\ \xc7\x8a\x32\xfa\xd3\x82\x89\x42\x87\xd1\x75\x4c\x00\x86\x15\xab\ \xaf\x9d\x72\x4a\x71\x5a\x8c\x55\x56\xba\x0f\xc7\x8f\x94\x68\x31\ \x14\xf3\x41\x5a\x4a\x12\xae\xb8\x74\x22\xe6\x4c\xbf\x04\x85\x97\ \x8c\x42\x7c\x9c\xa6\xc7\x3e\x30\xc6\xd8\x37\xcc\x4e\xa0\xa2\x45\ \x9b\x1a\xf4\x0b\x59\xdd\x88\xb5\x39\xd4\x97\x01\x7c\x57\x8f\xf1\ \x75\x49\x00\x96\xed\xa1\xe4\x5a\x0b\x1e\xd7\x62\x2c\x8b\xb9\x13\ \x9b\xbf\x58\xa5\xc5\x50\xac\x07\xe9\xa9\x49\xb8\x6e\xf6\x34\x5c\ \x33\x7b\x0a\x2e\x19\x33\x1c\x92\xa4\xcf\x1f\x1c\x63\x8c\x9d\xeb\ \x50\xa3\x36\xcf\xfd\xbb\xd3\x68\x91\x9e\x58\xb6\x87\xfe\xf5\xe1\ \x59\xc2\xac\xf5\xd8\xba\x24\x00\x2e\x89\xde\xb2\x7b\x84\x26\x63\ \x6f\xf8\xec\x43\xb8\x9d\x3d\x1c\xd5\xc4\x02\x26\xda\x68\xc4\xd5\ \xb3\x26\xe3\xe6\xab\x2f\xc5\x8c\xc9\x63\x60\x30\xe8\xbe\xe9\x84\ \x31\xc6\xbe\xd1\x60\x16\x68\xb5\xe9\x7b\x33\xe2\xf0\xc0\x68\x8f\ \xc7\x5b\x00\xe6\x6b\x3d\xb6\xe6\x09\xc0\x8a\x72\xca\xac\xa8\xa1\ \xbb\xb4\x18\xeb\x70\xc9\x1e\x54\x95\x1f\xd5\x62\x28\x76\x8e\xcc\ \xf4\x14\xdc\x71\xfd\x2c\xdc\x7d\xd3\x1c\xa4\xa5\x04\xe5\x31\xd8\ \x8c\xb1\x08\xa7\x10\x70\xa4\x29\x38\x56\x22\x9b\xad\x34\xef\x8d\ \x22\x2a\x78\x76\x86\x38\xa3\xe5\xb8\x9a\x27\x00\x56\x13\xde\x75\ \xca\x22\xe0\xb7\x82\x36\xab\x19\x5b\xd7\xaf\x0e\xf4\x30\xec\x1c\ \x13\x47\x0f\xc3\x43\xf3\xae\xc5\x15\x97\x5e\xc2\x4b\xfc\x8c\xb1\ \xa0\x56\xd9\x2a\x60\xd3\xa5\xf4\xee\x62\x6e\x45\x08\xd5\x43\x6f\ \x03\xb8\x46\xcb\x71\x35\x4d\x00\x56\x1f\xa6\xfc\xb2\x66\xdc\xa8\ \xc5\x58\x5b\xbf\xf8\x84\x97\xfe\x35\x32\xab\x70\x3c\x1e\x9e\x77\ \x1d\xa6\x4e\x18\xa9\x77\x28\x8c\x31\xd6\x2b\x97\x0c\x9c\x6c\x0d\ \xae\x9b\x94\x56\xbb\xb8\xfa\x9d\xbd\x34\xfc\xd1\x99\xa2\x4a\xab\ \x31\x35\x4d\x00\xda\xec\x78\xdb\xa5\x41\xcb\xdf\xea\xca\x13\x5c\ \xf5\xaf\x81\x4b\xc6\x0c\xc3\xb3\x0f\xdd\x86\x69\x13\x47\xe9\x1d\ \x0a\x63\x8c\xf9\xec\x74\x87\x80\x1c\x64\x2d\x61\x3c\x0a\x84\x03\ \x58\x0a\x60\xae\x56\x63\x6a\x96\x00\xbc\x7f\x98\xf2\x4f\xb5\x04\ \xfe\x3f\x4c\x51\x64\x6c\xfa\xe7\x47\x81\x1e\x26\xa2\x8d\x28\xc8\ \xc1\xf7\x1f\xbe\x03\xb3\x0a\xc7\xeb\x1d\x0a\x63\x8c\xf5\x09\x11\ \x50\xdd\x11\x5c\x77\xff\x67\x35\x5b\x71\x95\x96\xab\x00\x9a\x25\ \x00\x56\x07\xde\x71\xc9\x81\xbf\xfb\x2f\xda\xb9\x15\x1d\x6d\x2d\ \x81\x1e\x26\x22\x25\x25\xc4\xe3\xa9\xfb\x6f\xc6\xdd\x37\xcd\xe1\ \x8a\x7e\xc6\x58\x48\xea\x70\x7a\x8f\xf9\x0d\x46\x1e\x05\xc2\x29\ \xf0\x26\x34\xaa\x05\xd0\x24\x01\x58\x79\x90\x86\x1c\x6f\xc5\xd5\ \x81\x1e\xc7\x66\x35\x63\xcf\xf6\x8d\x81\x1e\x26\xe2\x08\x21\x70\ \xfb\x75\x33\xf1\xdc\x83\xb7\x23\x25\x39\x41\xef\x70\x18\x63\xac\ \xdf\x3a\x1d\xc1\x79\xf7\x7f\x56\x93\x05\x57\xbf\x57\x42\x43\x1f\ \x2a\x14\xd5\x81\x1e\x4b\x93\x04\xa0\xd3\x83\x25\x5a\x3c\xfb\xdf\ \xbe\x69\x0d\x3c\x6e\x57\xa0\x87\x89\x28\xb9\x83\x07\xe1\x67\xcf\ \x2e\xc0\x8c\xc9\x63\xf4\x0e\x85\x31\xc6\x06\xcc\x25\xeb\x1d\x41\ \xcf\x3c\x0a\x84\xd5\x83\xa5\x00\xae\x0d\xf4\x58\x01\x5f\xc7\x5d\ \x55\x4b\x83\x9a\xcd\xb8\x3e\xd0\xe3\x34\x35\xd4\xa2\xac\x74\x5f\ \xa0\x87\x89\x18\x42\x08\x2c\xb8\xed\x2a\xbc\xfd\xe7\x9f\xf1\xe4\ \xcf\x18\x0b\x1b\x5a\x9d\x3e\x3b\x10\xcd\x56\xcc\x7d\xbb\x94\xf2\ \x02\x3d\x4e\xc0\x57\x00\x2c\xcd\x78\xcd\x21\x07\x3e\xd1\xd8\xb6\ \xf1\x33\x50\x28\xfc\x64\x43\x40\x5a\x4a\x12\xfe\xfd\x07\x0f\x70\ \x91\x1f\x63\x2c\xec\x44\xf9\x76\xa8\xa8\xae\x3c\x0a\x84\xdd\x41\ \xaf\x03\xb8\x23\x90\xe3\x04\x74\x62\xde\xb5\x8b\xe2\x9a\xcc\x74\ \x77\x20\xc7\x00\x80\xda\xea\x53\xa8\xae\x3c\x11\xe8\x61\x22\xc2\ \x8c\xc9\x63\xf0\xd6\x9f\xff\x95\x27\x7f\xc6\x58\x58\x8a\x8f\xd2\ \x3b\x02\xdf\xb4\xdb\xc5\x2d\x0b\x8b\x28\xa0\x47\xa3\x06\x74\x05\ \xa0\x32\x56\xfd\xb3\xd5\x2a\x05\x74\x0c\x49\x00\x25\xdb\x3f\x0f\ \xe4\x10\x11\x41\x92\x04\x9e\xbe\xff\x56\x3c\x32\xff\x3a\x08\x11\ \xdc\x45\x32\x8c\x31\xd6\x5f\xc1\xba\x03\xe0\x42\x2e\x05\x86\x3c\ \x8f\xfa\x27\x00\xdf\x0f\xd4\x18\x01\x5b\x01\x58\x41\x64\x68\xb6\ \x8a\x27\x02\x75\x7d\x00\x88\x35\x02\x71\xe6\x32\x9c\x2c\x3f\x15\ \xc8\x61\xc2\x5e\x42\x7c\x1c\xfe\xf7\x17\x4f\xe3\xd1\x7b\xae\xe7\ \xc9\x9f\x31\x16\xb6\x5c\x32\x70\x52\xa7\xa3\x7f\xfb\xa3\xd3\x2d\ \x3d\x0e\xa2\x80\x05\x1c\xb0\x04\x20\xe1\x24\x9e\x4b\x8c\x41\xec\ \x88\x74\xc2\xb0\x34\x42\x4e\x32\x21\x31\x1a\x7e\xdb\x0a\x90\x18\ \x0d\x5c\x35\x5c\xc5\xc7\x9f\x6e\xf0\xd3\x15\x23\x53\x41\x6e\x16\ \x16\xfd\xe1\xc7\x98\x5d\x38\x41\xef\x50\x18\x63\x2c\xa0\xca\x1a\ \x05\x3c\x41\xd6\x01\xb0\x27\x36\x17\xe2\x16\xed\xc3\xbf\x06\xea\ \xfa\x01\x5b\x9e\xcf\x49\x54\x1e\xca\x49\xbc\x70\xba\x27\xb8\x15\ \xa0\xcd\x2e\xd0\x6a\x03\x5a\x6d\x02\x66\x27\xd0\xd7\xd2\xbd\xb4\ \x38\x60\x56\x81\x8a\xb2\xe3\xe5\x28\x2b\x3f\xed\xa7\x88\x23\xcf\ \x84\xd1\x43\xf1\xc7\x5f\x3e\xc3\x7b\xfb\x19\x63\x61\xaf\xde\x2c\ \x70\xc6\x14\x3a\x77\xff\x67\x99\xdd\xf8\x37\x00\x7f\x0c\xc4\xb5\ \x03\x92\x00\x14\xd7\xd3\x74\x90\x7a\x79\x57\x9f\x8b\x36\x00\x39\ \x49\x84\x9c\x24\x00\x20\x38\x65\xa0\xc1\x22\x50\xdd\x2e\xd0\xe9\ \xec\xfd\xda\xc3\xd3\x08\x97\xe4\x10\x0c\x02\x78\x67\xd5\x26\xff\ \x06\x1e\x41\xae\xbc\xf4\x12\x2c\x7c\xe1\x51\xc4\x44\x87\x48\x45\ \x0c\x63\x8c\xf5\x93\xc5\x05\x94\xd6\x87\xde\xe4\x0f\x00\x26\x07\ \x32\x97\xee\xa3\xf9\x4f\x5c\x26\x56\xf9\xfb\xda\x01\x49\x00\x04\ \xa9\x3f\xf5\xf5\xb5\xb1\x46\xef\xa4\x3e\x3c\x8d\xd0\x62\x13\xa8\ \x6a\x07\x1a\xcd\x02\xe7\xae\xd2\x18\x04\x30\x28\x81\x30\x36\x93\ \x30\xe8\xeb\x9a\xc8\xa3\x15\xd5\x28\x3a\x74\xd2\xcf\x91\x47\x86\ \xbb\x6e\x98\x8d\x7f\x79\xe6\x1e\x48\x12\xb7\xf3\x65\x8c\x85\x37\ \x97\x0c\xec\xa9\x96\xe0\x56\xf4\x8e\xa4\xff\xcc\x1e\xfa\x5f\x00\ \xc1\x9f\x00\x94\x9c\xa1\x5c\x40\xbd\xb7\x3f\x5f\x9b\x99\x40\xc8\ \x4c\x00\x14\x22\x98\x9c\x80\xa2\x0a\x44\x49\x84\xa4\x18\xe0\xc2\ \xd6\xf3\x1f\x7c\xba\xd5\x1f\xe1\x46\x9c\xfb\x6e\xbd\x0a\xcf\x3f\ \x31\x8f\x8b\xfd\x18\x63\x61\xcf\x25\x03\x3b\xab\x25\xd8\x42\xa4\ \xf2\xbf\x3b\x1d\x36\x31\xfa\x1f\xfb\x68\xea\x77\x2f\x13\xa5\xfe\ \xbc\xae\xdf\x13\x00\x61\x54\xbf\x4f\x84\xe8\x81\x5c\xc3\x20\x80\ \xf4\x38\xa0\xbb\xea\x80\x96\x76\x13\xb6\xed\x3b\x32\x90\x21\x22\ \xd2\x5d\x37\xcc\xe6\xc9\x9f\x31\x16\x11\xac\x6e\xef\x9d\xbf\xd5\ \xad\x77\x24\x03\xa7\x02\x20\xc2\x5f\xe0\xe7\xa3\x82\xfd\xba\x06\ \x5c\x5e\x4e\x31\x44\xf8\xae\x3f\xaf\xd9\x95\x95\x5f\x6c\x87\xac\ \x84\xf0\x7a\x8e\x0e\xee\xbd\xf5\x4a\xfc\xeb\x77\xef\xe5\xc9\x9f\ \x31\x16\xf6\x1a\x2d\x02\x5f\x55\x86\xc7\xe4\x7f\x56\xab\x15\x57\ \xbe\x73\x90\xfc\x5a\xb1\xed\xd7\x04\xc0\x9c\xa0\xdc\x07\x20\xd3\ \x9f\xd7\xbc\x90\xcb\xed\xc1\x67\x9b\xf6\x04\x72\x88\xb0\x73\xe3\ \x55\xd3\xf1\xe3\x27\xee\xe6\xc9\x9f\x31\x16\xd6\x64\xc5\x5b\xec\ \xb7\xf7\x4c\x68\x6d\xf7\xf3\x85\x4b\x81\x24\x7b\xf0\x7b\x7f\x5e\ \xd3\xaf\x09\x80\x20\x3c\xe7\xcf\xeb\x75\x65\xd3\x8e\x12\x98\x2d\ \xb6\x40\x0f\x13\x36\x66\x4e\x1b\x87\x5f\xfd\xe0\x01\x9e\xfc\x19\ \x63\x61\x8b\x00\x9c\x31\x09\x6c\xae\x90\x70\xba\x43\xf4\x79\x6b\ \x79\xa8\x30\x39\xe8\x31\x7f\x5e\xcf\x6f\x09\x40\x49\x03\x4d\x00\ \xc4\x1c\x7f\x5d\xaf\x3b\x9f\x6d\xe4\xbb\x7f\x5f\x8d\x1b\x59\x80\ \xdf\xfd\xcb\xe3\x30\x1a\x42\xe0\xf4\x0b\xc6\x18\xeb\x23\xc2\xd7\ \xcb\xfd\xa7\x24\x94\xd4\x0a\x38\x82\xfc\xa8\xdf\x81\xea\x74\x88\ \xd4\xe5\x07\xc9\x6f\x07\x04\xf9\xaf\x08\x90\xd4\x1f\xfa\xed\x5a\ \xdd\xa8\xaa\x69\xe4\xc6\x3f\x3e\x1a\x9c\x91\x86\x3f\xfe\xea\x69\ \xc4\xc7\xc6\xe8\x1d\x0a\x63\x8c\xf9\x95\xc3\xf3\x75\x63\x9f\x4e\ \x01\x93\x0f\xfd\x63\xc2\x05\x01\xe8\xb4\xd1\x6f\x01\xf8\xe5\x00\ \x1c\xbf\x24\x00\x65\xcd\x94\xe8\xf2\xa8\x0f\xf9\xe3\x5a\x3d\xf9\ \x7c\x33\xdf\xfd\xfb\x22\x3a\x2a\x0a\xbf\xff\xb7\xc7\x91\x96\x92\ \xa4\x77\x28\x8c\x31\xe6\x17\x6e\x05\xa8\x35\x09\xd4\x9a\x04\x3a\ \xec\x7d\xef\x20\x1b\x2e\xda\x1d\x62\xca\x8a\x12\xca\x5c\x50\x28\ \x5a\x06\x7a\x2d\xbf\x24\x00\x6e\x59\x79\x00\x10\xc9\xfe\xb8\x56\ \xf7\x63\xc8\x58\xf7\x55\x51\x20\x87\x08\x1b\x3f\x7b\x6e\x01\xc6\ \x8d\x2c\xd0\x3b\x0c\xc6\x18\xf3\x8b\x8a\x36\x81\xe3\x2d\x02\x32\ \x6f\xfe\x82\x5b\x81\x30\x7b\xd4\xff\x05\xf0\xd4\x40\xaf\xe5\x97\ \x1a\x00\x22\x3c\xe9\x8f\xeb\xf4\x64\x4f\xc9\x71\x2e\xfe\xf3\xc1\ \x7d\xb7\x5e\x85\x9b\xaf\x9e\xa1\x77\x18\x8c\x31\xe6\x17\xe5\xad\ \x02\x47\x1a\x79\xf2\x3f\x57\xa7\x4b\xea\x57\xb3\xbd\x0b\x0d\x38\ \x01\x28\xad\xa5\x31\x80\x98\xe9\x8f\x60\x7a\xb2\x69\x3b\xdf\xfd\ \xf7\x66\xcc\x88\x21\xf8\xfe\xa3\x77\xea\x1d\x06\x63\x8c\xf9\x85\ \xc5\x05\x1c\x6f\xe6\x1d\x4c\x17\x32\x3b\x91\xfc\xee\x7e\xba\x61\ \xa0\xd7\x19\x70\x02\x40\x42\x7d\x0a\xfe\x3b\xe5\xb7\x4b\x76\x87\ \x0b\x3b\x8b\x8f\x06\x72\x88\x90\x17\x17\x1b\x8d\x85\x3f\x79\x04\ \x51\x46\xae\xf8\x67\xcc\x9f\xa6\x4f\x1a\x8d\x91\x05\xd9\x7a\x87\ \x11\x91\x0e\x35\x08\x28\x91\xfa\xb0\xbf\x17\x36\x05\xbf\x19\xe8\ \x35\x06\x54\x03\xb0\x95\xc8\x48\x0d\xea\x23\x81\xae\xc6\xd8\xbe\ \xef\x30\x5c\xee\x10\x6f\xe6\x1c\x60\x3f\x7a\xfc\x6e\x14\xe4\x66\ \xe9\x1d\x06\x63\x61\x23\x2b\x23\x15\x3f\x7c\x6c\x1e\xae\xbd\x7c\ \x8a\xde\xa1\x68\x4a\x55\x09\x36\x87\x03\x09\x71\x71\x90\x24\xfd\ \xee\xbe\x6b\x4d\x02\x2d\x36\xbe\xfb\xef\x4e\x87\x9d\x2e\x7f\xa3\ \x88\xa2\x9e\x9d\x21\xfa\x3d\x39\x0e\x28\x01\x48\xae\x53\x6e\x86\ \x10\x39\x03\xb9\x86\x2f\x36\xef\x3c\x10\xe8\x21\x42\xda\x55\x33\ \x27\xe1\xce\xeb\x67\xe9\x1d\x06\x63\x61\x21\xda\x68\xc4\xfd\x77\ \xce\xc5\xa3\xf3\x6f\x40\x5c\xec\x80\x8e\x35\xd1\x9d\x5b\x96\xd1\ \xd0\xd8\x86\xa6\xb6\x4e\x74\x74\x5a\xd0\xd6\x69\x46\x6b\x87\x19\ \x1d\x26\x0b\x5a\xdb\x4d\xb0\x3b\xdd\xb0\x5a\x1d\xf0\xc8\x32\x1c\ \x4e\x37\x1c\x4e\x57\xb7\x6d\xd6\xa3\x8c\x06\xc4\xc6\xc4\x20\x3e\ \x2e\x06\xa9\x29\x89\x48\x4d\x4e\x40\x4a\x52\x02\x52\x93\x12\x90\ \x92\x9c\x80\xec\x8c\x74\xe4\xe7\x66\x62\x48\x4e\x26\x92\x13\xe3\ \x07\x14\xb7\xdd\x0d\x1c\x0c\xd1\xe3\x7b\xb5\xe2\x90\x85\x21\xd1\ \x80\x1f\x03\xf8\x53\x7f\xaf\x31\xa0\x04\x40\x12\xe2\x91\x40\xaf\ \xce\xd8\x1d\x2e\x3e\xf6\xb7\x07\x89\xf1\x71\xf8\x97\x67\xfc\x52\ \x0f\xc2\x58\xc4\x9b\x39\x6d\x1c\x7e\xfc\x44\xe8\xad\xa6\xb5\x77\ \x98\x51\x7e\xba\x1e\xa7\xeb\x9a\x50\xdb\xd0\x82\xda\xc6\x16\xd4\ \x36\xb4\xa2\xa9\xb5\x13\xaa\xea\x9f\x9e\xb8\x1e\x59\x81\x47\xb6\ \xc3\x62\xb3\xa3\xa9\xb5\xa3\xc7\xd7\x26\x27\x25\xa0\x20\x27\x13\ \xf9\xb9\x99\x18\x9e\x9f\x8d\x89\x63\x86\x62\xdc\xc8\x02\xc4\x44\ \x47\xf5\x3a\x8e\x53\x06\xf6\xd4\x48\x61\xd7\xca\x37\x10\x3a\x1c\ \x78\x0e\x7a\x24\x00\x3b\x5a\x28\x89\xdc\xea\xed\xfd\xfd\x7a\x5f\ \xed\x29\x3d\x0e\xb7\x1c\xe6\xed\x9d\x06\xe0\xb9\x87\x6f\xc7\xa0\ \xd4\x80\xee\xc0\x64\x2c\xec\xe5\x64\xa5\xe3\x47\x8f\xdf\x8d\xab\ \x2e\xbb\x44\xef\x50\x7a\xd5\xdc\xda\x89\x23\xe5\xd5\xa8\x38\x5d\ \x8b\x93\x55\x75\x28\x3f\x55\x87\xb6\x4e\xb3\xde\x61\x9d\xc7\x6c\ \xb1\xe1\x88\xc5\x86\x23\x27\x4f\x7f\xf3\x31\xa3\xc1\x80\x31\xc3\ \x87\xe0\x92\xb1\x43\x31\x71\xcc\x70\x4c\x1e\x3f\x1c\x99\xe9\x29\ \xe7\x7d\x9d\x43\x06\x76\x9e\x96\x60\x75\x69\x1c\x70\x88\xea\x70\ \x60\xc4\x40\x7a\x02\xf4\x3b\x01\x48\x70\x29\xf3\x49\x88\x81\xad\ \xf3\xf8\x60\xfb\xbe\x43\x81\x1e\x22\x64\x4d\x18\x3d\x14\x77\x5e\ \x7f\xb9\xde\x61\x30\x16\xb2\xa2\x8c\x06\xcc\xbb\x71\x0e\x9e\x79\ \xf0\xd6\xa0\xec\x9a\xa9\xaa\x2a\xaa\xeb\x9a\x71\xf8\x78\x15\x0e\ \x1e\x3f\x85\xc3\xc7\xab\x50\xdf\xd4\xa6\x77\x58\xfd\x22\x2b\x0a\ \x8e\x56\x54\xe3\x68\x45\x35\xf0\xcf\x6d\x00\x80\x61\xf9\xd9\x98\ \x33\x7d\x22\xe6\x4c\x9f\x80\x21\x43\x87\x63\x7f\x8d\x21\xec\xdb\ \xf9\xfa\x93\x47\x81\xb0\xc8\xea\x7f\x02\xe8\x57\x27\xde\x7e\x27\ \x00\x24\xa4\x07\x02\xdd\x8b\x49\x56\x14\xec\x2e\x3e\x16\xd0\x31\ \x42\x95\xd1\x60\xc0\x2f\xbe\x77\xbf\xae\x45\x3a\x8c\x85\xb2\xd9\ \xd3\x27\xe0\xc7\x4f\xde\x8d\xbc\xc1\x19\x7a\x87\x72\x9e\x86\xe6\ \x76\xec\x2b\x3d\x8e\x7d\x87\x4e\xa0\xe8\x50\x39\x6c\x76\x87\xde\ \x21\x05\xcc\xe9\x9a\x46\x9c\xae\x69\xc4\x7b\xab\x37\x23\x39\x25\ \x0d\xa3\xc6\x4f\xc2\xa8\xb1\x93\x90\x3f\x7c\x34\x1f\x60\xe6\x23\ \xb3\x4b\x2c\x80\x96\x09\xc0\xc1\x46\xca\x52\x14\xf5\xba\xfe\x7c\ \x6d\x5f\x1c\x3e\x5e\x05\x6b\x18\xff\xf2\x0f\xc4\x5d\x37\x5e\x8e\ \x11\x05\x01\xaf\xbf\x64\x2c\xec\xe4\x0d\xce\xc0\x8f\x9f\xbc\x1b\ \xb3\xa7\x4f\xd0\x3b\x14\x00\xde\x1b\x9d\x03\x47\x2a\xb1\xb3\xf8\ \x08\xf6\x95\x9e\xc0\x99\xfa\x66\xbd\x43\xd2\x85\xd9\xd4\x81\x92\ \x3d\xdb\x50\xb2\x67\x1b\x92\x53\xd3\x31\x65\xfa\x6c\x4c\x2a\x9c\ \x85\xf8\x44\x6e\x69\xde\x93\x4e\xa7\xc8\x7c\xbb\x94\xc6\x3c\x36\ \x55\xf4\xb9\x58\xae\x5f\x09\x80\xa2\xa8\xf7\xf7\xf7\x6b\xfb\x62\ \xdf\xc1\x13\x81\x1e\x22\x24\xc5\xc5\xc5\xe0\xf1\x7b\x6f\xd4\x3b\ \x0c\xc6\x42\x4a\x4c\x74\x14\x1e\x9a\x77\x2d\x1e\xbe\xfb\x3a\x44\ \x47\xf5\x5e\x8c\x16\x48\x2e\xb7\x07\x45\x87\x4e\x62\x67\x71\x19\ \xb6\xef\x3b\x82\x0e\x93\x45\xd7\x78\x82\x8d\xb9\xb3\x1d\xdb\x37\ \xaf\xc1\xae\x2f\xbf\xc0\xa8\x71\x93\x30\x79\xfa\x6c\x0c\x1d\x31\ \x06\xe0\x55\x81\x8b\x10\x01\x1e\x0f\xfe\x03\xc0\x23\x7d\xfd\xda\ \x7e\x4e\xe2\x74\x7f\x80\x7b\xff\x00\xe0\x04\xa0\x3b\x8f\xdf\x73\ \x03\x1f\xf4\xc3\x58\x1f\x5c\x35\x73\x12\x7e\xf4\xd8\x3c\xe4\x64\ \xa5\xeb\x16\x83\xac\x28\xd8\x57\x7a\x02\x1b\xb6\x15\x61\xfb\xfe\ \x23\xdc\xdb\xc4\x07\x8a\xa2\xe0\x44\x59\x29\x4e\x94\x95\x22\x3d\ \x73\x30\x2e\x9d\x7d\x2d\x26\x4e\xb9\x14\x12\x1f\x71\x7e\x1e\xab\ \x93\x6e\xed\xcf\xd7\xf5\x39\x01\x38\xdc\x44\x83\x3d\xb2\x1a\xf0\ \xca\x33\x93\xd9\x86\xf2\xaa\xda\x40\x0f\x13\x72\x06\x67\xa4\xe1\ \xde\x5b\xaf\xd2\x3b\x0c\xc6\x42\x42\x41\x6e\x16\x7e\xf2\xe4\x7c\ \x5c\x36\x75\xac\x6e\x31\x1c\xad\xa8\xc6\xfa\xaf\x8a\xb0\x79\x67\ \x29\x3a\xcd\x56\xdd\xe2\x08\x75\xed\x2d\x4d\x58\xff\xe9\xfb\xd8\ \xfd\xd5\x7a\xcc\xbc\xe2\x7a\x5c\x52\x38\x0b\x06\x4e\x04\x00\x00\ \x9d\x0e\x91\xfe\x41\x29\x8d\xf9\x4e\x1f\x1f\x03\xf4\x39\x01\xf0\ \x78\xd4\xf9\x10\xfe\x39\x44\xa8\x27\xfb\x0f\x9f\x84\xaa\x72\x0f\ \xc8\x0b\x3d\x7e\xef\x8d\x3e\xed\xa5\x65\x2c\x92\xc5\xc5\x46\xe3\ \xb1\x7b\x6e\xc4\xfd\x77\xcc\xd5\xa5\x3d\xb6\xcd\xee\xc0\xfa\xed\ \x25\xf8\x6c\xc3\x6e\x54\x54\xd7\x69\x3e\x7e\x38\x33\x77\xb6\x63\ \xe3\x9a\x15\xd8\xbf\x6b\x0b\xae\xbc\xfe\x0e\x8c\x9d\x30\x25\xe2\ \x1f\x0d\xa8\x00\x6c\x6e\xf5\x17\x40\xdf\x0e\xe6\xeb\xfb\x23\x00\ \x21\xee\xd1\xe2\x24\xe6\x92\x23\xe5\x01\x1f\x23\xd4\x64\x0d\x4a\ \xc5\xcd\x73\x2f\xd5\x3b\x0c\xc6\x82\xda\xb5\x97\x4f\xc1\x0f\x1f\ \x9f\x87\xac\x41\xa9\x9a\x8f\x7d\xea\x4c\x3d\x3e\x5c\xb3\x0d\x5b\ \x76\x1d\x80\xc3\xe9\xd6\x7c\xfc\x48\xd2\xd9\xde\x8a\xcf\x57\x2c\ \x45\xf1\x90\x61\xb8\xf6\xb6\x7b\x90\x9d\x1b\xd9\x47\xa0\x5b\x5c\ \xa2\xcf\x7d\x79\xfa\x94\x00\xec\xad\xa5\x41\x80\x7a\x75\x5f\x07\ \xe9\x8f\xd2\xa3\xa7\xb4\x18\x26\xa4\x3c\x70\xd7\x35\x7c\xd8\x0f\ \x63\xdd\x28\xc8\xcd\xc2\x8f\x9f\xb8\x1b\x33\xa7\x8d\xd3\x7c\xec\ \x43\xc7\x4e\x61\xd9\xea\xcd\xd8\x5d\x72\x0c\x44\xbc\x72\xa9\xa5\ \xfa\xda\xd3\x78\xef\x1f\x2f\x62\xfc\xe4\x19\xb8\xf6\xe6\xbb\x11\ \x1b\x9f\xa0\x77\x48\xba\x30\x39\x45\xe6\x7b\x25\x34\xf4\xa1\x42\ \x51\xed\xeb\xd7\xf4\x29\x01\x30\x0a\xe5\x4e\x40\x04\xbc\xfa\xbf\ \xd3\x6c\x45\x4d\x84\x6e\x85\xe9\x4e\x5a\x4a\x12\xee\xb8\x8e\xfb\ \xfd\x33\x76\xa1\xb8\xb8\x18\x3c\x79\xdf\x4d\xb8\xef\xb6\xab\x60\ \xd4\xf0\x99\xb0\xaa\x12\xbe\xdc\x7b\x08\x6f\x7d\xb4\x01\xa7\xce\ \xd4\x6b\x36\x2e\xbb\x18\x11\xe1\xe8\xc1\xfd\xa8\x3e\x75\x02\x37\ \xde\x71\x3f\x46\x8e\x0d\xfe\x8e\x8e\xfe\xa6\x10\xe0\x54\xf1\x0b\ \x00\xdf\xf3\xf5\x6b\xfa\x34\x99\x0b\x48\x77\x69\xb1\xfc\x7f\xe8\ \x58\x15\x67\xd1\x17\xb8\xef\xd6\xab\x10\x1b\x13\xda\x07\x93\x30\ \xe6\x4f\x42\x08\x5c\x7f\xc5\x34\xfc\xe0\xd1\x3b\x91\x91\x96\xd2\ \xfb\x17\xf8\x09\x11\x61\xcb\xae\x83\x78\xeb\xe3\xf5\xa8\xaa\x69\ \xd4\x6c\x5c\xd6\x3b\x9b\xc5\x8c\x4f\x96\x2f\xc2\x25\xd3\x66\xe2\ \xda\x5b\xe6\x23\x3a\x26\x56\xef\x90\x34\x65\x71\xd2\x9d\x08\x44\ \x02\x50\x5e\x4e\x31\x16\x04\xbe\xf9\x0f\x00\x1c\x3c\x56\xa9\xc5\ \x30\x21\x23\x3a\x2a\x0a\x77\xdd\xc0\x2d\x7f\x19\x3b\x6b\x44\x41\ \x2e\x5e\x78\xea\x6e\x4c\x9b\x38\x4a\xd3\x71\x0f\x1e\xab\xc4\xcb\ \x6f\x7f\x86\x63\x15\x67\x34\x1d\x97\xf5\xcd\x91\x03\x7b\x51\x53\ \x55\x8e\x3b\x16\x3c\x81\xec\xbc\xc8\xa9\x0d\x30\x3b\x45\xce\xb2\ \x3d\x94\xfc\xf0\x2c\xe1\xd3\xe1\x10\x3e\x27\x00\xd6\x58\xe5\x3a\ \x40\x24\xf6\x3f\x34\xdf\x95\x9d\xf4\xf9\x11\x46\x44\xb8\x66\xf6\ \x14\xa4\x24\x47\xe6\x73\x2d\xc6\xce\x95\x10\x1f\x87\xa7\xee\xbf\ \x19\xf7\xdc\x7c\x05\x0c\x86\x80\x6f\x46\xfa\x46\x63\x4b\x3b\x5e\ \x7e\xfb\x53\x7c\xb9\x87\xcf\x26\x09\x15\xa6\xce\x76\xbc\xbf\xe4\ \x2f\xb8\xfa\xc6\x79\x28\x9c\x15\x19\x5b\xa7\x65\x15\xc2\x23\xd4\ \xef\x01\xf8\x7f\xbe\xbc\xde\xe7\x04\x40\x95\xc4\x6d\x5a\x6c\xb4\ \x50\x14\x15\xe5\xa7\x79\xdb\xcc\xb9\xee\xbe\x69\x8e\xde\x21\x30\ \xa6\x2b\x21\x04\x6e\xbe\x7a\x06\xbe\xf7\xf0\x1d\x48\x4f\xd5\xae\ \x09\x96\xdb\xe3\xc1\x7b\xab\xb7\x60\xd9\x27\x9b\xb9\x71\x4f\x08\ \x52\x14\x05\x5b\xbe\x58\x89\x86\xda\xd3\xb8\xe9\xae\x07\x60\xd4\ \xb9\x03\xa4\x16\x9c\x24\x2d\x80\xbf\x13\x00\x01\xf4\xab\xd3\x50\ \x5f\x9d\xaa\x69\xe0\x3f\xb4\x73\x8c\x1e\x96\x87\x4b\xc6\x0c\xd3\ \x3b\x0c\xc6\x74\x33\x7a\x58\x1e\x5e\x78\x6a\x3e\x26\x8f\x1f\xa1\ \xe9\xb8\x3b\x8b\xca\xf0\xd7\xa5\x9f\x84\xec\xe9\x7b\xec\x5b\xc7\ \x0e\x17\xa3\xb3\xa3\x15\xf3\xbe\xf3\x34\x12\x92\xc2\xfb\xf8\x74\ \xb3\x93\x26\xf9\xfa\x5a\x9f\x12\x80\xe2\x1a\x9a\x0c\xa8\xc3\xfa\ \x1d\x51\x1f\x9c\xa8\xe4\xee\x7f\xe7\xe2\xbb\x7f\x16\xa9\x92\x12\ \xe2\xf1\xcc\x03\xb7\x60\xde\x8d\xb3\x21\x49\xda\x2d\xf7\xd7\x34\ \xb4\xe0\xaf\x4b\x3f\xc1\x9e\x12\x3e\x89\x34\x9c\x34\xd4\x56\xe3\ \xbd\x45\x2f\x62\xfe\xc3\xcf\x22\x23\x2b\x7c\x0f\x52\xb3\xb9\x45\ \xd4\x7b\x25\x74\xfd\x43\x85\x62\x53\x6f\xaf\xf5\x6d\x05\x40\x52\ \x6f\x1e\x70\x54\x3e\x3a\x5e\xc9\xc5\x35\x67\x45\x19\x0d\x98\x7b\ \xf9\x14\xbd\xc3\x60\x4c\x53\x42\x08\xdc\x78\xd5\x74\xfc\xf0\xd1\ \x3b\x35\x3d\xf3\xc2\xe9\x72\x63\xf9\xa7\x5b\xb0\xec\x93\x2d\x70\ \x7b\x78\x15\x32\x1c\x99\x4d\x1d\x78\x7f\xc9\xdf\x30\xff\xe1\xef\ \x22\x2f\x7f\xb8\xde\xe1\x04\x8c\xd3\x43\xcf\x02\xf0\x4f\x02\x20\ \x20\x6e\xd0\x62\xfb\x1f\x00\x9c\xac\xe2\xe7\xff\x67\xcd\x9c\x36\ \x0e\xc9\x89\xf1\x7a\x87\xc1\x98\x66\xc6\x8d\x2c\xc0\x4f\x9f\x99\ \x8f\x09\xa3\x86\x6a\x3a\xee\x96\x5d\xa5\x78\xf9\x9d\x4f\xd1\xdc\ \xda\xa9\xe9\xb8\x4c\x7b\x2e\xa7\x1d\x2b\xdf\x79\x0d\x77\x7d\xe7\ \x29\x0c\x1d\xa9\xdf\x19\x11\x81\x64\xf6\xc0\xa7\xaa\xc7\x5e\x13\ \x80\xad\x55\x14\x0b\xa8\x9a\xac\x43\x13\x11\xaa\x6a\x79\x5f\xed\ \x59\xd7\xcd\x29\xd4\x3b\x04\xc6\x34\x91\x9c\x94\x80\xe7\x1e\xbc\ \x0d\xb7\x5f\x37\x0b\x92\xa4\x5d\x5f\xf7\xaa\x9a\x46\xbc\xb4\x64\ \x15\xb7\x1e\x8f\x30\x6e\xb7\x0b\xab\xde\xfb\x07\xee\x7e\xf0\x19\ \x0c\x1b\xa5\x7d\xe7\xc8\x40\x33\x3b\x91\xb5\xa2\x88\x52\x16\xcc\ \x10\xa6\x9e\x5e\xd7\x6b\x02\x90\x1a\x2d\x5f\x49\x90\xe2\xfc\x17\ \x5a\xf7\x1a\x5b\x3a\xe0\x70\xb8\xb4\x18\x2a\xe8\xc5\xc5\x46\xe3\ \x8a\x19\x91\xd7\xcd\x8a\x45\x16\x49\x12\xb8\xe1\xca\xe9\xf8\xd1\ \x63\x77\x21\x35\x59\x93\x5d\xc6\x00\x00\x87\xd3\x8d\xf7\x3f\xdb\ \x82\x77\x57\x6d\x82\x47\x56\x34\x1b\x97\x05\x0f\x45\x91\xb1\xfa\ \x83\xc5\x98\xff\xd0\x77\x51\x30\x7c\x8c\xde\xe1\xf8\x95\xa2\x0a\ \xd8\x55\x3c\x03\xe0\x4f\x3d\xbd\xae\xd7\x04\x40\x85\x74\x83\x56\ \xf9\x38\xdf\xfd\x7f\x6b\xce\xf4\x89\x88\x8b\xe5\xce\x7f\x2c\x7c\ \x4d\x1c\x3d\x0c\x3f\x7d\x7a\x3e\xc6\x8e\xcc\xd7\x6c\x4c\x22\xc2\ \xfa\x6d\xc5\x78\x75\xd9\xe7\x68\xef\xf0\xa9\x57\x0a\x0b\x63\xb2\ \xc7\x83\x4f\x96\x2f\xc2\xbd\x8f\x7c\x0f\x79\x05\xda\xee\x32\x09\ \x34\x87\x4c\xf3\x31\xd0\x04\x40\x00\x37\xf8\x2d\xa2\x5e\x54\x9d\ \x69\xd0\x6a\xa8\xa0\x37\x87\xef\xfe\x59\x98\x4a\x4e\x8c\xc7\xe3\ \xf7\xdd\x84\x7b\x6f\xb9\x52\xd3\xe5\xfe\xf2\xd3\x75\x78\x69\xf1\ \x2a\x1c\x3a\xce\x07\x8d\xb1\x6f\x79\xdc\x6e\x7c\xb2\x7c\x11\x1e\ \x7c\xfa\x27\x48\xcf\x18\xac\x77\x38\x7e\x63\x97\x45\xaf\xdb\x01\ \x7b\x4c\x00\xca\x6a\x28\xdd\x05\x75\xb2\xff\x42\xea\x19\xf7\xd5\ \xf6\x92\x24\x09\x97\x4d\x09\xcf\xe2\x14\x16\xb9\x24\x49\xc2\xdd\ \x37\xcd\xc6\x33\x0f\xdc\x8a\xc4\x78\x4d\x9e\x2a\x02\x00\xcc\x56\ \x3b\x16\xbd\xbf\x16\x9f\x6e\xdc\x0d\x55\x55\x35\x1b\x97\x85\x0e\ \xa7\xc3\x8e\x95\xcb\xde\xc0\x83\x4f\xfd\x24\x6c\xfa\x04\x58\x9c\ \x48\x5c\x51\x4a\x79\x0b\xa6\x8a\x6e\x2b\xeb\x7b\x4c\x00\xdc\x06\ \xe5\x2a\x90\xd0\x6c\x03\x6e\x5d\x23\x37\xdc\x00\x80\xf1\x23\x0b\ \xb8\xf5\x2f\x0b\x2b\x93\xc7\x8f\xc0\x4f\x9f\x9e\x8f\x51\x43\xf3\ \x34\x1b\x53\x55\x09\x6b\x36\xef\xc1\xeb\xcb\xff\x09\xb3\xc5\xa6\ \xd9\xb8\x2c\x34\x99\x3a\xda\xf0\xc9\xf2\x45\xb8\xff\xc9\x1f\x21\ \x2a\x2a\xf4\x1f\xbf\xaa\x00\xec\xb2\xfa\x04\x80\xff\xee\xee\x35\ \xbd\x3c\x02\x10\x9a\x36\x50\xae\x6b\x6a\xd5\x72\xb8\xa0\x75\xf9\ \xf4\xf1\x7a\x87\xc0\x98\x5f\xa4\xa7\x25\xe3\xfb\x0f\xdf\x81\x9b\ \xae\x9a\x0e\x21\xb4\x5b\xee\x3f\x51\x59\x83\x17\x17\xaf\x42\x59\ \xf9\x69\xcd\xc6\x64\xa1\xaf\xb1\xfe\x0c\xd6\x7f\xf6\x01\x6e\xbf\ \xe7\x51\xbd\x43\xf1\x0b\x97\x2c\xdd\x8a\xfe\x26\x00\x44\xb8\xda\ \xef\x11\x75\xc3\xee\x74\xa1\xc3\x64\xd5\x6a\xb8\xa0\x36\x6b\x5a\ \xf8\x6d\x4b\x61\x91\xc5\x60\x90\x30\xff\xa6\x2b\xf0\xf4\x77\x6e\ \x46\x82\x96\xcb\xfd\x16\x1b\x96\x7e\xb4\x01\x2b\xd7\x6d\x87\xaa\ \xf2\x91\xe2\xac\xef\x8e\x1f\x2a\x46\x4e\x6e\x01\xa6\x5f\x3e\x57\ \xef\x50\x06\xcc\xea\x41\x8f\xc5\x64\xdd\x26\x00\x7b\xda\x28\x19\ \x4e\x55\xb3\x36\x74\x0d\x4d\x6d\x20\xe2\x3f\xd8\xc4\xf8\x38\x8c\ \x19\xae\x5d\x55\x34\x63\xfe\x36\x6d\xe2\x48\xbc\xf0\xd4\x3d\x18\ \x51\xa0\x5d\xbb\x55\x45\x51\xb1\x6a\xfd\x0e\x2c\xf9\x60\x1d\xac\ \x76\x87\x66\xe3\xb2\xf0\xf4\xd5\x86\x4f\x91\x95\x9b\x8f\xfc\xa1\ \x23\xf5\x0e\x65\x40\x2c\x4e\x24\xad\xaa\xa0\xac\xf9\xa3\x44\x73\ \x57\x9f\xef\x36\x01\x88\xb1\x2b\x57\x90\x24\x0c\x81\x0b\xed\x7c\ \xfc\xfc\xdf\x6b\xd2\xd8\x61\x9a\x56\x46\x33\xe6\x2f\x83\x52\x93\ \xf1\xbd\x47\xb4\x5f\xee\x2f\x3d\x5a\x89\x97\x96\xac\x42\x65\x75\ \xbd\x66\x63\xb2\xf0\xa6\xaa\x2a\xfe\xf9\xf1\xdb\x78\xfc\x7b\x3f\ \x47\x6c\x7c\xe8\xd6\x63\xa9\x04\x98\x4d\xea\xe3\x00\xfe\xaf\xab\ \xcf\x77\x9b\x00\xa8\x42\xcc\xd1\x72\x1a\x6a\x68\x69\xd7\x70\xb4\ \xe0\x35\x69\x5c\xf8\xf6\xa7\x66\xe1\xc9\x68\x30\xe0\xee\x9b\xe6\ \xe0\x99\xef\xdc\x82\xf8\xf8\x58\xcd\xc6\x6d\xed\x30\xe1\xb5\x65\ \x6b\xb0\x61\x5b\x31\xaf\x1e\x32\xbf\xb3\x9a\x4d\x58\xff\xd9\x07\ \xb8\xeb\x3b\x4f\xe9\x1d\xca\x80\x38\x3d\xe2\x36\xf4\x35\x01\x80\ \x24\x2e\x87\x86\x7f\x54\xad\xed\x3d\x76\x2c\x8c\x18\x97\x70\x02\ \xc0\x42\xc8\x8c\xc9\x63\xf0\xd3\xa7\xe6\xa3\x20\x4f\xbb\xfd\xd3\ \x1e\x59\xc1\x47\x6b\xbe\xc2\x5b\x1f\x6f\x80\xdd\xc9\x9d\x43\x59\ \xe0\x94\x1f\x3b\x84\xc3\xc5\xbb\x31\x69\xfa\xe5\x7a\x87\xd2\x6f\ \x0e\x59\x4c\xec\xee\x73\x5d\x26\x00\x44\x24\x1d\xa8\x57\xa7\x07\ \x2e\xa4\x8b\x35\xb7\x75\x68\x39\x5c\x50\x32\x18\x24\x8c\xd3\xb0\ \x2b\x1a\x63\xfd\x95\x99\x9e\x82\x67\x1f\xba\x1d\x37\x5f\x3d\x43\ \xd3\x71\x4b\x8e\x94\xe3\xa5\x25\xab\xb8\x67\x08\xd3\xcc\x96\x75\ \xab\x50\x30\x62\x0c\x52\xd2\x06\xe9\x1d\x4a\xbf\xd8\x5c\x48\x5f\ \x41\x64\x58\x20\xc4\x45\x3d\xaf\xbb\x4c\x00\x4a\x6a\x71\x89\x90\ \xa0\x69\x37\x84\x96\x36\x8b\x96\xc3\x05\xa5\x51\xc3\xf2\x10\x1f\ \x1b\xa3\x77\x18\x8c\x75\x2b\xda\x68\xc4\x77\xee\x9a\x8b\x47\xe7\ \xdf\x80\xd8\x18\xed\xf6\x4a\x37\x34\xb7\xe3\xef\x6f\xaf\xc6\xb6\ \xbd\x87\x35\x1b\x93\x31\xc0\xdb\x29\x70\xd3\x3f\x3f\xc6\x3d\x0f\ \x3f\xab\x77\x28\xfd\xe2\x56\x20\xdc\x07\x70\x23\x80\x2f\x2e\xfc\ \x5c\x97\x09\x80\x64\x50\x66\x12\x69\x53\x01\xd0\xee\x00\xca\x5b\ \x05\xaa\x1b\x79\x05\x60\xf4\xb0\x5c\xbd\x43\x60\xac\x5b\xd3\x27\ \x8d\xc6\x4f\x9f\xba\x07\x43\x87\x68\xbb\xdc\xff\xc9\xfa\x9d\x58\ \xfc\xfe\x5a\x5e\xee\x67\xba\xa9\x2a\x3f\x8a\x63\x87\x8a\x30\x7e\ \xb2\xb6\x2b\x5e\xfe\xe2\x26\xcc\x83\xaf\x09\x00\x91\x98\x19\xe8\ \x80\x14\x02\x0e\x35\x08\x54\x77\x08\x80\x08\x56\x0b\x1f\xcc\x31\ \x22\x5f\xbb\x6d\x53\x8c\xf9\x2a\x77\xf0\x20\xfc\xe8\xf1\x79\xb8\ \xf2\x52\x6d\xcf\xa7\xd8\xbe\xff\x08\xfe\xfe\xd6\x6a\xd4\x37\xf1\ \x0e\x21\xa6\xbf\x2d\xeb\x56\x61\xf8\xe8\x09\x88\x8d\x8b\xd7\x3b\ \x94\x3e\x73\xbb\xd1\x65\x11\x43\xd7\x45\x80\x02\x97\x21\x80\xf5\ \x7f\xb2\x0a\xec\x3e\x23\xa1\xed\xeb\xee\x9c\x6e\x97\x13\x8a\x22\ \x07\x6e\xc0\x10\x31\x8a\x57\x00\x58\x10\x89\x32\x1a\x30\xef\xc6\ \x39\x78\xe6\xc1\x5b\x35\x7d\x34\x55\xdb\xd8\x8a\xbf\xbd\xf9\x09\ \x76\x95\x1c\xd5\x6c\x4c\xc6\x7a\xe3\xb0\xd9\xb0\x73\xeb\x5a\x5c\ \x77\xeb\xbd\x7a\x87\xd2\x67\x76\x0f\x75\x79\xd4\xe1\x45\x09\xc0\ \xd6\x2a\x8a\x05\xd4\x80\xb6\xa2\x3b\x50\x27\xbe\x99\xfc\x01\xc0\ \xe1\xb0\x07\x72\xb8\x90\x31\x3c\x3f\x5b\xef\x10\x18\x03\xe0\xbd\ \xeb\x7f\xef\xaf\xbf\x44\xee\x60\xed\x0a\x9f\x1c\x4e\x37\xde\x5e\ \xb9\x01\x1f\x7e\xfe\x25\x3c\xf2\x45\xf5\x4a\x8c\xe9\xee\xe0\xfe\ \x9d\x98\x32\x7d\x36\x32\x06\x87\xd6\xcd\x9a\xc5\x2d\x12\x56\x94\ \x51\xe2\x82\x89\xe2\xbc\x76\xbb\x17\x1d\xf4\x93\x66\xc4\x64\x10\ \xa2\x02\x15\x48\x9d\x59\xa0\xce\x7c\x7e\x7d\x81\xc3\xc1\x07\x75\ \xa4\xa5\x24\x21\x2d\x25\x49\xef\x30\x18\x03\x00\x0c\xce\x48\xd3\ \x74\xf2\xdf\xb2\xab\x14\x0f\xfd\xf8\x0f\x58\xf6\xc9\x66\x9e\xfc\ \x59\xd0\x52\x55\x15\x9b\xd7\xae\xd2\x3b\x8c\x3e\x53\x54\xc0\xe9\ \xc2\xed\x17\x7e\xfc\xa2\x15\x00\x45\xa8\xd3\x02\x55\xfe\x27\xab\ \xc0\x91\xc6\x8b\xaf\xee\xb4\xf3\x0a\x00\xdf\xfd\xb3\x48\x54\x5d\ \xdb\x84\x17\x97\xac\x44\xf1\xe1\x72\xbd\x43\x61\xcc\x27\x35\xa7\ \xcb\x51\x79\xb2\x0c\x23\xc7\x74\xbb\xbd\x3e\x28\xb9\x65\xdc\x0c\ \xe0\x83\x73\x3f\x76\x51\x02\x20\x80\x69\x81\x0a\xe0\x54\x9b\x80\ \xc3\x73\xf1\xc7\x1d\x76\x3e\x04\x28\x2f\x3b\x34\xf7\x98\x32\xd6\ \x1f\x0e\xa7\x1b\xef\x7f\xb6\x05\xcb\x56\x6d\x86\x5b\xe6\xfa\x1f\ \x16\x5a\xb6\x6f\x5a\x83\x11\xa3\x27\x68\xda\xf2\x7a\xa0\x5c\x32\ \x2e\xea\xed\x73\x71\x11\xa0\x44\xd3\x10\x80\x2d\x80\x8a\x0a\x54\ \xb6\x77\x7d\x5d\xa7\x83\x0f\xef\xc8\xce\x4c\xd3\x3b\x04\xc6\x34\ \xb1\x71\x47\x09\x5e\x79\xfb\x33\xb4\x76\x70\xf7\x4f\x16\x9a\x5a\ \x9b\xea\x71\xa2\xec\x00\xc6\x5d\x52\xa8\x77\x28\x3e\x73\x29\x54\ \x70\xe1\xc7\xce\x4b\x00\x56\x10\x19\x50\xaf\x4e\x0a\xc4\xe0\xa7\ \x3b\x04\x5c\xdd\x24\xfa\x6e\xb7\x33\x10\x43\x86\x94\xec\x8c\x74\ \xbd\x43\x60\x2c\xa0\x4e\x9d\x69\xc0\x4b\x4b\x56\xe2\x40\x59\xa5\ \xde\xa1\x30\x36\x60\x3b\xb7\xac\xc5\x98\x09\x53\x21\x49\x17\x95\ \xd2\x05\x25\x87\x5b\x24\x11\x91\x24\x84\x50\xcf\x7e\xec\xbc\x04\ \x60\x4c\x1d\x46\xaa\x02\x7e\x3f\xbc\x5b\x21\xa0\xa2\xb5\xfb\x55\ \x05\x8f\xc7\xed\xef\x21\x43\x4e\x76\x16\xaf\x00\xb0\xf0\x74\x76\ \xb9\xff\xdd\x55\x9b\xb8\xc0\x8f\x85\x8d\x8e\xb6\x16\x9c\x38\x52\ \x12\x32\xcd\x81\x5c\x0a\xc4\x07\x87\x70\x29\x80\xbd\x67\x3f\x76\ \x5e\x02\xa0\x48\xca\x44\x11\x80\xe5\xff\x33\x1d\x02\x8e\x1e\x1e\ \xf3\x79\x5c\xdc\xe1\x2b\x3b\x93\x57\x00\x58\xf8\xd9\x59\x54\x86\ \x3f\x2f\xfe\x18\xcd\xad\x9d\x7a\x87\xc2\x98\xdf\xed\xdd\xb1\x19\ \xe3\x27\x4d\x07\x42\xa4\x16\xc0\xa3\xa8\x37\xa0\xbb\x04\x00\xaa\ \x98\x00\x3f\xff\x77\x38\x65\xe0\x58\x73\xcf\x17\x75\xbb\x23\x3b\ \x01\x30\x18\x24\x64\xa4\xa5\xe8\x1d\x06\x63\x7e\x53\x59\x5d\x8f\ \x17\x17\xaf\xc2\xc1\x63\xbc\xdc\xcf\xc2\x57\x6b\x53\x3d\x2a\xcb\ \x8f\x86\xcc\x8e\x00\x97\x2c\x5d\x76\xee\xbf\x9f\x97\x00\x08\x81\ \x09\xfe\x1e\xf0\x70\x83\x80\xbb\x97\x55\x3f\x8f\x3b\xb2\x1f\x01\ \xa4\x24\x25\xc0\x60\x08\x8d\xe7\x48\x8c\xf5\xc4\x6a\x77\x60\xc9\ \x07\xeb\xb0\x6a\xfd\x0e\x28\x8a\xda\xfb\x17\x30\x16\xe2\xf6\xef\ \xd8\x14\x32\x09\x80\x47\xa6\xf3\x9a\xfc\x5d\xb8\x0b\xc0\xaf\x09\ \xc0\xb1\xe6\x8b\x9b\xfe\x74\x19\x54\x84\xd7\x00\x24\x25\x86\x5e\ \x6f\x69\xc6\xce\x45\x44\xf8\xe2\xcb\xfd\x78\xfd\xbd\x35\x68\xef\ \xe4\x93\x3d\x59\xe4\xa8\xad\x3e\x85\xc6\xfa\x33\xc8\xce\xbd\xa8\ \xc8\x3e\xe8\x38\x3c\xe2\xbc\x03\x67\xbe\xb9\xed\x24\x22\x09\xc0\ \x58\x7f\x0c\xe2\x56\x80\x92\x3a\x81\x13\x2d\xbe\x3d\x4f\x50\xd5\ \xc8\x2e\x0c\x4a\x4d\x4a\xd4\x3b\x04\xc6\xfa\xad\xfc\x74\x1d\x7e\ \xf0\x1f\x2f\xe3\x7f\x5e\x79\x9f\x27\x7f\x16\x91\x0e\xec\xdb\xa1\ \x77\x08\x3e\xb1\xcb\x48\x58\xe8\x9d\xeb\x01\x9c\xb3\x02\x70\xa8\ \x09\x43\x81\xfe\xef\x00\x90\x55\xa0\xd3\x21\xd0\x68\x01\xce\x74\ \xf6\xbe\xec\x7f\x2e\x55\x8d\xec\xa5\xc2\xa4\xc4\x58\xbd\x43\x60\ \x61\x28\xca\x68\xc0\x82\xdb\xe7\x62\xfc\xa8\xc0\xdc\x99\x58\x6c\ \x76\x2c\x5a\xbe\x16\xab\x37\xee\x8e\xf8\xbf\x61\x16\xd9\x4e\x1c\ \x2e\xc1\xdc\x1b\xee\x42\x5c\x42\x82\xde\xa1\xf4\xc8\xa3\x40\x8c\ \x2c\xc2\x0c\x00\xfb\x80\x73\x12\x00\x55\x91\x47\x75\x71\x34\xc0\ \xf9\x5f\xac\x02\x36\x17\x60\xf3\x08\xd8\xdd\x80\xdd\x0d\xd8\x3c\ \x80\xcd\x25\x60\x97\x01\xea\xe7\x09\x82\xaa\x12\xd9\x2b\x00\xc9\ \x89\xc1\xfd\x4b\xc3\x42\xcf\xf4\x49\xa3\xf1\xc2\x93\xf3\x31\x2c\ \x00\x2d\xa6\x89\x08\xeb\xb7\x15\xe3\x95\x77\x3e\x43\x87\x2f\xeb\ \x2f\xd6\x00\x00\x20\x00\x49\x44\x41\x54\x89\xef\xf8\x19\x93\x65\ \x0f\x8e\x1c\xd8\x83\x4b\xaf\xb8\x4e\xef\x50\x7a\x27\xd4\xcb\x71\ \x61\x02\x40\x24\x8d\xba\x70\x07\x80\x4a\x40\xbb\x5d\xa0\xc5\x06\ \x34\x5b\x05\x3a\x9d\xfd\x9f\xe4\x7b\x12\xe9\x77\x0f\xc9\x5c\x03\ \xc0\xfc\x24\x6f\x70\x06\x9e\x7f\x62\x1e\xe6\xcc\x08\x4c\x51\xd2\ \xf1\xca\x33\x78\x71\xf1\x2a\x1c\x2d\xaf\x0e\xc8\xf5\x19\x0b\x55\ \x07\x8b\x77\xe1\xd2\x39\xd7\x06\xfd\x96\x40\x19\x98\x7a\xf6\x9f\ \xbf\x49\x00\x4a\x1b\xc5\x7d\x46\x01\x18\x0d\x80\x5b\x06\x3a\x9d\ \x02\x26\x87\xb7\x89\x4f\xa0\x45\x7a\x0d\x40\x62\x82\xdf\x7b\x2f\ \xb1\x08\x13\x17\x1b\x8d\x47\xee\xbe\x1e\xdf\xb9\x73\x2e\xa2\xa3\ \xfc\x7f\x98\xa7\xd9\x62\xc3\x1b\xcb\xd7\xe2\xf3\xcd\xbb\xa1\xaa\ \x1a\xbc\x29\x30\x16\x62\x3a\xdb\x5b\x51\x5b\x73\x0a\x43\x0a\x46\ \xea\x1d\x4a\x8f\x64\x45\x8c\x39\xfb\xcf\xdf\x24\x00\x16\x27\xc6\ \xb5\xd9\xf5\xc9\x5c\x22\x7d\x05\x20\xda\x78\xf1\x91\x0c\x8c\xf9\ \xea\xda\xd9\x53\xf1\xc3\x47\xef\x42\x56\x46\xaa\xdf\xaf\xad\xaa\ \x84\xcf\x37\xed\xc6\x1b\xef\xaf\x85\xd9\xc2\xc7\x76\x33\xd6\x93\ \xb2\x03\xfb\x82\x3e\x01\x70\x2b\x62\xc8\xd9\x7f\xfe\x66\xe6\x71\ \xc9\x82\x7b\xd1\xea\xc4\x68\x34\xe8\x1d\x02\x0b\x41\x05\xb9\x59\ \xf8\xc9\x93\xf3\x71\xd9\x54\xbf\x6c\xde\xb9\xc8\x89\xca\x1a\xbc\ \xb8\x78\x15\xca\xca\x4f\x07\xe4\xfa\x8c\x85\x9b\x13\x65\xa5\xb8\ \xf6\xd6\x7b\x10\x15\x15\xad\x77\x28\xdd\x72\xcb\xf8\xe6\xe8\x59\ \x23\x00\x2c\x24\x92\x9c\x9b\x28\x16\xfe\x6e\x03\xe8\xa3\x50\x3a\ \x52\x31\x10\x38\x01\x60\x7d\x91\x18\x1f\x87\x27\xef\xbf\x19\xf7\ \xdc\x7c\x45\x40\x1a\x48\x75\x9a\xad\x78\xfd\xbd\x35\x58\xbb\x75\ \x1f\x2f\xf7\x33\xd6\x07\x6e\x97\x13\x15\xc7\x0f\x7b\xdb\x03\x07\ \x29\xa7\x07\xdf\x14\x9d\x19\x01\x20\x7f\x1f\x26\x55\x07\xe0\x0c\ \x00\x5f\x09\x9d\x12\x8f\x60\x61\xe4\x47\x00\xcc\x07\x42\x08\xdc\ \x78\xd5\x74\xfc\xe0\x91\x3b\x91\x9e\x9a\xe4\xf7\xeb\xab\x2a\x61\ \xc3\xf6\x62\xfc\xfd\xed\xd5\x30\x99\x79\xb9\x9f\xb1\xfe\x38\x71\ \xe4\x40\x50\x27\x00\x6e\x05\x62\x65\x11\xe5\xdc\x33\x43\x34\x78\ \x67\x1e\x23\x2e\xeb\xe5\x6b\x02\x4a\x48\x91\x9d\x00\x44\xf1\x0a\ \x00\xeb\xc5\x84\x51\x43\xf1\x93\xa7\xef\xc6\x84\x51\x43\x03\x72\ \xfd\xc3\x27\xaa\xf0\xd2\xe2\x55\x38\x59\x55\x1b\x90\xeb\x33\xff\ \xca\x48\x4b\x41\x6e\xf6\x20\x0c\x1e\x94\x8a\x41\x69\xc9\x48\x4b\ \x49\x44\x52\x42\x3c\x62\xa2\xa3\x10\x15\xfd\x6d\x11\xa8\xd3\xe9\ \x82\xc5\xe6\x40\x87\xc9\x82\xf6\x4e\x0b\x1a\x9a\xdb\x71\xaa\xa6\ \x81\x13\xbc\x00\x3a\x5d\x71\x1c\x1e\xb7\x0b\x51\xd1\x31\x7a\x87\ \xd2\x25\x02\xe0\x10\x98\x05\xe0\x13\x23\x00\x78\x3c\xea\xd4\xde\ \x7a\x00\x04\x52\xc4\xaf\x00\x18\x38\x01\x60\x5d\x4b\x4b\x49\xc2\ \x73\x0f\xdd\x86\x5b\xe6\x5e\x06\x29\x00\x89\x72\x7b\xa7\x05\xaf\ \xbf\xb7\x06\x5f\x7c\xb9\x1f\x14\x88\x3d\xbe\x6c\x40\x24\x49\xc2\ \xb0\x21\xd9\x98\x30\x3a\x1f\x13\x46\x0d\xc5\xc8\x61\x39\x18\x96\ \x37\x18\x09\xf1\x03\xdb\x39\x64\x32\xdb\x50\x56\x5e\x8d\x92\x23\ \xe5\x28\x3d\x7a\x0a\x27\xab\x6a\xf8\x71\x8f\x9f\xc8\xb2\x07\x95\ \x27\xcb\x30\xee\x92\x42\xbd\x43\xe9\x96\x4c\x98\x8a\xb3\x09\x80\ \x42\x62\x84\x9e\xc1\x44\xfa\x0a\x80\x24\xf1\x41\x40\xec\x7c\x06\ \x83\x84\x7b\x6e\xbe\x02\x4f\xde\x7f\x33\x12\x07\xf8\x66\xdf\x15\ \x45\x51\xb1\x6a\xfd\x0e\x2c\xf9\x60\x1d\xac\x76\x87\xdf\xaf\xcf\ \xfa\x2f\x6b\x50\x2a\x66\x4e\x1b\x87\x99\x53\xc7\xe3\xd2\xc9\xa3\ \x07\x3c\xd9\x77\x25\x25\x39\x01\xb3\xa7\x4f\xc0\xec\xe9\xde\xe3\ \x5f\x5a\xda\x4d\xd8\xba\xab\x14\x9b\x76\x1e\xe0\x1e\x0f\x7e\x70\ \xb2\xac\x34\xb8\x13\x00\x55\x1d\x03\x7c\x5d\x03\xa0\xa8\x22\x4f\ \xcf\x60\x22\x7d\x05\x40\x96\x65\xbd\x43\x60\x41\xa4\x70\xe2\x28\ \xfc\xe4\xa9\xf9\x18\x51\x90\xd3\xfb\x8b\xfb\xe1\xe0\xb1\x4a\xbc\ \xb4\xf8\x13\x54\x54\xd7\x05\xe4\xfa\xac\x6f\x24\x49\xc2\xd4\xf1\ \x23\x30\xab\x70\x3c\x66\x4d\x1b\x87\x11\x05\xb9\x9a\xc7\x90\x99\ \x9e\x82\x05\xb7\x5f\x8d\x05\xb7\x5f\x8d\xf2\xd3\x75\x78\xff\xb3\ \xad\xd8\xb2\xb3\x14\x72\x84\x77\x69\xed\xaf\xaa\xf2\x63\xf0\x78\ \xdc\x41\xbb\x1b\x40\x51\xa5\x21\xc0\xd7\x09\x80\xac\x22\x43\xcf\ \x60\x44\x84\x2f\x81\xf3\x1f\x19\x03\x80\xac\x8c\x54\xfc\xf0\xd1\ \xbb\x70\xed\xec\xa9\xbd\xbf\xb8\x1f\xda\x3a\xcd\x78\xf5\xdd\xcf\ \xb1\x61\x5b\x31\x2f\xf7\x07\x81\x61\xf9\xd9\xb8\xf5\xea\x4b\x71\ \xe3\xd5\xd3\x91\x91\x96\xa2\x77\x38\xdf\x18\x3d\x2c\x0f\xbf\x79\ \xfe\x61\x3c\xfb\xe0\x6d\x58\xf4\xfe\x5a\xac\xe7\xdf\x97\x3e\xf3\ \x78\xdc\xa8\x39\x5d\x81\x11\xa3\xfd\x7a\xc0\xae\xdf\xa8\x84\xc1\ \xc0\xd7\x09\x80\x47\x81\xff\x4b\x8a\xfb\xc0\x68\xf4\x7f\xe7\xb2\ \x50\x22\xcb\x9c\x00\x44\xb2\xe8\xa8\x28\x7c\xe7\xce\xb9\x78\x74\ \xfe\xf5\x88\x8d\xf1\xff\x1d\x83\xac\x28\x58\xf9\xc5\x0e\x2c\x59\ \xb1\x0e\x76\xbb\xd3\xef\xd7\x67\xbe\x4b\x4e\x8c\xc7\xf5\x73\xa6\ \xe1\xe6\x6b\x2e\x0d\x58\x41\xa7\xbf\x0c\xce\x48\xc3\xaf\x7f\xf4\ \x10\xe6\xdf\x72\x05\xfe\xb2\xf8\x13\x1c\xad\xe0\x47\x03\x7d\x51\ \x55\x7e\x34\x68\x13\x00\x97\x42\x69\xc0\xd7\x09\x80\x4a\xd0\xb5\ \x5c\x31\xd2\xb7\xc1\x79\x38\x01\x88\x58\x73\x66\x4c\xc4\xf3\x4f\ \xcc\x43\xde\xe0\xc0\x2c\xc2\x95\x94\x55\xe0\xa5\xc5\x2b\x51\x55\ \xd3\x18\x90\xeb\x33\xdf\x0c\xcb\xcf\xc6\x83\x77\x5e\x83\xeb\xaf\ \x2c\x0c\xb9\xce\x9f\x13\x46\x0d\xc5\xeb\xff\xf3\x3c\xde\x5e\xb9\ \x11\x6f\x7d\xbc\x01\x8a\x12\xd9\x9d\x5b\x7d\x55\x55\x7e\x4c\xef\ \x10\xba\x25\xab\x48\x00\xbe\x5d\x01\xd0\xf5\x37\xd2\x10\xe9\x2b\ \x00\xfc\x08\x20\xe2\x14\xe4\x66\xe1\xf9\xc7\xe7\x61\x56\xe1\xf8\ \x80\x5c\xbf\xa5\xdd\x84\x57\xde\xfe\x14\x9b\x76\x1e\x08\xc8\xf5\ \x99\x6f\xa6\x4d\x1c\x85\x07\xee\xba\x06\x97\x4f\x1b\x1f\xd2\x0d\ \xcf\x24\x49\xc2\x13\xf7\xdd\x84\x19\x93\xc7\x60\xe1\x4b\xef\xa2\ \xa9\xb5\x43\xef\x90\x82\x5e\x67\x7b\x2b\xda\x5b\x9b\x91\x9e\x91\ \xa5\x77\x28\x17\xf1\x28\x22\x1a\x00\x8c\x2b\xca\x28\xba\xa6\x8d\ \x84\xd9\x25\x02\x72\xd2\x9f\x2f\x8c\x01\x38\xbc\x24\x94\x28\x9c\ \x00\x44\x8c\xf8\xd8\x18\x3c\x76\xef\x0d\x58\x70\xfb\xdc\x80\xf4\ \x7f\xf0\xc8\x0a\x3e\x5a\xf3\x15\xde\xfa\x78\x03\xec\xff\x9f\xbd\ \xfb\x8e\x8e\xb3\x3a\x13\x3f\xfe\xbd\xef\x14\xf5\x51\xb1\x7a\x97\ \x6d\xc9\xb6\x64\xcb\x96\x2c\xdb\xe0\x8a\x0b\x60\x1b\x07\x5c\x08\ \x09\x25\x05\x30\x21\x85\x24\x40\x36\xbb\xd9\x24\xfb\x4b\x76\xb3\ \x9b\xb6\xd9\x40\x08\x29\x24\xb4\x84\x90\x50\x8d\xe9\x60\x70\x01\ \x8c\x71\x95\x4d\x71\xc5\x4d\xdd\x92\x65\xf5\xae\x99\xf7\xfe\xfe\ \x10\x26\x36\xc8\xb6\xca\xbc\x65\x34\xf7\x73\x4e\xce\xf1\x91\x67\ \xee\x7d\x82\x47\x73\x9f\xf7\x96\xe7\x76\x75\xfb\xbd\x7d\xe5\xc2\ \x84\x10\xcc\x9b\x31\x89\xeb\x97\x2f\x64\xc2\xd8\x4c\xab\xc3\xf1\ \xab\x49\xe3\x72\xb8\xef\x67\xdf\xe6\xbb\xff\xf3\x67\x3e\x3c\xae\ \x36\x91\x5e\xc8\xf1\x23\xfb\x6d\x99\x00\xf4\x78\x71\xdc\x27\xa5\ \xcb\x99\xe7\x21\x61\x6c\x8c\xa4\xa3\x47\x72\xe8\x94\xa0\xac\x41\ \x60\x76\x1e\x10\xec\x4b\x00\xed\x9d\xea\x8b\x7a\xa4\x13\x42\xb0\ \x68\x76\x11\x5f\xff\xc2\x95\x24\xc4\x19\xb3\xe1\x6b\xc7\xbb\x87\ \xb8\xeb\xc1\x35\x94\x57\xd5\x1a\xd2\xbe\x72\x61\x53\x27\xe5\xf2\ \xf5\x1b\x3e\xc3\xb8\x31\x19\x56\x87\x62\x98\xf8\xd8\x68\x7e\xf7\ \x93\x6f\xf2\x83\x5f\x3d\xc8\x8e\x77\x0f\x59\x1d\x8e\xad\x55\x1c\ \xfd\x90\xe2\x19\xf3\xac\x0e\xe3\x53\x7c\x12\x92\x0f\x32\xda\x89\ \x46\x22\x40\xb8\x1b\xa6\xa4\x48\xd2\x3d\x92\xd2\x6a\x8d\x8e\x1e\ \xf3\x82\x09\xf6\x25\x80\xb6\xb6\x0e\xab\x43\x50\x0c\x34\x36\x2b\ \x8d\x3b\x56\xaf\x60\xf2\x04\x63\x6e\x09\x3b\xd9\xd0\xcc\x1f\x1f\ \x7d\x81\x57\xdf\xd8\x69\x48\xfb\xca\x85\xe5\x64\x24\x73\xe3\x35\ \x8b\x59\x70\xf1\x64\xab\x43\x31\x45\x78\x58\x08\xbf\xfc\xf7\xaf\ \xf0\x6f\x3f\xff\x33\xdb\xf7\x1c\xb4\x3a\x1c\xdb\xaa\x28\x3b\x82\ \x94\xd2\x96\xcb\x3f\xed\x9d\x4c\x70\x22\xbc\x09\xc8\x7f\x16\xa2\ \x89\x8f\x80\x79\x39\x3a\x5b\xca\x34\x9a\x4d\xda\x30\xec\x76\xdb\ \xf3\xac\xa4\x59\x5a\x54\x02\x30\x22\x79\x22\xc3\x59\x7d\xed\x52\ \x96\x5f\x7a\xb1\x21\xc5\x9e\x7a\xbc\x5e\x1e\x7b\x76\x23\x7f\x5d\ \xf3\x3a\x5d\xdd\x26\x66\xec\xca\xc7\x12\xe3\x63\xf8\xea\x75\xcb\ \x58\x34\xbb\xd8\x90\x4a\x8d\x76\xe6\x72\x3a\xf8\xaf\xef\xdc\xc8\ \xea\xef\xff\x81\xca\x0a\x75\x42\xa0\x3f\x5d\x9d\x1d\xd4\xd7\x55\ \x93\x90\x64\x69\xa9\x9d\x7e\xf5\xf8\xf4\x31\x4e\x9f\x14\xb1\x9f\ \xfc\xd8\x86\x38\x61\x76\x8e\xce\xdb\xc7\x35\x9a\x4c\x28\x12\xe6\ \x0e\xf1\x7f\xa5\xab\x40\xd2\xd2\xae\x12\x80\x91\x44\xd3\x04\xcb\ \x16\x5e\xc4\xad\xd7\x5e\x41\xb4\x27\xc2\x90\x3e\xde\xd9\xbd\x9f\ \xdf\x3c\xb0\x86\xca\x13\xf5\x86\xb4\xaf\x9c\xdf\xe9\xa3\x9b\x5f\ \x58\xb1\x88\xb0\xd0\xe0\x7d\x80\x39\xd2\x18\xca\xf2\xeb\x6e\xe5\ \xd1\x3f\xff\x9a\xc6\x06\xf5\x59\xec\x4f\xc5\xf1\xc3\xb6\x4c\x00\ \xa4\x24\xc9\x89\x14\x31\xfd\x15\xe2\x73\x69\x70\x71\xa6\xce\x1b\ \xc7\x8c\x5f\x0e\x08\x0d\x0d\x35\xb6\x03\x9b\x6b\x6d\x53\xa5\x58\ \x47\x8a\x49\xe3\x72\xb8\xe3\xe6\x95\xe4\x8d\x4e\x37\xa4\xfd\xea\ \xda\x53\xdc\xf3\xf0\x33\x6c\xde\xb1\xd7\x90\xf6\x95\x0b\x33\xfa\ \xe8\x66\xa0\xa8\x6a\x16\x1c\x6b\x14\x84\x86\x47\xf0\x99\xcf\xdd\ \xc4\xdf\xff\x7c\x17\x5e\x6f\xaf\xd5\x61\xd9\x4e\xe5\xf1\x23\xb6\ \xdc\x07\x20\x25\xf1\x4e\xa1\x11\x7b\xae\x5d\x7f\x21\xce\xbe\x24\ \xe0\xad\x63\x1a\x3d\x06\x6e\x54\x0f\xf6\x19\x80\x56\xb5\x04\x10\ \xf0\xe2\x62\x3d\xac\xbe\x66\x31\xcb\x16\x5e\x64\xc8\x54\x70\x77\ \x4f\x2f\x4f\xbd\xf4\x26\x0f\x3f\xb5\x8e\xce\x2e\x35\xdd\x6f\x85\ \xf4\xe4\x78\xbe\x75\xe3\x8a\x8f\xeb\xe7\x07\xb3\x2e\x2f\xbc\x5b\ \xf3\xcf\xcf\x79\x62\x72\x1a\x0b\x96\xae\x64\xdd\x73\x8f\x5b\x18\ \x95\x3d\x55\x55\x1c\xb3\x3a\x84\x7e\x49\x4d\x8b\x75\x22\x39\xef\ \x96\xe4\xa8\x10\x98\x91\xd1\xb7\x27\xc0\x67\xd0\xf1\x80\x90\x90\ \xe0\x9e\x01\x38\xd5\xdc\x6a\x75\x08\xca\x10\x39\x1d\x0e\xae\x5e\ \x3a\x87\x9b\x3e\x7b\x39\xe1\xe1\xc6\x7c\x8e\x37\xef\xd8\xcb\x3d\ \x0f\x3f\x43\x75\xed\x29\x43\xda\x57\xce\x2f\x3c\x34\x84\x2f\xae\ \xba\x94\xcf\x7d\xc6\x98\xa3\x9b\x81\xe8\xc3\x7a\xf1\xa9\x87\xc2\ \xc2\xa9\x33\x29\x3b\x7a\x88\x83\x1f\xa8\xda\x13\x67\x6a\x6f\x6d\ \xa1\xb5\xb9\x89\xa8\xe8\x18\xab\x43\x39\x8b\xae\xcb\x18\x27\x82\ \x98\x0b\x9d\xfb\x1b\x15\x01\x85\x29\x92\xdd\xd5\xc6\x6c\x72\x71\ \x07\xf9\x12\x40\x67\x67\x37\x6d\x1d\x9d\x86\xdc\xfa\xa6\x18\xc7\ \xe8\x4b\x7b\xaa\x6a\xeb\xf9\xcd\x03\xcf\xb0\xa5\x74\x9f\x21\xed\ \x2b\x17\x36\x73\x6a\x3e\x77\xae\x5e\x45\x72\x42\x9c\xd5\xa1\xd8\ \x4a\x6d\x5b\xff\x63\xc1\xc2\x25\xab\x38\x7e\xf8\x20\xdd\x5d\x6a\ \x56\xf3\x4c\x35\x55\x65\xb6\x4b\x00\x7c\x92\xe8\x0b\xce\x00\x9c\ \x96\x15\x2b\x69\xeb\xe9\xcb\xfc\xfc\x2d\x34\x54\x0d\x7c\x35\x75\ \x0d\xe4\x66\xdb\x6f\xa3\x88\xf2\x69\x29\x89\x71\x7c\xf3\x4b\xcb\ \x99\x3b\x63\x92\x21\xed\x77\x75\xf7\xf0\xd7\x35\xaf\xf3\xd8\xb3\ \x1b\xe9\x51\x37\x45\x5a\x62\x4c\x56\x2a\x77\xdc\xbc\x92\x29\xf9\ \xc6\x1c\xdd\x0c\x74\xfa\x39\x1e\x1a\xc3\x23\xa3\x98\xb3\xf0\x0a\ \x5e\x7f\xf1\x49\x73\x03\xb2\xb9\x9a\xaa\x32\xf2\xf2\xed\x75\x44\ \x54\xd7\x45\xa4\x13\x44\x24\x03\x2c\xfd\x93\x9f\x28\xe9\xea\x85\ \x8a\x66\xff\x26\x01\xa1\xa1\xc6\xec\x94\x0e\x24\x27\x4e\x36\xaa\ \x04\xc0\xe6\x42\xdc\x2e\xae\x5f\xbe\x80\xeb\x97\x2f\x24\xc4\x6d\ \x4c\xed\x8a\x4d\x5b\xdf\xe3\xb7\x0f\xaf\x55\xa5\x56\x2d\x12\x15\ \x11\xce\x2d\xd7\x2e\x61\xf9\x65\x33\x0d\x39\xba\x39\x52\x24\x45\ \x4a\x8e\x35\xf4\x3f\x0e\x4c\x9e\x36\x8b\xbd\x7b\xb6\x53\x53\xa5\ \x8e\x06\x9e\x76\xa2\xb2\xdc\xea\x10\x3e\xc5\x27\x09\x73\x02\xe1\ \x03\x7d\x83\x10\x50\x9c\x26\x71\x68\x70\xbc\xd1\x7f\x49\x40\x68\ \x78\x38\x9a\xa6\xa1\xeb\xc1\x7b\xc9\xc4\x89\x93\x0d\x56\x87\xa0\ \x9c\xc7\xbc\x8b\x0a\xb9\xed\x8b\x57\x91\x92\x68\xcc\x54\x70\x79\ \x55\x2d\x77\x3d\xb8\x46\x55\x56\xb3\x88\x19\x47\x37\x47\x92\xdc\ \x51\x92\xb2\x06\x41\x7f\xdf\xd8\x42\x08\xe6\x2c\x5a\xc6\x13\x7f\ \xf9\x9d\xe9\x71\xd9\x55\x6d\x4d\x05\x48\xd9\x37\x88\xda\x84\xd7\ \x27\x43\x9d\xa0\x87\xd1\xdf\x39\xc0\x73\x10\x02\x26\xa7\x4a\xa2\ \x42\x60\x7f\x9d\xc0\xeb\x87\x31\x5b\x08\x41\x58\x44\x24\xed\xad\ \x2d\xc3\x6f\x2c\x40\xd5\x9e\x54\x4f\x7c\x76\x94\x95\x9e\xc4\xed\ \x37\xae\x64\xda\xe4\x3c\x43\xda\xef\xe8\xea\xe6\xe1\xa7\xd6\xf1\ \xe4\x0b\x6f\xa8\x5b\x21\x2d\x32\x31\x2f\x9b\x3b\x6e\x5e\x39\xa2\ \xcb\xf7\xfa\x5b\xb8\x1b\xd2\x63\x24\xe5\x4d\xfd\x8f\x1d\x99\xa3\ \xf3\x48\xcf\x1c\x43\x65\xf9\x11\x93\x23\xb3\xa7\x9e\xee\x2e\x9a\ \x9b\x1a\x88\x8e\x1d\x65\x75\x28\x1f\xd3\x25\x6e\x27\x88\x01\xcf\ \x00\x9c\x26\x80\x31\xa3\x24\xc9\x51\x92\x7d\xb5\x82\x13\xad\x62\ \xd8\x27\x04\xc2\x83\x3c\x01\x28\xaf\xae\xb3\x3a\x04\xe5\x0c\x61\ \xa1\x6e\xae\xbd\x72\x3e\x37\xac\x5c\x64\xd8\xf5\xad\x6f\xef\xdc\ \xcb\xaf\xef\x7f\x5a\x4d\xf7\x5b\xc4\x13\x15\xc1\x97\xaf\xbe\x8c\ \xab\x97\xcc\x09\xba\x2a\x7e\xfe\x90\x17\x7f\xee\x04\x00\x60\xe6\ \x82\xc5\x3c\xf1\xb0\x9a\x05\x38\xed\x64\x6d\x8d\xcd\x12\x00\xe1\ \x1c\xd4\x12\xc0\x27\x45\xb8\x61\x5a\x86\xc4\xeb\x93\x34\x76\x09\ \x9a\x3a\xa1\xa5\x1b\x5a\xbb\x04\x2d\xdd\xe7\xde\x28\xd2\x9f\xf0\ \x88\xa8\xa1\x86\x31\x22\x1c\x29\xab\xb1\x3a\x04\x85\xbe\xd9\xa8\ \xcb\xe7\x4e\xe5\xeb\x37\x7c\x86\xb8\x58\x8f\x21\x7d\x1c\xab\x38\ \xc1\xaf\xef\x7f\x8a\xdd\x7b\xd5\xd3\x91\x15\xcc\x38\xba\x19\x0c\ \x22\x43\xc0\x13\x0a\x2d\xe7\x28\x19\x9f\x99\x93\x47\x5a\x46\x8e\ \x6d\xcf\xc1\x9b\xad\xbe\xb6\x9a\xb1\xe3\x27\x5a\x1d\xc6\xc7\xa4\ \xc4\xe1\x04\x86\xbd\x05\xdf\xe9\x80\x84\x08\x49\xc2\xc7\x4b\x67\ \x12\xaf\x0f\x6a\xda\x04\xd5\x2d\x50\xdb\x2a\x2e\x98\x0c\x04\x7b\ \x02\x50\x5b\xdf\x48\x6b\x7b\x07\x51\x11\x43\xce\xc7\x94\x61\xca\ \xcb\x49\xe7\xf6\x9b\x57\x50\x38\x7e\xb4\x21\xed\x77\x76\xf5\xf0\ \x8f\xe7\x36\xf0\xc8\x9a\xd7\xd5\x74\xbf\x45\x8c\x3e\xba\x19\x6c\ \x22\xdc\x92\x96\xae\x73\xcf\x02\x4c\x9e\x36\x4b\x25\x00\x1f\xa9\ \xaf\xab\xb6\x3a\x84\xb3\x09\x34\x27\x60\x48\x0a\xec\x74\x40\x46\ \xb4\x24\x23\x1a\xba\xbd\x92\x8a\x66\x41\x6d\x2b\x34\x74\xf4\xbf\ \x5c\x10\x11\x11\x69\x44\x18\x01\xe5\x48\x59\x8d\x3a\x76\x64\x81\ \x68\x4f\x04\xb7\x5e\x7b\x85\x61\x55\xfc\xa4\x94\xac\x7b\x73\x17\ \xbf\xfb\xdb\xf3\x34\x34\x06\xef\x32\x97\x95\x92\x13\xe2\xb8\xed\ \x4b\x57\x71\xc9\x45\x85\x56\x87\x32\xa2\xf4\x5e\x20\x8f\xcd\x2b\ \x98\xc2\x86\x57\x9e\xa1\xab\xa3\xdd\x9c\x80\x6c\xec\x64\x9d\xbd\ \x66\x79\x75\xbd\x2f\x01\x30\x66\x81\xf3\x0c\x21\x4e\x18\x3b\x4a\ \x32\x76\x14\xf8\xa4\xa4\xa9\xb3\x6f\x99\xa0\xad\xa7\x6f\xc9\xa0\ \xad\x5b\x10\xe9\xb1\x57\x91\x04\x2b\xa8\x04\xc0\x5c\x9a\xa6\xb1\ \xfc\xb2\x99\xac\xfe\xfc\x12\x3c\x91\xc6\xcc\xbc\x1c\x2e\xab\xe2\ \xd7\xf7\xaf\xe1\xbd\xfd\x47\x0d\x69\x5f\x39\xbf\x10\xb7\x8b\xeb\ \xae\x5a\xc0\xf5\xcb\x17\x10\x1a\x12\xbc\x97\xf6\x18\xa5\xa3\xe7\ \xfc\x09\xb3\xd3\xe9\x62\xe2\x94\xe9\xec\xdc\xb2\xd1\xa4\x88\xec\ \xab\xf1\xd4\x49\x74\x9f\x0f\xcd\x61\x8f\x6a\x92\x52\x22\x9c\x12\ \x9c\x66\x6e\x7f\x71\x08\x18\x15\x0e\xa3\xc2\xcf\x9c\x06\x90\x68\ \x27\x63\xd8\xf4\xaa\x89\x81\xd8\xd0\xc1\xa3\x15\x56\x87\x10\x34\ \x26\x4f\x18\xc3\x1d\xab\x57\x30\x36\xcb\x98\xda\x0b\x6d\x1d\x9d\ \xfc\xf9\x1f\x2f\xf1\xcc\xab\x5b\x82\xfa\x78\xab\x95\xe6\x4e\x9f\ \xc8\x6d\x5f\x5a\x4e\x6a\x92\x7d\x36\x5e\x8d\x24\x3d\x3e\xe8\x18\ \xc0\xdd\x3f\x93\x8a\x2e\x52\x09\x00\xa0\xfb\x7c\x34\x37\x35\x10\ \x3b\x2a\xc1\xea\x50\x80\xbe\x3d\x7a\x4e\x01\xb6\x48\x47\x52\x12\ \x07\x54\x90\x70\x44\x7b\x77\x9f\x7a\x4a\x34\x5a\x42\x5c\x34\x5f\ \xff\xc2\x95\x2c\x9a\x5d\x84\x30\xe0\x4c\xae\x94\x92\x97\x37\xed\ \xe0\x0f\x7f\x7b\x81\x46\x75\xc7\x83\x25\x32\x53\x13\xb9\xfd\xe6\ \x95\x4c\x9f\x3c\xce\xea\x50\x46\xb4\xd6\xee\x81\xbd\x6e\x54\x62\ \x32\x71\x09\x49\x34\x9c\xac\x35\x36\xa0\x00\xd0\xd8\x70\xd2\x36\ \x09\x00\x20\x9c\xd8\x24\x01\x48\x1c\x15\x6b\x75\x08\x96\xab\xaa\ \xad\xa7\xae\xbe\x89\xc4\x78\xb5\x1c\xe2\x6f\x4e\x87\x83\x15\x97\ \xcf\xe2\x96\xcf\x2f\x31\x6c\xe7\xf7\xa1\xa3\x95\xdc\xf5\xc0\x1a\ \xde\x3f\xa8\x36\x3d\x59\xc1\x8c\xa3\x9b\xca\x3f\xf5\xf8\x06\x9e\ \x40\xe7\xe5\x4f\x66\xeb\x1b\xeb\x0c\x8c\x26\x30\x34\x35\x9c\xb4\ \x3a\x84\x8f\xf9\xa4\xb4\x4f\x02\x30\x2a\x26\x0a\xa7\xc3\x81\xd7\ \x17\xdc\xbb\xa3\xdf\xdd\x7f\x94\x4b\xe7\x14\x5b\x1d\xc6\x88\x32\ \x75\x52\x2e\x77\xdc\xb4\x92\xec\x8c\x64\x43\xda\x6f\x69\xeb\xe0\ \xa1\x27\x5e\xe5\xe9\x57\x36\xab\xe9\x7e\x0b\x08\x21\x98\x7f\x51\ \x21\xb7\x7d\x69\xb9\x4a\x9e\x4d\x14\xe2\x90\x0c\xb4\x88\x5c\xee\ \xf8\x42\x95\x00\xd0\xb7\x0f\xc0\x2e\xa4\x14\xd8\x26\x01\xd0\x34\ \x8d\xf8\xb8\xe8\xa0\x2f\x89\xbb\x7b\xdf\x61\x95\x00\xf8\x49\x5a\ \x52\x3c\xdf\xba\x71\x39\xb3\x4a\x0a\x0c\x69\xdf\xe7\xd3\x59\xbb\ \x6e\x0b\xf7\x3f\xf6\x32\xad\xed\xea\xf6\x33\x2b\x18\x7d\x74\xb3\ \xa7\xb7\x97\xc7\x9e\xdb\xc4\x94\x82\x31\x86\xf5\x11\xa8\x62\xc3\ \x20\xcc\x09\x9d\x03\xb8\xaf\x2a\x29\x25\x1d\x4f\x4c\x1c\x2d\x4d\ \xc1\xfd\xfd\x6e\xa7\x04\x40\xc7\x84\x13\x00\x83\x91\x9a\x18\x17\ \xf4\x09\xc0\xce\xf7\xac\xab\x05\xaf\x03\x75\xad\x82\x50\x97\x24\ \x26\x80\xeb\xa3\x84\x84\x38\xf9\xca\xb5\x4b\xf9\xfc\x95\x97\xe0\ \x76\x19\x73\x69\xcf\xee\xbd\x87\xb9\xfb\xc1\x67\x38\x52\x66\xb3\ \xb3\xbd\x41\xc2\x13\x15\xc1\x57\xae\x5d\xca\x95\x8b\x2e\x32\xec\ \xd2\x9e\xb7\x77\xee\xe5\x9e\x87\xd6\x52\x55\x5b\xcf\x5d\xff\xf1\ \x35\x43\xfa\x08\x64\x42\x40\x7e\xb2\x64\x57\xe5\x00\x66\x01\x84\ \x20\x6b\xcc\x38\xde\xdf\xf5\x8e\xf1\x81\xd9\x58\x73\x43\xbd\xd5\ \x21\x9c\xc5\x09\xf8\xb0\x49\x22\x90\x9e\x9a\x40\xe9\xde\xc3\x56\ \x87\x61\xa9\xea\xda\x53\x1c\x3a\x56\x49\x5e\x4e\xba\xa9\xfd\x76\ \xf6\xc2\xd6\x72\x8d\xe6\xae\xbe\x29\xd5\x29\xa9\x92\xac\x98\x61\ \xd6\x77\xb6\x48\xe1\xf8\xd1\x86\x3d\xad\xd5\x37\x36\xf3\x87\xbf\ \xbd\xc0\xba\x37\x77\x21\x65\x60\xfe\xf7\x09\x64\x9a\x26\xb8\x74\ \xce\x54\xbe\xf9\xa5\xab\x88\xf1\x18\x53\x3b\xa4\xf2\x44\x3d\xf7\ \x3c\xf8\x0c\x5b\x4a\xf7\x19\xd2\xfe\x48\x92\x11\x2d\xa9\x6f\x83\ \xb2\xf3\x94\x04\x3e\x2d\x33\x7b\x6c\xd0\x27\x00\x2d\xcd\x4d\xb6\ \xb9\x14\x48\xc3\x66\x09\x40\x46\x8a\x6d\x76\x47\x5a\xea\xcd\x6d\ \xef\x9b\x9a\x00\x34\x77\xc1\xd6\x32\xed\xe3\xa9\x3c\x29\xe1\x60\ \x9d\x08\xd8\x04\xc0\x08\x5e\x9f\x8f\x35\xaf\xbc\xcd\x9f\x1f\x7b\ \x89\xce\xce\x01\x6e\x7f\x56\xfc\x6a\xc2\xd8\x4c\xee\x58\xbd\x92\ \xfc\xb1\x59\x86\xb4\xdf\xd5\xdd\xc3\xdf\x9f\xdd\xc0\xdf\xd6\xac\ \xa7\xc7\x3b\x80\x79\x6d\x05\x80\xc9\x69\x92\x2e\x5f\x5f\xc5\xd7\ \xf3\x49\xcf\x1e\x6b\x52\x44\xf6\xe5\xf3\x79\xe9\xe8\x68\xb3\x45\ \xe5\x5b\x21\xe4\xc7\x09\x80\x2d\x64\xa4\x24\x5a\x1d\x82\x2d\xbc\ \xb1\xed\x3d\x56\x7f\x7e\x89\x29\x7d\x55\x34\x09\xde\x3d\x21\xf8\ \x64\x65\xda\xa8\xd0\xf3\x0f\xfe\x9d\x5e\x68\xec\x10\x44\x87\x4a\ \x22\x46\x78\x7d\x95\x77\x76\xef\xe7\x9e\x07\x9f\xa1\xa2\xc6\x3e\ \xeb\x77\xc1\x24\x2e\xd6\xc3\x37\x6e\xf8\x0c\x97\xcd\x9d\x6a\xd8\ \xd1\xcd\xd7\x37\xef\xe6\xf7\x8f\x3c\xc7\xc9\x86\x66\xbf\xb7\x3f\ \xd2\x69\xc0\xf4\x0c\xc9\xce\x0a\xa8\x39\x4f\x12\x10\xe5\x89\x21\ \x3a\x76\x14\xcd\x8d\xa7\xcc\x0b\xce\x86\x5a\x9a\x1b\x6d\x91\x00\ \x38\x84\x90\x4e\x09\x83\x38\xcc\x61\xac\xf4\x64\x55\xb0\x03\xfa\ \x2e\x8b\x29\xab\xac\x25\x2b\x3d\xc9\xb0\x3e\xba\xbd\xb0\xa7\x5a\ \xf4\xfb\x0b\xeb\x76\x40\x61\x72\xff\x09\x80\x94\x70\xb0\x5e\x70\ \xa8\xae\xef\x2e\x70\x81\x20\x29\x4a\x32\x2e\x41\x12\x3b\xec\x5b\ \x25\xec\xa5\xaa\xb6\x9e\x7b\x1e\x5a\xcb\xdb\x3b\xf7\x5a\x1d\x4a\ \x50\x72\x38\x34\xae\x5e\x32\x87\x9b\xaf\x59\x6c\xd8\xd1\xcd\xc3\ \x65\x55\xdc\x75\xff\x33\xbc\xbb\x5f\x5d\xcc\x34\x1c\x0e\xd1\x97\ \x04\xbc\x5b\x03\xc7\x1b\xcf\x3d\xa2\xa4\x67\x8d\x09\xfa\x04\xa0\ \xb5\xb9\x89\xe4\xd4\x4c\xab\xc3\x00\x90\x4e\x01\xb6\x99\xeb\x4a\ \x4d\x89\x47\xd3\x34\x75\x94\x0a\x78\x7e\xfd\x56\x6e\xfb\xd2\x55\ \x7e\x6f\x57\x07\x8e\x9d\x12\x1c\x3c\x29\xe8\x39\xc7\xdc\xcf\x94\ \xd4\xfe\x9f\xea\xa5\x84\xd2\x6a\x41\xc5\x19\xeb\x7d\x12\x38\xd1\ \x2a\xa8\x6d\x15\x64\x44\x4b\x0a\x92\x25\x21\xb6\x58\x50\x1a\xba\ \xce\xae\x1e\x1e\x79\xe6\x75\x1e\x7b\x6e\x13\x3d\xbd\x03\x28\x75\ \xa6\xf8\x5d\x51\xc1\x18\xee\x5c\x7d\x35\x39\x06\x1e\xdd\xbc\xff\ \xb1\x97\x59\xbb\x4e\x55\x6a\xf4\x17\x21\xfa\xbe\x3b\x22\x43\x60\ \x6f\xad\xa0\xbf\x2d\x32\x49\xa9\xe9\xec\xdd\xb3\xdd\xfc\xe0\x6c\ \xa4\xa5\xd9\x1e\xd7\x7f\x6b\xa2\x6f\xed\xdf\x36\x09\x80\xdb\xe9\ \x24\x3d\x39\x9e\xf2\xea\x3a\xab\x43\xb1\xdc\x4b\x9b\x76\xf0\x95\ \xeb\x96\xfa\x6d\x17\x7b\x5b\x0f\x54\x35\x0b\x8e\x37\x88\xf3\x1e\ \xdb\x09\x77\x43\xaa\xe7\xd3\xbf\xb9\xba\x84\xed\x15\x82\x13\xe7\ \x98\xe2\x93\x40\x79\xb3\xa0\xba\x4d\x30\x3e\x41\x32\x7a\x94\xc4\ \x98\xbd\xd9\xc6\xda\xf0\xce\xbb\xdc\xfb\x97\xb5\xd4\xd5\x37\x59\ \x1d\x4a\x50\x4a\x88\x8b\xe6\x1b\x5f\xba\x8a\x45\xb3\x8a\x0c\x69\ \x5f\xd7\x25\x2f\xac\xdf\xca\x7d\xff\x78\x91\xe6\x16\x75\x41\x8d\ \x11\xc6\x8e\x92\x44\xb8\x60\x67\x95\xc0\xf7\x89\xdc\x2a\x21\xd1\ \x98\xd2\xdb\x81\xa4\xb5\xc5\x1e\xdf\x2d\x42\x20\x9d\xc0\x39\x6e\ \x73\xb6\xc6\xe8\xcc\x64\x95\x00\x00\x2d\xad\xed\x6c\x7a\xe7\x3d\ \x2e\x9b\x3b\x75\x50\xef\xd3\x81\xf6\x8f\x2e\x58\x6a\xeb\xe9\x2b\ \xd7\xd9\xd0\xd1\xf7\xe7\x81\xf0\xe9\xe0\x93\x7d\x53\x7a\xa7\x75\ \xf4\xc2\xf6\x0a\x8d\xa6\xce\x0b\xbf\xdf\xeb\x83\x0f\x4e\xf4\x25\ \x0a\xd3\xd2\xf5\x80\x99\x0d\x28\xaf\xae\xe3\xee\x07\xd7\xb0\x7d\ \xcf\x41\xab\x43\x09\x4a\x66\x54\x6a\x3c\x70\xa4\x9c\xbb\xee\x7f\ \x86\xbd\x1f\x1e\x37\xa4\x7d\xe5\x9f\x52\x3c\x92\xd9\x2e\xc9\xd6\ \x72\x8d\xee\x33\x1e\x38\x12\x92\x53\xad\x0b\xca\x26\x3a\xda\xed\ \x51\x22\x5c\xd3\xd0\x9d\xc0\x00\xbe\xd6\xcd\x33\x3a\x33\x85\x4d\ \x5b\xdf\xb3\x3a\x0c\x5b\x58\xbb\x6e\xcb\x80\x12\x80\x86\x0e\xa8\ \x6c\x16\xd4\x77\x08\xda\xba\xfa\x92\x80\xa1\xea\xf6\xc2\xd6\x32\ \x41\x7a\x34\x38\xb4\xbe\xb6\x8f\x37\x0a\xf4\x41\x1e\x08\xa8\x6f\ \x87\x37\x8e\x69\xcc\xce\xd2\x09\xb7\xf1\x26\xc1\xf6\x8e\x4e\x1e\ \x78\xfc\x15\x9e\x7e\x65\x33\xbe\x4f\x3e\xae\x28\xa6\x28\x29\xcc\ \xe3\xce\x9b\x57\x92\x99\x66\xcc\x9e\x97\x86\xc6\x16\x7e\xff\xb7\ \xe7\x79\x55\x1d\xdd\x34\x55\x6c\x18\xcc\xcd\xd1\xd9\x52\xa6\xd1\ \xfe\xd1\x03\x48\x68\x58\x38\x91\x9e\x68\xda\x5a\x82\x77\xb3\x65\ \x47\x9b\x3d\x12\x00\x64\x5f\x02\x60\xab\x12\x66\x63\x32\x55\x86\ \x78\xda\x7b\x07\x8e\xb2\x7b\xef\x61\x8a\x0a\xfa\x3f\x3e\xd3\xda\ \x0d\x7b\x6a\x34\x4e\xf9\x79\x26\xf3\x64\xbb\xe0\xa4\x1f\xda\xec\ \xe8\x81\xb7\x8e\x6b\xcc\xca\xd6\x89\xb4\x59\x12\x20\xa5\xe4\xd5\ \x37\x77\xf1\xfb\x47\x9e\xa3\xa1\xc9\x26\xbf\x90\x41\x26\x21\x2e\ \x9a\x5b\xaf\x5f\xc6\xe2\x79\x25\x86\xb4\x7f\xfa\xe8\xe6\x03\x8f\ \xbf\x42\x7b\x87\xad\x9e\x73\x82\x46\x84\xbb\x2f\x09\x78\xa7\xfc\ \x9f\x33\x88\x09\x49\x69\x41\x9d\x00\x74\xb6\xb7\x59\x1d\x02\x00\ \x42\xe0\x73\x82\xec\x18\x68\x3d\x67\x33\x8c\xce\x4a\xb1\x3a\x04\ \x5b\xb9\xff\xb1\x57\xf8\xdd\x4f\x6e\xfb\xd4\xcf\x4f\xb5\xc3\x3b\ \x15\xda\xa7\x8e\xef\xd9\x4d\x67\x2f\xbc\x71\x44\x63\x6a\xba\x24\ \x39\xca\x98\xa7\xaf\x2e\x6f\xdf\xfe\x86\x96\xee\xbe\xe5\x0f\xaf\ \x14\x68\x02\x22\x5d\x92\x84\x48\x48\x8d\x96\x67\x2d\x69\xec\x3f\ \x5c\xce\x5d\x0f\xac\x61\xdf\x87\x65\x86\xc4\xa3\x9c\x9f\xdb\xe9\ \xe4\xf3\x57\x5d\xc2\x17\x56\x5c\x4a\x58\xa8\x31\x99\xe1\x8e\x77\ \x0f\x71\xf7\x43\x6b\x28\xab\x54\x37\xd0\x59\x2d\xc4\x09\xb3\xb3\ \x75\xb6\x57\x08\xea\xda\x04\x71\xf1\x89\x1c\xfb\x30\x78\x8b\x2c\ \x75\xd8\x24\x01\x70\x38\xe8\x75\x82\xd6\xd9\xb7\x85\xcb\x1e\xd2\ \x92\xe2\x09\x0b\x75\xd3\xd9\x35\xc0\x45\xeb\x11\xee\xdd\xfd\x47\ \xd8\xf5\xfe\x87\x4c\x9d\x94\xfb\xf1\xcf\xba\xbd\xb0\xbd\xd2\xfe\ \x83\xff\x69\xbd\x3a\x6c\x2d\x17\xc4\x86\x09\xe2\xc3\x25\x0e\xad\ \xef\x67\x1d\x3d\xd0\xe9\x15\x74\xf6\xf6\xa5\xa0\x2e\x07\xc4\x84\ \x49\x32\xa2\x21\x31\x72\x60\x9f\xc9\x63\x8d\x82\xbd\x27\x04\xde\ \x7e\x66\xef\x1b\x10\x94\x37\xc3\xfe\x3a\x41\x51\x9a\xc4\xe5\x6b\ \xe5\xbe\xbf\xbf\xc8\x8b\x1b\xb6\xa1\x0f\x76\x4d\x43\xf1\x8b\x8b\ \x8a\x27\xf0\xed\x1b\x57\x18\x56\xf4\xab\xa6\xae\x81\x7b\xff\xfa\ \x2c\x6f\xa8\x65\x44\x5b\x71\x6a\x70\x51\xa6\x64\x77\x35\x78\xa2\ \x83\xfb\xe6\xd7\x8e\xf6\x56\x5b\x54\x03\xd4\xa0\xc7\x76\x4b\x00\ \x9a\x26\xc8\xcb\xc9\x50\xe7\x72\xcf\x70\xdf\xa3\x2f\xf2\xc7\x9f\ \x7e\x1b\x4d\xeb\xfb\xc0\x1c\x38\x29\xce\xda\x58\x13\x28\x1a\x3b\ \xa1\xb1\xf3\xdc\x1f\xfa\x2e\x2f\xb4\x76\x0b\x2a\x9a\x20\x21\x02\ \x0a\x53\x24\x51\x21\xfd\xbf\xd6\x27\xe1\xbd\x1a\x41\xd9\x79\xce\ \x1c\x9f\xd6\xd6\xad\x73\xef\xe3\x9b\xd9\xba\xe9\x65\xda\x3b\x6c\ \xf5\x71\x0f\x1a\xa9\x49\xa3\xf8\xe6\x97\x97\x33\x67\xda\x44\x43\ \xda\xef\xee\xe9\xe5\xd1\xb5\xeb\x79\x74\xed\x06\xba\x7b\xd4\xd1\ \x4d\x3b\xd2\x04\x14\xa7\x49\x0e\x66\xc6\xb2\xd1\xea\x60\x2c\xe4\ \xf3\xf9\xe8\xee\xee\x22\x24\xd4\xda\xc2\x29\x4e\x21\xbb\x9d\x20\ \xed\x31\x1f\x71\x86\x09\x63\x55\x02\x70\xa6\x7d\x87\xcb\x78\xf6\ \xb5\x2d\xac\xb8\x7c\x16\x3d\x3e\x28\x1f\x40\xdd\xed\x40\x77\xb2\ \x5d\xb0\xf1\xb0\x20\x67\x54\x5f\x91\x21\xf7\x19\x77\x56\x36\x75\ \xc2\xae\x2a\x8d\xd6\x01\x54\xe4\xad\x38\x7e\x98\xf5\x2f\x3d\x4d\ \x7d\xad\xba\xb4\xc7\x0a\x6e\x97\x8b\x1b\x56\x2c\xe0\xfa\xe5\x0b\ \x09\x71\x1b\x73\x31\xd3\x9b\xdb\xde\xe7\xb7\x7f\x59\x4b\x4d\x5d\ \x70\x5f\x24\x16\x08\x04\x50\x32\x36\xb8\x67\x00\x00\x3a\x3b\xda\ \x2d\x4f\x00\x1c\x1a\x9d\x4e\x04\x4d\x36\x5a\x01\x00\x20\x3f\xd7\ \x98\x5a\xdf\x81\xec\x0f\x8f\xbe\xc0\x9c\xe9\x13\x69\xf0\xc5\x7c\ \xea\x6c\xad\xbf\x64\xc4\x48\x0a\x92\x24\x5e\x1d\xb6\x95\x0f\x6c\ \x80\x35\x92\x0e\x1c\x39\x25\x28\x6b\x12\x24\x46\x48\x5c\x0e\x68\ \xed\x12\x34\x0e\x60\xd1\xaa\xad\xb5\x99\x37\x5f\x7b\x9e\x7d\xef\ \xed\xa4\xdf\x8a\x24\x8a\xe1\x66\x4e\xcd\xe7\xf6\x9b\x56\x92\x9a\ \x64\x4c\x85\xcf\xf2\xea\x3a\xee\x79\x78\x2d\x5b\x4b\xf7\x1b\xd2\ \xbe\x62\x8c\xe4\x04\x95\x00\x74\x77\x5b\x7f\xfa\x5e\x13\xa2\xc5\ \x89\x4e\x93\x8d\xf6\x00\x02\x7d\x97\x7e\x28\x67\xeb\xe8\xe8\xe2\ \x37\x0f\xad\x65\xe6\x15\x37\x1a\xd2\x7e\xa8\x13\x8a\x52\x25\x1f\ \xad\x32\x50\x90\x24\xd9\x5a\x6e\x8f\x0f\x86\xd7\x07\xd5\x2d\x03\ \x8b\xc5\xe7\xf3\xb2\x73\xcb\x46\xb6\xbe\xb9\x8e\xde\x1e\xb5\x8f\ \xc4\x0a\xe9\xc9\xf1\x7c\xeb\xa6\x15\xcc\x2c\xce\x37\xa4\xfd\x8e\ \x8e\x2e\x1e\x7c\xf2\x55\x9e\x7a\xe9\x2d\xbc\xbe\x00\xd9\x08\xa3\ \x7c\xcc\x13\x15\x41\x78\x68\x08\x1d\x5d\xc1\x7b\xa9\x56\x4f\xb7\ \xf5\xa7\x52\x84\x90\x2d\x4e\x29\xb0\xdd\x84\x72\x4a\x62\x1c\x71\ \x31\x51\xea\x78\xd6\x27\x6c\xdc\xb2\x07\x57\xfc\x3b\x14\x4e\xbd\ \xd8\xef\x6d\x87\xb9\xf8\x78\xf0\x07\x88\x0e\x95\xd8\xe9\x74\xc8\ \x40\x94\x1d\x39\xc8\x86\x57\xd6\x70\xaa\xee\x84\xd5\xa1\x04\xa5\ \x10\xb7\x8b\xeb\x97\x2f\xe0\x86\x15\x0b\xfd\x56\xc1\xf2\x4c\x52\ \x4a\x36\x6e\x7d\x4f\x55\x6a\x1c\x01\x62\xa2\x23\x83\x3a\x01\xe8\ \xea\xb2\x7e\x06\xc0\x21\x44\x93\x2d\x97\x00\x00\x26\xe6\x65\xf1\ \xe6\xf6\x0f\xac\x0e\xc3\x76\x36\xbc\xf4\x34\xc9\x69\x99\x24\x26\ \xfb\xb7\xa4\x66\x6b\x0f\x78\xf5\xbe\xdd\xba\xc0\x39\xef\x09\xb0\ \xa3\xa6\x86\x7a\x36\xbe\xbc\x86\x23\x87\xd4\xa5\x3d\x56\x99\x77\ \x51\x21\xdf\xfc\xd2\x55\x24\x27\xc4\x19\xd2\xfe\xa1\xa3\x95\xdc\ \xf5\xc0\x1a\xde\x3f\x78\xcc\x90\xf6\x15\x73\x45\x45\x86\x43\x6d\ \xf0\x5e\x0a\xd4\xd3\x65\xfd\x0c\x00\x9a\xde\xe0\x44\x97\x4d\x56\ \x1f\x47\xe8\xcf\x94\xfc\xb1\x2a\x01\xe8\x87\xd7\xdb\xcb\xf3\x8f\ \x3f\xc8\x75\xab\xef\x24\x2c\x22\xc2\x7f\xed\x7e\x54\xc2\xb7\x30\ \x45\x22\x80\xc3\xa7\xec\xf7\x99\xf8\xa4\xde\x9e\x1e\xb6\x6d\x7e\ \x8d\x1d\x9b\x37\xe0\xf3\x05\xe0\xb1\x88\x11\x20\x33\x35\x91\xdb\ \x6f\x5e\xc9\xf4\xc9\xe3\x0c\x69\xbf\xb9\xa5\x9d\xfb\xfe\xf1\x22\ \x2f\xac\xdf\xaa\x8e\x6e\x8e\x20\x9e\xc8\x11\x76\x75\xe8\x20\x75\ \xdb\x60\x09\x40\x83\x7a\xa7\x26\x64\xa3\xb4\xe1\x54\xef\x94\x82\ \x31\x56\x87\x60\x5b\x8d\x0d\xf5\x3c\xfd\xe8\x1f\xf9\xdc\x97\x6f\ \xc3\xe5\x3e\xc7\x39\xb9\x21\x38\xde\x28\xa8\x6e\xe9\x2b\xa2\xd3\ \x65\xe3\xf1\x54\x4a\xc9\x81\xf7\x77\xf1\xe6\xeb\xcf\xd3\xda\xac\ \xa6\x82\xad\x10\x16\xea\xe6\xcb\x57\x5f\xc6\x35\xcb\x2e\xc1\xe5\ \x74\x5c\xf8\x0d\x83\xa4\xeb\x3a\x6b\x5f\x7b\x87\xfb\xff\xf1\x12\ \x2d\x6d\xea\xe8\xe6\x48\xe3\x89\x0c\xb7\x3a\x04\x4b\x75\xdb\x62\ \x09\x40\xab\x75\x22\x9d\x27\x11\xf6\xab\x81\x3e\x36\x2b\x95\xc8\ \xf0\x30\xda\x54\x09\xcf\x7e\x9d\xa8\x2a\x67\xcd\xa3\x7f\x66\xd5\ \x17\x6e\xc5\xe9\xf4\xdf\x7a\xab\xdd\xa7\xfe\xcb\x8e\x1c\xe4\x8d\ \xd7\x9e\xa3\xae\xa6\xd2\xea\x50\x82\xd6\xcc\xa9\xf9\xdc\xb9\x7a\ \x95\x61\xd3\xfd\xfb\x0e\x97\x71\xf7\xfd\xcf\xb0\xef\xb0\xaa\xd4\ \x38\x52\x45\x45\x04\x77\x02\xd0\xdb\x6b\xfd\x06\x65\xcd\xc1\x87\ \x4e\x9f\x97\x5a\xcd\x98\xe3\xb9\xc3\xa2\x69\x1a\x85\x13\x72\xd8\ \xb2\x2b\x78\x4b\x46\x5e\x48\xc5\xf1\x0f\x59\xf3\xb7\x3f\xb1\xfc\ \xf3\x37\xe1\xb6\xf8\x4c\xa9\xd1\x4e\x54\x95\xf3\xe6\x6b\xcf\x53\ \x7e\xec\x90\xd5\xa1\x04\xad\xcc\xd4\x44\x6e\xbf\x69\x25\xd3\xa7\ \x18\x33\xdd\x7f\xaa\xa9\x85\xdf\x3f\xf2\x3c\xeb\xd4\xa5\x3d\x23\ \x9e\x27\x2a\xb8\x13\x00\x9f\xd7\xfa\x29\x56\xb7\x8b\x03\xce\xd6\ \x2c\x4e\x46\x57\xa3\x83\xfd\xae\x6f\x2f\x9e\x98\xab\x12\x80\x0b\ \x28\x3f\x76\x88\x7f\x3c\xf8\x5b\x56\xdd\x70\x2b\x91\x9e\x68\xab\ \xc3\xf1\xbb\xea\xca\xe3\xec\xd8\xbc\x9e\x0f\x0f\xbc\xaf\xce\xf3\ \x5b\x24\x2c\x2c\x84\x9b\xaf\x59\xcc\xd5\x4b\xe7\xe0\x74\xf8\x7f\ \xba\xbf\xd7\xeb\xe3\x89\x17\x36\xf1\x97\xa7\x5e\x0b\xea\x9d\xe1\ \xc1\x24\x2c\xc4\x7f\x4b\x97\x81\xc8\xdb\x6b\x6d\xb5\x4a\x87\x00\ \xf2\x39\xe6\x9c\x2f\x84\xb7\xb4\xca\xd7\x00\xc4\x5b\x1a\x51\x3f\ \x2e\x9a\x32\x9e\x7b\xff\xf2\xac\xd5\x61\xd8\xde\xc9\xda\x2a\x1e\ \xbd\xff\xd7\x5c\xb1\xea\x4b\xa4\x67\x8d\xb6\x3a\x9c\x61\x93\x52\ \x72\xe4\xe0\x5e\x76\x6e\x59\x4f\x65\xd9\x51\xab\xc3\x09\x5a\x42\ \x08\x2e\x9d\x53\xcc\xd7\xbf\xf0\x19\xe2\x63\x8d\x49\x2e\xb7\x96\ \xee\xe7\x9e\x87\xd7\x52\x5e\x5d\x67\x48\xfb\x8a\x3d\xb9\x5c\x4e\ \xab\x43\xb0\x94\xd7\x6b\x6d\x02\x10\xe2\xc4\x77\x8d\x10\xbe\xd3\ \xff\x0a\x75\xd8\x30\x01\xc8\xce\x48\x26\x39\x21\x8e\x13\x27\x55\ \x89\xcf\x0b\x69\x6d\x6e\xe2\xf1\x87\x7e\xcb\x45\xf3\x2e\xe3\xe2\ \x79\x97\xa3\x69\xb6\x9b\xd0\xb9\x20\xaf\xb7\x97\xfd\xef\xee\x64\ \xc7\x96\x8d\x34\xd4\xab\x5b\xdc\xac\x34\x3a\x33\x95\x3b\x57\xaf\ \x64\x4a\xbe\x31\x9b\x71\xab\x6a\xeb\xb9\xe7\xa1\xb5\xbc\xbd\x53\ \x1d\xdd\x0c\x46\x0e\x47\xe0\x7d\x3f\xf9\x93\xd5\x09\x80\xdb\x21\ \xbb\x01\x9c\x00\x52\x88\x5a\x21\xa5\x31\x65\xbb\x86\xe9\xa2\xa2\ \xf1\xac\x5d\xb7\xc5\xea\x30\x02\x82\x94\x3a\xef\x6c\x7a\x85\x63\ \x87\xf6\xb1\x60\xe9\x2a\x52\x33\xb2\xad\x0e\x69\x40\xaa\x2a\x8e\ \xb1\x6f\xcf\x0e\x0e\x7c\x50\x4a\xb7\x1d\xce\xc7\x06\xb1\x88\xf0\ \x30\x6e\xfe\xdc\x62\x56\x2d\x9e\x6d\xc8\x97\x74\x57\x77\x0f\x7f\ \x5d\xf3\x3a\x8f\x3d\xb7\x89\x1e\x8b\xa7\x41\x15\xeb\xb8\x9c\xc1\ \x3d\x03\xe0\xb3\x38\x01\x70\x39\x44\x1b\x7c\x94\x00\x08\x29\x6b\ \x2c\x8d\xe6\x3c\x2e\x2a\xca\x57\x09\xc0\x20\x9d\xa8\x2e\xe7\xef\ \x0f\xdc\xcd\xf8\x49\xc5\xcc\x59\xb8\x8c\xe8\x18\x63\x76\x6b\x0f\ \x47\x4b\x73\x23\xfb\xf6\xec\x60\xdf\x7b\x3b\x68\xa8\x57\xd3\xbf\ \x56\x13\x42\xb0\x78\x5e\x09\x5f\xbb\xe1\x33\xc4\xc5\x44\x19\xd2\ \xc7\x86\x2d\x7b\xb8\xf7\xaf\xcf\xaa\x2a\x7e\x8a\x21\x7b\x49\x02\ \x89\xb7\xd7\xda\x4d\x80\x0e\x4d\x36\xc1\x47\x09\x00\x50\x61\x61\ \x2c\xe7\x55\x3c\x69\x2c\x6e\x97\x4b\x3d\x2d\x0c\x96\x94\x1c\x78\ \x6f\x17\x87\x3e\xd8\x4d\x5e\xfe\x14\x4a\x66\xcd\x27\x39\xd5\xc2\ \x3b\x16\xa4\xa4\xee\x44\x15\xc7\x0e\xef\xe7\xd8\x87\xfb\xa8\x2a\ \x3f\xa6\x76\x7a\xdb\x44\x5e\x4e\x3a\x77\xac\x5e\xc9\xa4\x71\x39\ \x86\xb4\x7f\xb4\xbc\x86\xbb\x1f\x58\x43\xe9\xde\xc3\x86\xb4\xaf\ \x04\x1e\x23\x6a\x47\x04\x12\x9f\xc5\x77\x58\x38\x34\x4e\xc0\xe9\ \x25\x00\xa8\xb0\x5f\x29\xa0\x3e\xe1\xa1\x21\xcc\x98\x32\x8e\xb7\ \x76\xa8\xaa\x80\x43\xa1\xeb\x3a\x07\x3e\x28\xe5\xc0\x07\xa5\x24\ \xa7\x66\x32\x7e\x52\x31\xe3\x26\x16\x11\xe5\x89\x31\xbc\xef\xee\ \xae\x0e\x8e\x1f\x3e\xd8\x37\xe8\x1f\xde\x4f\x7b\x6b\x8b\xe1\x7d\ \x2a\x03\x17\x15\x11\xce\x2d\xd7\x2e\x61\xf9\x65\x33\x0d\xd9\x33\ \xd2\xd6\xd1\xc9\x83\x8f\xbf\xc2\xd3\xaf\x6c\xc6\x67\xd4\x15\x96\ \x4a\x40\x72\x06\xf9\x12\x00\xd2\xda\xdf\x07\x0d\x59\x01\xa7\x97\ \x00\x90\x15\x76\xbe\xf8\x65\xfe\xc5\x53\x54\x02\xe0\x07\x27\xaa\ \xcb\x39\x51\x5d\xce\x1b\xeb\x9e\x25\x21\x39\x8d\x8c\xec\xb1\x64\ \x64\x8f\x25\x25\x3d\x8b\x88\x48\xcf\xb0\xda\xee\x6c\x6f\xa7\xbe\ \xfe\x04\xf5\xb5\xd5\x9c\xa8\x2a\xa7\xa6\xaa\x8c\xc6\xfa\x3a\xf5\ \x94\x6f\x43\x9a\x26\x58\x3a\x7f\x3a\x5f\xbd\x7e\x19\x31\x9e\x48\ \xbf\xb7\xaf\xeb\x92\x97\x37\x6d\xe7\x8f\x8f\xbe\x48\x63\xb3\xba\ \xd0\x4b\xf9\x34\x5d\x0f\xee\x84\x50\x5a\x7c\x01\x8f\xdb\xc1\x41\ \xf8\x28\x01\xd0\x70\x54\xe8\xd8\xf7\x1f\x64\x56\x49\x3e\x6e\xa7\ \x93\x1e\x1b\x14\x4f\x18\x09\xa4\x94\xd4\xd5\x54\x52\x57\x53\xc9\ \xae\x77\x36\x01\x10\x12\x1a\xc6\xa8\x84\x24\x62\xe2\x12\x08\x8f\ \x88\x24\x22\x32\x8a\x90\xd0\x30\x34\xcd\xf1\x71\xb9\xe1\x9e\xee\ \x2e\x7a\x7b\xbb\xe9\xed\xee\xa1\xbd\xad\x95\xd6\x96\x46\xda\x5b\ \x5b\x68\x6c\x3c\x49\x67\x7b\xbb\x85\xff\x8f\x94\x81\x1a\x37\x26\ \x83\xef\xac\x5e\x45\x7e\x6e\x96\x21\xed\xab\x2a\x7e\xca\x40\xf4\ \x06\xf9\x77\xb9\xb4\xf8\x5e\x0b\x87\x53\xdb\x0d\x1f\x25\x00\xdd\ \x92\x72\x97\x7d\x27\x00\x88\x08\x0f\xa3\xa4\x30\x8f\x2d\xa5\xaa\ \x28\x90\x51\xba\xbb\x3a\xa9\xae\x38\x4e\x75\xc5\x71\xab\x43\x51\ \x0c\x10\x15\x11\xce\x8d\xd7\x5c\xce\xd5\x4b\x66\x1b\x32\xdd\xdf\ \xd2\xd6\xc1\x43\x4f\xbc\xca\xd3\xaf\x6c\x0e\xfa\xa7\x3b\xe5\xc2\ \x7a\x2d\xde\x04\x67\x35\x2b\x67\x00\x84\x80\x70\xc1\x76\xf8\x28\ \x01\x98\x91\x2e\x4e\x95\x56\xf9\x3a\x00\xdb\xd6\x67\x5c\x38\xab\ \x48\x25\x00\x8a\x32\x48\x9a\x26\x58\xb6\xf0\x22\xbe\x7a\xdd\x15\ \x78\xa2\xfc\x77\x7b\xe4\x69\x3e\x9f\xce\xd3\xaf\x6c\xe6\xc1\xc7\ \x5f\x51\xf7\x76\x28\x03\xe6\x0d\xf2\x3d\x21\x56\xce\x00\x84\xb8\ \xd0\x57\x4e\x16\x75\xf0\xcf\x53\x00\x48\xc9\x51\x21\x98\x68\x59\ \x54\x17\x30\x77\x46\x21\x61\xf7\x3f\x45\x67\xa7\x2a\x15\xaa\x28\ \x03\x91\x37\x3a\x9d\xef\xdc\xb2\x8a\x82\xdc\x6c\x43\xda\xdf\xbd\ \xf7\x30\x77\x3d\xf0\x0c\x47\xcb\xab\x0d\x69\x5f\x19\xb9\x82\x7e\ \x09\xc0\xc2\xbd\x51\x61\x0e\x3e\xbe\x5e\xf3\x9f\x5b\x31\x85\x38\ \x0c\xd2\xb6\x09\x40\x58\xa8\x9b\xf9\x17\x4d\xe6\xa5\x8d\xdb\xad\ \x0e\x45\x51\x6c\x2d\xc6\x13\xc9\xad\xd7\x5d\xc1\x15\x0b\x66\xa0\ \x69\xfe\x5f\xdb\xab\xab\x6f\xe2\xde\xbf\x3e\xcb\x86\x2d\x7b\xfc\ \xde\xb6\x12\x1c\xbc\x5e\x9b\x5f\x3b\x6a\x30\x2b\x97\x00\x5c\x9a\ \x3c\x75\xfa\xcf\x67\x9c\xc5\x90\xb6\x3f\xa4\xbb\xe4\x92\x69\x2a\ \x01\x50\x94\x73\xd0\x34\x8d\xe5\x97\x5e\xcc\x2d\xd7\x2d\x35\xe4\ \xba\xd5\x9e\xde\x5e\x1e\x7b\x6e\x13\x8f\x3c\xf3\x3a\x9d\x5d\xd6\ \x5f\x67\xaa\x04\xae\x9e\x20\xdf\x03\x60\x65\xa9\x76\x97\xe3\x9f\ \x75\x7f\x3e\x4e\x00\x84\xe4\x88\x8d\x4f\x02\x02\x30\x25\x7f\x0c\ \xa9\x49\xa3\xa8\xae\x3d\x75\xe1\x17\x2b\x4a\x10\x99\x34\x2e\x87\ \x3b\x56\xaf\x24\x2f\x27\xdd\x90\xf6\xdf\xde\xb9\x97\x7b\x1e\x5a\ \x4b\x55\x6d\xbd\x21\xed\x2b\xc1\x25\xd8\xf7\x8b\x38\x2c\xac\x84\ \xe8\x72\xc8\x8f\xef\x54\xff\x67\x02\x20\xf4\x23\xd2\x7e\x37\x02\ \x9f\x45\x08\xc1\x95\x8b\x2e\xe6\x8f\x8f\xbe\x60\x75\x28\x8a\x62\ \x0b\x9e\xa8\x08\xbe\x7c\xf5\x65\x5c\xbd\x64\x8e\x21\xd3\xfd\x95\ \x27\xea\xb9\xe7\xc1\x67\xd4\x06\x5c\xc5\xaf\x9a\x5b\x83\xfb\xd8\ \xb0\x10\xd6\x25\x00\x4e\xc9\xbb\x1f\xff\xf9\xf4\x1f\x34\x87\xf3\ \x70\x20\x54\xeb\x5a\xb6\x70\x06\x0f\x3e\xf1\xaa\x2a\x0d\xac\x04\ \x35\x87\x43\x63\xe5\xe5\xb3\xb9\xf9\xf3\x8b\x89\x0c\x0f\xf3\x7b\ \xfb\x5d\xdd\x3d\x3c\xf4\xe4\x3a\x9e\x78\x61\x13\xbd\x41\xbe\x5e\ \xab\xf8\x5f\x6b\x5b\xc7\x85\x5f\x34\x82\x99\x3d\x03\x20\x04\xc4\ \x86\x41\x72\x94\x24\x42\x68\xeb\x4e\xff\xfc\xe3\x04\xa0\x30\x89\ \xb2\xdd\xd5\x74\x02\xfe\xff\x36\xf1\xa3\x18\x4f\x24\x0b\x66\x4e\ \xe6\x95\x37\x76\x5a\x1d\x8a\xa2\x58\x62\xf2\x84\x31\xdc\xb9\x7a\ \x25\x63\xb2\x52\x0d\xeb\x63\xff\xe1\x72\x1e\x5d\xbb\xde\xb0\xf6\ \x95\xe0\xd6\xdc\x12\xdc\x09\x80\x66\x42\x02\x20\x04\x24\x44\x48\ \xd2\x3c\x90\xe2\x91\xb8\xfb\xba\x6c\x2b\x4a\x15\x07\x4f\xbf\xe6\ \x8c\x25\x00\xa1\xef\xaa\xf2\x1d\x10\x50\x89\xce\xf1\xc3\x00\x00\ \x20\x00\x49\x44\x41\x54\x64\x78\x64\xc3\xb4\x72\xf1\x6c\x95\x00\ \x28\x41\x27\x32\xd2\xc3\x67\xae\xba\x92\x2b\x2e\x99\x4a\x46\xb4\ \xd5\xd1\x28\xca\xd0\xb5\xb6\x07\x77\x02\xe0\x30\x70\x13\x60\x84\ \x0b\xb2\xe3\x24\x19\x31\x92\xd0\x4f\x5e\xb9\x20\xe4\x3e\x21\xc4\ \xc7\x47\x10\x3e\xf9\xd7\xfb\x08\x80\x04\x20\x3f\x37\x8b\x49\xe3\ \x72\x78\xff\xe0\x31\xab\x43\x51\x14\xc3\x69\x9a\x46\xf1\x8c\xb9\ \xcc\x9c\xbf\x04\x77\x48\x28\x7b\xaa\x61\xef\x09\x18\x3d\x4a\x32\ \x36\x5e\xe2\xb2\xf7\xd6\x1d\x45\xf9\x94\x96\x20\x5f\x02\x70\x7e\ \x54\x5e\xdd\x5f\x34\x20\xd9\x23\xc9\x8e\xed\x7b\xea\x17\xe7\xd8\ \x0e\x24\xa5\x38\x6b\x33\xcf\x59\x09\x80\x26\xd8\x17\x28\x77\xb7\ \x5c\xbf\x7c\x21\xdf\xfb\xc5\xfd\x56\x87\xa1\x28\x86\xca\xc8\x19\ \xcb\xc2\xa5\x57\x13\x9f\x98\x72\xd6\xcf\x7b\x75\x38\x78\x52\x70\ \xac\x41\x30\x21\x49\x92\x1d\x2b\xed\x7e\x88\x47\x51\x00\xf0\xfa\ \x7c\x41\x9f\x00\xb8\x5c\x6e\xbf\xb4\xe3\x74\x40\x4e\x8c\x64\x4c\ \x7c\x3f\x4f\xfb\xfd\xd0\xfa\x1e\xf2\xff\xf9\xfe\xb3\xff\x5a\xee\ \xb3\xf3\xad\x80\x67\x9a\x55\x92\xcf\xe8\xcc\x14\x8e\x96\xd7\x58\ \x1d\x8a\xa2\xf8\x5d\x44\xa4\x87\x79\x97\x5e\x49\xfe\xe4\x12\xce\ \x99\xce\x03\x3d\x3e\x78\xb7\x5a\x50\xd1\x24\x28\x4a\xd5\x89\xf2\ \xef\x83\x85\xa2\xf8\x5d\x5d\x7d\x53\xd0\xdf\x17\xe1\x0e\x19\xde\ \x2f\xaa\xd3\x01\x39\xb1\x92\xdc\xf8\x8f\xd7\xf6\x07\x46\xca\xb3\ \x12\x80\xb3\x26\x0f\x85\xee\x08\x98\xb3\x3e\x42\x08\xae\xbb\x6a\ \x81\xd5\x61\x28\x8a\x5f\x69\x0e\x07\xc5\x33\xe6\x71\xf3\xb7\x7e\ \x40\xfe\x94\x69\xe7\x1d\xfc\xcf\xd4\xd0\x01\x9b\x8e\x6a\x1c\x39\ \x25\x2c\xbe\x68\x54\x51\xce\xef\x44\x7d\xa3\xd5\x21\x58\x2e\x35\ \xd6\x4d\x8a\x47\x12\xe6\x1a\xdc\xfb\xc2\x9c\x50\x90\x2c\x59\x9c\ \xa7\x53\x90\x34\xc8\xc1\x1f\xd0\x9c\x8e\x73\xcf\x00\x1c\x4a\xe3\ \xc8\xd8\x6a\x6c\x7d\x29\xd0\x99\x16\xcd\x2e\xe2\xe1\x27\x5f\xa5\ \xf2\x84\x2a\x4e\xa2\x04\xbe\xcc\xd1\x79\x2c\x5a\x7a\x35\x71\x09\ \x49\x43\x7a\xbf\x4f\x87\xf7\x4f\x08\xea\xdb\xa1\x28\x6d\xf0\x5f\ \x0e\x8a\x62\x86\x13\x75\x0d\x56\x87\x60\xb9\xf4\x38\x37\x33\x32\ \x24\x20\x69\xe9\x82\x13\x6d\x82\xba\x56\x68\xec\x14\xf8\x3e\x91\ \xc1\x6b\x40\x42\xa4\x24\x3b\x0e\x92\x23\xcf\xbd\xbe\x3f\x00\x6d\ \x85\x49\x9c\x75\x4f\xf7\x59\x09\xc0\x35\x42\xf8\x76\x57\x7b\xdf\ \x97\x52\xcc\x18\x72\x17\x26\x72\x3a\x1c\xdc\xfc\xb9\x25\xfc\xe7\ \x6f\x1e\xb1\x3a\x14\x45\x19\xb2\xa8\xe8\x18\x2e\xb9\x7c\x05\xe3\ \x0a\xa6\xf8\xa5\xbd\x9a\x56\x41\xcb\x51\xc1\xb4\x74\x9d\x18\x5b\ \x1f\xea\x55\x82\xd1\x89\x93\x6a\x06\x20\x2c\xf4\x9f\x4b\x00\x9e\ \x50\xf0\x84\x4a\xf2\xe2\xc1\xa7\x4b\x9a\xbb\xa0\xb5\x47\x80\x84\ \x70\x37\xc4\x86\x49\x9c\x7e\xd8\xe8\x2b\x90\x7b\x84\x10\x67\xad\ \xbd\x7c\xaa\x59\x89\xd8\x3d\xfc\xae\xcc\xb3\x70\x56\x11\x63\xb3\ \xd2\xac\x0e\x43\x51\x06\xed\xf4\x74\xff\x4d\xb7\x7d\xdf\x6f\x83\ \xff\x69\xed\x3d\xf0\xd6\x31\x8d\xa3\x0d\x81\xb1\xa7\x47\x09\x1e\ \x27\x4e\xaa\x19\x80\xe8\x73\x5c\xcd\xed\xd0\x20\x2e\x1c\xb2\x62\ \x24\x59\xb1\x92\x84\x08\xff\x0c\xfe\xd0\xff\xd8\xde\x4f\x02\x40\ \x40\x25\x00\x9a\x26\x58\xfd\xf9\x25\x56\x87\xa1\x28\x83\x92\x3d\ \x76\x3c\x5f\xfe\xfa\xf7\x58\xb0\x74\x25\x2e\x3f\x1f\x09\x3a\xcd\ \x27\xe1\xbd\x1a\xc1\xae\x2a\x81\x37\xb8\xf7\x5c\x29\x36\x52\xa3\ \x96\x00\xce\x99\x00\x18\x4b\x7e\xea\xfa\xce\x4f\x1d\x1c\x10\x68\ \xa5\x10\x58\xdf\x16\xb3\xa7\x15\x50\x54\x30\x86\xdd\x7b\x8f\x58\ \x1d\x8a\xa2\x9c\x57\xa4\x27\x9a\xb9\x0b\x3f\xd3\xb7\xc1\xcf\x24\ \x15\x4d\x82\xa6\xce\xbe\x25\x01\x4f\xa8\x69\xdd\x2a\x4a\xbf\x8e\ \xa8\x93\x5b\x44\x7b\xcc\xdf\x66\xa7\xe1\xb8\xf0\x0c\x40\x54\x3b\ \xef\x23\x08\xb8\x42\xfb\xb7\xdf\xb4\xd2\xd2\x2b\x16\x15\xe5\x7c\ \x9c\x4e\x17\x17\xcd\xbd\x9c\x9b\xbf\xf5\x43\x53\x07\xff\xa6\xc6\ \x53\xb4\x34\x37\xd2\xda\xdd\xb7\x24\xd0\x14\xdc\x97\xb0\x29\x16\ \xab\x6f\x6c\xa6\x25\xc8\x2f\x02\x02\xf0\x44\x9a\x3e\x03\xd0\x13\ \xd1\xc1\xa7\x4e\xf9\x7d\x6a\xc4\xcc\xcd\x15\xdd\x48\xf6\x9b\x13\ \x93\xff\x8c\xc9\x4a\x65\xf9\x65\x33\xad\x0e\x43\x51\x3e\x25\x6b\ \xcc\x38\xbe\xf8\xb5\xef\x32\x7b\xe1\x52\xbf\x15\x00\xb9\x10\x6f\ \x6f\x2f\x5b\x36\xbd\xcc\xc3\xf7\xfe\x8c\xe6\x86\xbe\xeb\xb3\x7b\ \x75\xd8\x52\xa6\xd1\xd1\x63\x4a\x08\x8a\xf2\x29\x47\xcb\x4f\x58\ \x1d\x82\x2d\x58\xb0\x04\xb0\x2f\x37\x57\x74\x7f\xf2\x87\xfd\xd6\ \x0e\x92\xb0\x4d\x40\xa1\xf1\x31\xf9\xd7\xea\xcf\x2f\xe1\xf5\xb7\ \x77\xab\x0c\x53\xb1\x85\xd8\xb8\x78\xe6\x2f\x59\xc9\xe8\xbc\x02\ \x53\xfb\x3d\x7c\xe0\x7d\x36\xbe\xf2\x0c\xcd\x8d\xa7\x3e\xf5\x77\ \x3d\x3e\x78\xef\x84\xe0\xa2\x4c\x55\x2d\x40\x31\x9f\x2a\xdc\x06\ \xe1\xa1\x21\x84\xb8\x07\x59\x00\x60\xb8\x04\x5b\xfb\xfb\x71\xbf\ \x09\x80\x40\x6e\x03\x71\x8b\xb1\x11\xf9\x9f\x27\x32\x9c\x6f\x7f\ \x79\x39\x3f\xf9\xed\xa3\x56\x87\xa2\x04\x31\x97\xcb\xcd\xb4\xd9\ \x0b\x98\x3e\x7b\x11\x4e\xa7\x79\xbf\xe8\x8d\x0d\xf5\x6c\x7c\x79\ \x0d\x47\x0f\xed\x3d\xef\xeb\x6a\x5b\x05\x3d\x3e\x55\x27\x40\x31\ \x9f\x4a\x00\x20\x3e\xce\xfc\x9b\xbc\x84\x2e\xb7\xf5\xf7\xf3\x7e\ \x13\x00\x87\xc3\xb1\xcd\xe7\x0b\xac\x8d\x80\xa7\x5d\x3e\xaf\x84\ \x75\x9b\x77\xb1\x6d\xf7\x01\xab\x43\x51\x82\xd0\xb8\xfc\x29\xcc\ \x5b\xbc\x1c\x4f\x74\xac\x69\x7d\xf6\xf6\xf6\xb0\xf5\xcd\x75\xec\ \x7c\x7b\x23\x3e\x9f\xf7\x82\xaf\x97\x40\x53\xa7\x20\x31\x52\xcd\ \x02\x28\xe6\xfa\xf0\x58\x95\xd5\x21\x58\xce\x8a\x04\xc0\xa7\x39\ \x06\x3e\x03\x50\x98\xc4\xbe\xdd\x35\x34\x23\x09\xc8\x4b\x47\xff\ \xf5\xd6\x6b\xb8\xe1\x8e\x5f\xd0\xd9\xf9\xa9\x25\x0f\x45\x31\x44\ \x5c\x7c\x22\xf3\x17\xaf\x24\x27\x77\x82\xa9\xfd\x1e\x39\xf8\x01\ \x1b\x5e\x7a\x9a\xe6\xa6\xc1\x1d\xad\xea\x08\xb8\x6d\xbe\x4a\xa0\ \xeb\xe8\xe8\x52\x27\x00\x80\x84\x51\xa6\x0f\xab\x4d\x53\x53\x38\ \xd4\xdf\x5f\xf4\xbf\x04\x20\x84\x5e\x5a\xa5\xef\x04\xb9\xd0\xd8\ \xb8\x8c\x91\x14\x1f\xcb\x37\xbf\x78\x15\xbf\xbc\xef\x09\xab\x43\ \x51\x46\x38\x97\x3b\x84\x59\xf3\x97\x50\x3c\x63\x2e\x9a\xc3\xbc\ \x39\xf5\x86\xfa\x5a\xd6\xbf\xf4\x34\x65\x47\x0e\x0e\xe9\xfd\x5d\ \x17\x9e\x28\x50\x14\xbf\xda\xfb\x61\x79\xd0\x5f\x02\x04\x90\x60\ \xfa\x0c\x80\xd8\xfa\xc9\x0a\x80\xa7\x9d\xf3\x02\x41\x21\xe4\x56\ \x29\x09\xc8\x04\x00\xe0\xca\x4b\x2f\x66\xe7\xfb\x87\xd8\xb0\xe5\ \x53\xb5\x0f\x14\xc5\x2f\xc6\xe4\x15\xb0\x68\xd9\x35\x44\x45\xc7\ \x98\xd6\x67\x6f\x6f\x0f\x3b\xde\x5e\xcf\xb6\x37\x5f\x1f\xd0\x74\ \xff\xb9\x74\xaa\x19\x00\xc5\x64\xef\x1d\x38\x6a\x75\x08\xb6\x90\ \x38\xca\xbc\xef\x0b\x00\x49\xff\xeb\xff\x70\x9e\x04\x40\x0a\xf9\ \x36\x32\xb0\xcb\x88\x7e\xf7\xd6\xcf\xb2\xf7\x50\x19\xb5\xea\xf6\ \x29\xc5\x8f\x12\x92\xd2\x58\xb8\x6c\x15\xe9\x99\x63\x4c\xed\xf7\ \xc8\xc1\x0f\x58\xff\xe2\x53\xb4\x34\x0f\xff\xf3\xdc\xd0\x21\x40\ \xdd\x1b\xa8\x98\x48\x25\x00\x7d\x92\xe2\xcd\xdb\x1f\x04\x20\xd0\ \x37\x9f\xeb\xef\xce\x99\x00\x74\x38\x1d\x9b\xc3\x7b\x74\xef\xf9\ \x5e\x63\x77\x51\x11\xe1\xfc\xf0\x9b\xd7\x71\xfb\x7f\xfd\x81\x40\ \xdd\xd4\xa8\xd8\x47\x48\x68\x38\xb3\x17\x2c\x65\xca\xf4\x59\x08\ \x61\x5e\xd1\xa9\xfa\xba\x1a\xd6\xbf\xf4\x14\x15\xc7\x0e\xfb\xad\ \xcd\xb6\x6e\x68\xee\x84\x68\x75\x59\x90\x62\x02\x5d\xd7\xd9\x77\ \xb8\xdc\xea\x30\x6c\x21\x2d\x69\x94\x79\x9d\x09\x7a\x1d\x9a\xf3\ \x9d\x73\xfd\xf5\x39\xbf\xc5\x66\x27\x88\xd6\xfe\x6a\x07\x07\x9a\ \xa2\x82\xb1\x7c\xfd\x0b\x57\x5a\x1d\x86\x12\xc8\x84\x20\x7f\xf2\ \x34\x6e\xfe\xd6\xf7\x29\x9a\x31\xc7\xb4\xc1\xbf\xb7\xa7\x87\x2d\ \x9b\x5e\xe6\x91\x3f\xfe\xaf\x5f\x07\x7f\xe8\x7b\xf6\xdf\x5b\x27\ \xd4\x1c\x80\x62\x8a\xbd\x1f\x96\xa9\x4d\xd9\x80\x10\x82\x14\x33\ \x13\x00\x29\x77\x4e\x4e\x16\xe7\x2c\x8c\x73\xfe\xa7\x7b\x21\xde\ \x44\x52\xe2\xf7\xa0\x4c\xf6\xb9\x65\xf3\x38\x52\x56\xcd\x4b\x1b\ \xb7\x5b\x1d\x8a\x12\x60\x92\x52\xd2\x59\x78\xc5\x67\x49\xcd\xc8\ \x36\xb5\xdf\x23\x07\x3f\xe0\xf5\x17\x9f\xa4\xb5\xb9\xc9\xb0\x3e\ \xea\xda\x04\x47\x4e\xc1\xd8\x51\x2a\x0d\x50\x8c\xb5\xb5\x54\x1d\ \xcb\x86\xbe\x13\x00\xe6\x16\x01\x12\x6f\x9e\xef\x6f\xcf\x9b\x00\ \x08\x29\xdf\x94\x88\x3b\xfd\x1b\x90\x35\xee\x5c\xbd\x8a\x23\xe5\ \x35\x1c\x3c\x52\x61\x75\x28\x4a\x00\x08\x0b\x8f\x64\xee\xa2\x65\ \x4c\x2c\xbe\x08\x21\xcc\xdb\x0b\x73\xb2\xb6\x8a\xf5\x2f\x3e\x45\ \x65\x99\x39\xeb\xa5\x7b\x6b\x05\x9e\x10\x54\x4d\x00\xc5\x50\x5b\ \xf7\x04\x5c\x75\x79\x43\x98\x3a\xfd\x0f\x08\x5d\x0e\x3d\x01\x70\ \xeb\x8e\xb7\xba\x35\x5d\xe7\x3c\x4b\x05\x81\x22\x34\xc4\xcd\xff\ \x7e\x6f\x35\xb7\xfe\xe0\x37\xea\x3a\x4a\xe5\x9c\x84\xd0\x98\x32\ \x6d\x16\xb3\x17\x2e\x25\x24\xd4\xbc\x1b\xbb\xba\xbb\x3a\xd9\xbc\ \xe1\x45\xf6\x6c\x7f\x1b\x29\xcd\xdb\xaf\x22\x25\xec\xa8\x10\xcc\ \x1d\x2d\x89\x32\xe6\x56\x62\x25\xc8\x35\x34\xb5\x72\xe8\x68\xa5\ \xd5\x61\xd8\x42\x7a\x4a\x82\x99\xdd\xf9\xf0\x3a\xb6\x9c\xef\x05\ \xe7\x1d\xd8\x0b\x32\x44\x83\x84\x77\xfd\x1b\x93\x75\xe2\x62\x3d\ \xfc\xea\xfb\x5f\xc1\x13\x69\xfe\x55\x8c\x8a\xfd\x25\xa7\x65\x72\ \xdd\x2d\xb7\xb3\xf0\x8a\xab\xcd\x1b\xfc\xa5\x64\xdf\x9e\x1d\x3c\ \xf0\xdb\xff\x61\xf7\xb6\xb7\x4c\x1d\xfc\x4f\xeb\xd5\xe1\x9d\x72\ \x0d\xaf\xcf\xf4\xae\x95\x20\xb0\x75\xf7\x7e\xa4\x54\x33\x4c\x00\ \x39\xe9\xc9\xe6\x75\x26\xe4\xae\xa2\x1c\x71\xde\x35\xc4\x0b\xee\ \xf0\xd7\xe0\x35\x09\x45\xfe\x8b\xca\x5a\x59\xe9\x49\xfc\xfc\x7b\ \x37\xf3\x9d\xff\xbe\x8f\xce\x2e\x75\x2d\x9a\x02\x11\x91\x1e\xe6\ \x5d\x76\x25\xf9\x85\x25\x60\xe2\x74\x7f\x6d\x75\x05\xeb\x5f\x7c\ \x8a\xea\xca\xe3\xa6\xf5\x79\x2e\x1d\x3d\x7d\x9b\x02\x27\xa7\xa8\ \x2f\x6a\xc5\xbf\xb6\xec\x3a\xff\xdd\x14\xc1\x24\x27\x23\xc5\xb4\ \xbe\x04\xe2\xb5\x0b\xbd\xe6\x82\x53\xfb\x12\xfd\x82\x8d\x04\x9a\ \xc2\xf1\xa3\xf9\xbf\x1f\xdc\x4a\x58\xa8\x39\x57\xb3\x2a\xf6\xa4\ \x69\x1a\xc5\x33\xe6\x71\xf3\xb7\x7e\x40\xfe\xe4\x69\xa6\x0d\xfe\ \xdd\x5d\x1d\x6c\x78\x69\x0d\x7f\xfb\xd3\xaf\x6d\x31\xf8\x9f\x76\ \xbc\x51\xd0\xad\x2a\x04\x2a\x7e\xd4\xd1\xd1\xc5\x3b\xa5\x6a\xfd\ \xff\xb4\xd1\x59\xe6\xcd\x00\x48\xa1\x5d\x70\xec\xbe\xe0\x0c\x40\ \x73\x8f\x73\x73\xb4\x5b\xef\x04\x46\xd4\x89\xe1\xc2\x09\xa3\xf9\ \x9f\x7f\xb9\x89\xef\xfd\xfc\x7e\x7a\xbc\xea\x5b\x2f\xd8\x64\x8e\ \xce\x63\xe1\x92\x55\x8c\x4a\x34\xf1\x17\x52\x4a\xde\x2f\x7d\x87\ \x37\x5f\x7f\x81\xae\x0e\xfb\x5d\x59\x2d\x25\x54\x36\x0b\xc6\xa8\ \x53\x01\x8a\x9f\xbc\xb9\xe3\x03\xba\x7b\x54\xd9\x49\xe8\xab\x4b\ \x33\x2a\xc6\x63\x56\x77\xed\x51\x6d\xfd\x5f\x01\x7c\xa6\x0b\x26\ \x00\xf3\x73\x44\x57\x69\x95\xbe\x19\xe4\xa5\xfe\x89\xcb\x3e\xa6\ \x4f\x19\xc7\x2f\xbf\x7f\x0b\xdf\xff\xe5\x83\x74\x74\xa9\x33\xaa\ \xc1\x20\x2a\x3a\x86\x4b\x2e\x5f\xc1\xb8\x82\x29\xa6\xf6\x5b\x53\ \x55\xc6\xfa\x17\x9e\xe2\x44\xb5\xbd\x8b\xa1\x34\x75\x59\x1d\x81\ \x32\x92\xbc\xb6\x79\x97\xd5\x21\xd8\x46\x4e\xa6\x79\x0f\x1b\x02\ \xb1\x31\x37\x57\x5c\x70\x50\x1b\x50\x95\x3f\x81\x7c\x4d\xc2\x88\ \x4b\x00\x00\x4a\x0a\xf3\xf8\xcd\x8f\xbf\xce\xbf\xfc\xf4\x4f\x34\ \xb7\xd8\xef\xa9\x4c\xf1\x0f\xcd\xe1\x60\x4a\xc9\x6c\xe6\x2c\xba\ \x02\x97\xdb\xbc\xed\xee\x5d\x9d\x1d\x6c\xd9\xf8\x0a\xbb\xb7\x5b\ \xb3\xc1\x6f\xb0\xbc\xf6\x0f\x51\x09\x10\x4d\x2d\x6d\xec\x7c\xaf\ \xdf\x4b\xe8\x82\xd2\xf8\xd1\xe9\xa6\xf5\x25\x85\x1c\xd0\xd2\xfd\ \x80\x12\x00\xcd\xa1\xbd\xec\xf3\xe9\xbf\x1c\x5e\x48\xf6\x35\x61\ \x6c\x26\xbf\xfd\xf1\x37\xf8\xee\x4f\xff\xac\xee\x0d\x18\x81\x46\ \xe7\xe6\x33\x7f\xc9\x4a\x62\x47\x99\x77\x04\x47\x4a\x9d\x3d\x3b\ \xde\x66\xf3\xfa\x97\xe8\xee\xea\x30\xad\xdf\xe1\x0a\x31\xef\x42\ \x43\x65\x84\x5b\xff\xf6\x1e\x55\x82\xfd\x0c\xe3\x46\x67\x98\xd6\ \x97\x43\x6a\xaf\x0e\xe4\x75\x03\x4a\x00\x26\x27\x8b\x0f\x4a\xab\ \x7c\xc7\x80\x9c\x61\x45\x65\x63\xa3\x33\x53\x78\xf0\x97\xdf\xe1\ \xfb\xff\xfb\x10\xef\xee\x3f\x62\x75\x38\x8a\x1f\x44\x45\xc7\x30\ \x67\xc1\x32\xf2\xa7\x4c\x33\xb5\xdf\x13\xd5\xe5\xac\x7f\xe1\x29\ \x6a\xaa\xca\x4c\xed\xd7\x1f\xd4\xdd\x00\x8a\xbf\x3c\xfb\xda\x79\ \x8f\xa0\x07\x9d\x09\x63\x4d\x4a\x00\x04\x47\x26\xa7\x8a\x01\xdd\ \x13\x3e\x98\x8b\x7e\x5e\x04\x6e\x1b\x5a\x44\x81\x21\xda\x13\xc1\ \xaf\xff\xe3\x56\x7e\xfe\xfb\xc7\x78\x6d\x73\xa9\xd5\xe1\x28\x43\ \xe4\x72\xb9\x99\x3e\x67\x11\xd3\x66\x2d\xc0\xe9\x34\xaf\xec\x66\ \x47\x5b\x2b\x6f\xbc\xf6\x1c\x7b\xdf\xdd\xd1\xb7\xa3\x2e\x00\xa9\ \xeb\xda\x15\x7f\xd8\xb3\xef\x08\x47\xcb\x6b\xac\x0e\xc3\x36\xc2\ \xc2\x42\x48\x4f\x49\x34\xab\xbb\x67\x07\xfa\xc2\x01\x27\x00\x52\ \xe8\x2f\x0a\xa9\x8d\xe8\x04\x00\x20\xc4\xed\xe2\x47\xb7\x7f\x81\ \x89\xe3\x72\xf8\xdd\x5f\x9e\x55\x27\x04\x02\x4c\x5e\xfe\x64\x2e\ \xb9\x7c\x39\x9e\x98\x38\xd3\xfa\xd4\x75\x9d\xdd\xdb\xdf\xe2\xed\ \x8d\x2f\xd3\xd3\xd5\x69\x5a\xbf\x46\xd8\x57\x27\xf0\x84\x5a\x1d\ \x85\x12\xe8\x9e\x5d\xa7\x9e\xfe\xcf\x34\x2e\x27\x1d\x4d\x33\xe7\ \x98\xb1\xd4\xf5\x17\x07\xfa\xda\x01\x27\x00\x9e\x76\xe7\xc6\x96\ \x70\xbd\x55\x40\xd4\xd0\xc2\x0a\x2c\xab\x96\xcc\x26\x3f\x37\x93\ \x1f\xdd\xf5\x57\xaa\x6b\x4f\x59\x1d\x8e\x72\x01\x29\xe9\x59\x5c\ \x72\xf9\x72\xd2\x32\x47\x9b\xda\x6f\xc5\xf1\xc3\xac\x7f\xe9\x69\ \xea\x6b\xab\x4d\xed\xd7\x28\x3e\x1d\xb6\x94\x09\xb4\x66\xf3\x0a\ \x22\x29\x23\x4b\x63\x73\x2b\x9b\xb6\x8e\x98\x02\xb2\x7e\x51\x90\ \x97\x6d\x56\x57\x2d\xa1\xcd\xce\xcd\x03\x7d\xf1\x80\x13\x80\xdc\ \x5c\xd1\xbd\xbb\x4a\x7f\x5d\x22\x57\x0c\x2d\xae\xc0\x33\x61\x6c\ \x26\x0f\xff\xea\xbb\xdc\xf3\xf0\x33\xbc\xb0\x7e\x9b\xd5\xe1\x28\ \xfd\x48\x48\x4a\xe3\xe2\x4b\x2e\x27\x6f\x42\xa1\xa9\x55\xfc\xda\ \x5a\x9b\xd9\xb4\xee\x59\x0e\xbc\x5f\x1a\xb0\xd3\xfd\xe7\x22\x25\ \x1c\x6f\x50\x09\x80\x32\x34\x6b\xd7\x6d\xa1\x57\xd5\x95\x3e\xcb\ \xe4\x09\xe6\x3c\x98\x08\x78\xa5\xa0\x40\x0c\xb8\xc4\xed\x60\xf6\ \x00\x80\xd4\x9f\x43\x88\xa0\x49\x00\x00\xc2\xc3\x42\xf8\xde\xd7\ \x3e\xcf\xdc\x19\x85\xfc\xe2\xf7\x8f\x73\xaa\xa9\xc5\xea\x90\x14\ \x20\x31\x25\x9d\x8b\xe7\x5d\x4e\xee\xf8\x49\xa6\x0e\xfc\xba\xcf\ \x47\xe9\xd6\x37\x78\x7b\xd3\x2b\xf4\xf6\xa8\xda\x11\x8a\x72\xa6\ \x8e\xae\x6e\x9e\x7c\xe9\x2d\xab\xc3\xb0\x15\x4d\x13\x4c\x1a\x9f\ \x6d\x4a\x5f\xba\x90\xcf\x0f\xe6\xf5\x83\x4b\x00\x7a\x1d\x6b\x71\ \xeb\xf7\x01\x41\x57\x43\x77\x66\x71\x3e\x8f\xdd\xfb\x03\x1e\x7e\ \x6a\x1d\xff\x78\x6e\x23\xba\xda\x2d\x65\x3e\x21\xc8\x1a\x9d\x47\ \xf1\x8c\xb9\x8c\xc9\x2b\x30\x75\xe0\x07\xa8\x38\x76\x98\xf5\x2f\ \x3d\x45\x7d\x9d\xda\xdc\xa4\x28\xfd\x59\xfb\xea\xdb\xb4\xb4\xaa\ \x7a\x2a\x67\xca\xc9\x48\x21\x2a\xc2\x94\xcb\xc5\x7a\x5c\xbd\x8e\ \x01\xaf\xff\xc3\x20\x13\x80\xa2\x1c\xd1\x54\x5a\xa5\xbf\x31\x12\ \xab\x02\x0e\x44\x58\xa8\x9b\xaf\xdd\xb0\x8c\x84\xd1\x45\x3c\xf9\ \xe4\xd3\x54\x55\x1c\xb3\x3a\xa4\xa0\xe0\x0e\x09\x25\x7f\x72\x09\ \xc5\x33\xe6\x12\x17\x9f\x64\x7a\xff\xad\x2d\x4d\x6c\x7a\xe5\x19\ \x0e\xee\xdd\x63\x7a\xdf\x8a\x12\x28\xba\x7b\x7a\x79\xec\x85\x37\ \xac\x0e\xc3\x76\x26\x8f\x37\xe7\xf4\xbc\x14\xe2\xb5\xc2\x2c\x31\ \xa8\x42\x36\x83\x9b\x01\x00\xa4\x94\x4f\x0b\x31\x32\xab\x02\x0e\ \x44\x63\x27\x38\xa3\x33\xb8\xf6\xe6\x6f\x73\x68\xff\x7b\xbc\xf5\ \xfa\xf3\x34\x9e\x3a\x69\x75\x58\x23\x52\x72\x6a\x26\x93\x4b\x66\ \x32\x6e\x52\x31\x6e\x13\xab\xf7\x9d\xe6\xf3\xf9\xd8\xb5\x65\x23\ \xef\xbc\xf9\x2a\xbd\x3d\xea\xe6\x48\x45\x39\x9f\x17\x37\x6c\xa3\ \xa1\x51\x2d\x91\x7e\x52\xd1\xa4\x5c\x73\x3a\x92\xfa\xd3\x83\x7d\ \xcb\xa0\x13\x00\xb7\x4b\x5b\xdb\xeb\xd5\x7f\x07\x04\x65\xcd\xb0\ \x43\xf5\x1f\x4d\x3b\x0b\x41\x5e\xfe\x64\xc6\x8e\x9b\xc8\x07\x7b\ \xb6\xb3\xed\xad\xd7\x68\x6e\x54\xa7\x05\x86\x2b\x22\xd2\xc3\xf8\ \x49\xc5\x14\x4c\x9e\x46\x62\x8a\x79\xa5\x33\x3f\xe9\xf8\xe1\x03\ \x6c\x78\xf9\x69\x1a\xea\xeb\x2c\x8b\x41\x51\x02\x45\x67\x57\x0f\ \x7f\x79\x6a\xc4\x5d\x1c\x3b\x6c\x9a\xa6\x51\x62\x4e\x02\xe0\x95\ \xc2\x31\xa8\xf5\x7f\x18\x42\x02\x30\x29\x49\xd4\x96\x56\x79\xdf\ \x06\x31\x77\xb0\xef\x0d\x74\xad\xdd\x70\xa2\xf5\xec\x75\x67\xcd\ \xe1\xa0\x70\xea\xc5\x4c\x9c\x32\x9d\xfd\xef\xed\x64\xdb\xdb\xeb\ \x69\x38\x59\x6b\x51\x84\x81\xc9\x1d\x12\xca\xd8\x09\x93\xc8\x2f\ \x2c\x21\x6b\x74\x1e\x42\x5c\xf0\x96\x6a\xc3\x34\x37\x35\xb0\xe9\ \x95\x67\xf8\x70\xff\x7b\x96\xc5\xa0\x28\x81\xe6\x1f\xcf\x6d\x54\ \x1b\xa4\xfb\x91\x3f\x36\xd3\xa4\xf5\x7f\xb1\xb1\x24\x55\xd4\x0f\ \xf6\x5d\x83\x4e\x00\x00\x24\xe2\x49\x01\x41\x97\x00\x94\x35\x8a\ \x73\x9e\xf8\xd2\x1c\x0e\x0a\x8a\x66\x50\x30\x65\x3a\xc7\x8f\x1e\ \xa4\xf4\x9d\x37\x38\x76\x78\x3f\x72\x84\x1d\x11\xf3\x97\xb0\xf0\ \x48\xc6\x8e\x9f\x48\xee\x84\x42\xb2\xc6\x8c\xc3\xe1\x18\xd2\x47\ \xd1\x6f\xbc\xde\x5e\x76\xbc\xbd\x81\xed\x6f\xbd\x4e\x6f\xaf\x9a\ \xee\x57\x94\x81\x3a\xd5\xd4\xc2\x63\xcf\x6d\xb4\x3a\x0c\x5b\x9a\ \x3e\x65\x9c\x29\xfd\x48\x21\x9f\x1a\xca\xfb\x86\xf4\xad\x2b\x34\ \xed\x71\x74\xfd\xae\xa1\xbe\x3f\x50\xd5\xb5\x0d\x60\xd7\xb9\x10\ \x64\x8f\x19\x4f\xf6\x98\xf1\x34\x37\x9e\xe2\x83\x3d\xdb\xd9\xbb\ \x7b\x1b\x2d\xcd\xea\x92\xa1\x51\x09\xc9\xe4\xe4\x4e\x60\x4c\xde\ \x44\xd2\xb3\x47\x5b\xfa\xa4\x7f\xa6\x23\x87\xf6\xb2\xf1\xe5\x35\ \x34\x35\x0c\x3a\x81\x56\x94\xa0\xf7\xc0\x63\x2f\xab\xeb\xd4\xcf\ \x61\xda\x64\x53\x12\x80\x1e\xaf\xae\x0d\x7a\xfd\x1f\x86\x38\x80\ \x17\xa7\x88\x93\xa5\x55\xfa\xeb\x20\x17\x0f\xe5\xfd\x81\x48\x02\ \xed\x83\x7c\x30\x8c\x8e\x1d\xc5\xac\xf9\x4b\x98\x79\xc9\x62\x2a\ \xcb\x8e\x70\xf8\xc0\xfb\x1c\x3e\xf0\x7e\xd0\xec\x15\x08\x8b\x88\ \x20\x2b\x27\x8f\xec\xb1\x13\xc8\x1e\x33\x9e\x48\x4f\xb4\xd5\x21\ \x9d\xa5\xa9\xa1\x9e\x8d\x2f\xaf\xe1\xc8\xa1\xbd\x56\x87\xa2\x28\ \x01\xe9\xd0\xb1\x4a\x5e\xd8\xb0\xdd\xea\x30\x6c\x29\xc6\x13\x49\ \x41\x6e\x96\x09\x3d\x89\x97\x67\xa4\x8b\x21\x0d\x2a\x43\x7f\x82\ \x17\xfa\xdf\x91\x22\x68\x12\x00\x24\xe8\x43\x9c\xcd\x17\x42\x90\ \x91\x3d\x96\x8c\xec\xb1\xcc\x5f\xbc\x82\x53\x27\x4f\x70\xe4\xe0\ \x07\x1c\x39\xb8\xb7\xef\x28\xe1\x08\x59\x26\x88\x88\xf4\x90\x9e\ \x35\x9a\xd4\xcc\xd1\xa4\x67\x8e\x26\x31\x25\x1d\x61\xf2\x59\xfd\ \x81\xf0\xf6\xf6\xb2\xfd\xed\xd7\xd9\xfe\xd6\x7a\xbc\xde\x5e\xab\ \xc3\x51\x94\x80\xa4\xeb\x92\xff\xfb\xd3\xd3\xaa\x26\xca\x39\xcc\ \x9e\x56\x80\xa6\x19\x3f\xcb\x29\xd1\xff\x31\xd4\xf7\x0e\x39\x01\ \x70\x68\x8e\x35\x3e\x9f\xfe\x07\x20\x62\xa8\x6d\x04\x12\x21\xc0\ \xa9\x41\xaf\x1f\x3e\xeb\xa3\x12\x92\x19\x95\x90\xcc\xf4\xd9\x8b\ \x68\x6b\x6d\xa6\xaa\xe2\x18\x35\xe5\xc7\xa8\xae\x3c\x4e\x6d\x75\ \x05\x3e\x9f\xfd\xcb\x68\x46\x45\xc7\x90\x98\x9c\x4e\x62\x72\x1a\ \x09\xc9\x69\xa4\xa6\x67\xdb\xee\x09\xbf\x3f\x1f\x1e\x78\x9f\x4d\ \x2f\xaf\xa1\xb9\xa9\xc1\xea\x50\x14\x25\xa0\x3d\xf5\xd2\x9b\xec\ \xfd\xf0\xb8\xd5\x61\xd8\xd6\xec\x92\x49\x66\x74\xd3\xee\x74\x38\ \x5e\x18\xea\x9b\x87\x9c\x00\x4c\x4e\x16\xed\xa5\x55\xbe\x17\x80\ \xcf\x0d\xb5\x8d\x40\x13\x13\x26\x39\xd9\xee\xdf\x27\xda\xc8\xa8\ \x68\xc6\xe5\x4f\x61\x5c\xfe\x14\x00\x7c\x3e\x2f\x27\xaa\x2b\xa8\ \xa9\x38\xce\xa9\x93\x27\x68\xac\x3f\x49\xc3\xa9\x3a\x3a\xda\x5b\ \xfd\xda\xef\x40\x68\x0e\x07\xd1\x31\x71\xc4\xc4\x25\x10\x1b\x97\ \x40\xec\xa8\x04\xe2\xe2\x13\x49\x4a\x49\x27\x34\x3c\xb0\xf2\xbe\ \x86\xfa\x3a\x36\xbe\xb2\x86\x63\x1f\xee\xb7\x3a\x14\x45\x09\x78\ \xb5\xf5\x8d\xdc\xff\xd8\xcb\x56\x87\x61\x5b\x61\xa1\x6e\xa6\x4d\ \xce\x33\xbe\x23\xc9\x9a\xc9\xc9\x62\xc8\xa5\x17\x87\xb5\x89\x4f\ \x48\xf9\x88\x14\x22\x68\x12\x80\xc4\x28\x38\x69\x70\x95\x4b\x87\ \xc3\x49\x5a\x46\x0e\x69\x19\x67\x57\x8f\xea\xe9\xea\xa4\xa1\xe1\ \x24\x8d\xa7\x4e\xd2\xda\xd4\x48\x47\x47\x1b\x9d\x1d\xed\x74\x75\ \xb4\xd3\xd9\xd1\x4e\x67\x67\x3b\x1d\xed\x7d\xc1\xf9\xbc\xbd\xfd\ \x4e\x6d\xbb\x5c\x6e\x1c\x4e\x27\xa1\xa1\x61\x38\x5c\x2e\x9c\x4e\ \x17\x61\xe1\x11\x84\x47\x46\x11\x11\xe9\x21\x32\xd2\x43\x44\x44\ \x14\x11\x9e\x68\x22\x3d\x31\x44\xc7\xc4\x99\x32\x85\x65\xa4\xde\ \x9e\x1e\xb6\xbe\xb9\x8e\x9d\x5b\x36\xe2\xf3\xa9\xab\x9d\x15\x65\ \xb8\xa4\x94\xfc\xf2\xbe\x27\xd4\xc6\xbf\xf3\x98\x3e\x65\x3c\x21\ \x6e\x97\xe1\xfd\x08\xa1\x3f\x32\x9c\xf7\x0f\x2b\x01\xf8\x30\xcd\ \xf1\xca\xd8\x6a\xbd\x12\xb0\xae\x62\x8b\x89\x72\x62\x25\x65\x8d\ \x82\x36\x0b\x3e\xf7\xee\xd0\x30\x92\x53\x33\x49\x4e\xcd\x1c\xf4\ \x7b\xa5\xd4\x6d\xb3\xe3\xde\x4c\x07\xf7\xee\x61\xd3\xab\xcf\xd0\ \xda\xdc\x64\x75\x28\x8a\x32\x62\x3c\xf5\xf2\x5b\x6c\xdb\x7d\xc0\ \xea\x30\x6c\x6d\xe1\xcc\x22\x33\xba\xa9\xfc\x30\xd5\xb9\x61\x38\ \x0d\x0c\x6b\x54\xb8\x46\x08\x1f\xf0\xe8\x70\xda\x08\x24\x4e\x0d\ \x66\x66\xe9\x84\x1b\x9f\xd8\xf9\x55\xb0\x0d\xfe\xed\xad\x2d\x3c\ \xf1\x97\xdf\xf1\xfc\x13\x0f\xa9\xc1\x5f\x51\xfc\xe8\x68\x79\x0d\ \x7f\x78\x64\xc8\x4b\xce\x41\x21\x2c\x2c\x84\x59\x25\x05\xc6\x77\ \x24\x78\xe8\xa3\x31\x78\xc8\x86\x3d\x32\x68\x52\x7b\x90\xbe\x53\ \x72\x41\x21\xdc\x05\xb3\xb2\x75\xdc\x41\x59\x08\x39\x30\xd4\xd7\ \xd5\x50\x7e\xf4\x90\xd5\x61\x28\xca\x88\xd2\xe3\xf5\xf2\x5f\xbf\ \x79\x94\x9e\x5e\x75\x72\xe6\x7c\x2e\x99\x51\x68\xc6\xf4\xbf\xd4\ \xbd\xda\x5f\x86\xdb\xc8\xb0\x13\x80\x29\xe9\xe2\x10\xc8\x77\x86\ \xdb\x4e\x20\x89\x70\xc3\xf4\x4c\x1d\xfb\x1d\x70\x53\x14\x45\x31\ \xc6\x6f\x1e\x78\x86\xc3\x65\x55\x56\x87\x61\x7b\x8b\x66\x15\x1b\ \xdf\x89\x90\x6f\x94\x64\x8a\x23\xc3\x6d\xc6\x5f\x73\xc3\x0f\xfa\ \xa9\x9d\x80\x11\x1f\x0e\xf1\x11\x41\x33\xf1\xa1\x28\x4a\x10\x7b\ \xee\xf5\xad\x3c\xfb\xda\x16\xab\xc3\xb0\xbd\x51\x31\x1e\x4a\x0a\ \x4d\xd8\xfd\xaf\xf3\x90\x3f\x9a\xf1\x4b\x02\x10\xe2\x72\x3c\x0e\ \x04\xdd\x4d\x10\x21\x41\x55\x08\x59\x51\x94\x60\xb4\xf7\xc3\xe3\ \xdc\x7d\xff\x90\x2a\xcd\x06\x9d\xa5\x0b\xa6\xe3\x70\x18\xbe\xe7\ \xaa\x49\xd7\x1c\x43\xaa\xfd\xff\x49\x7e\x89\xb4\x20\x51\xb4\x49\ \xf8\x9b\x3f\xda\x0a\x14\xbd\x3a\x7e\xaf\x09\xa0\x28\x8a\x62\x27\ \xf5\x8d\xcd\x7c\xff\x17\x0f\xd1\xe3\x55\x47\x68\x2f\x44\x08\xc1\ \xd2\xf9\xd3\x0d\xef\x47\x4a\xfe\x52\x92\x2a\x3a\xfc\xd1\x96\xdf\ \x52\x15\xa1\x69\xbf\x23\x48\x36\x03\x4a\x09\x7b\xaa\x04\xdd\xea\ \x77\x42\x51\x94\x11\xaa\xa3\xa3\x8b\xef\xfe\xcf\x9f\xd5\x35\xbf\ \x03\x34\x25\x7f\x34\x19\x29\x09\x86\xf7\xe3\x70\x68\x7f\xf6\x57\ \x5b\x7e\x4b\x00\x8a\x53\xc4\x3e\x81\x7c\xdb\x5f\xed\xd9\x95\x94\ \x50\x5a\x2d\xa8\x6a\x51\x4f\xff\x8a\xa2\x8c\x4c\x3d\x5e\x2f\xdf\ \xfb\xe5\x83\x7c\x78\x5c\x6d\xfa\x1b\xa8\xcf\x2c\xbc\xd8\xf8\x4e\ \x84\xdc\x34\x25\x45\xf8\xed\xf6\x32\xff\x2e\x56\x08\xfe\xe8\xd7\ \xf6\x6c\xc6\x27\x61\x5b\x85\xa0\xa2\x49\x0d\xfe\x8a\xa2\x8c\x4c\ \x52\x4a\x7e\xf1\x87\xc7\x29\xfd\xe0\x43\xab\x43\x09\x18\xb1\xd1\ \x51\x5c\x72\x71\xa1\xe1\xfd\x48\xe9\xdf\x31\xd6\xaf\x09\x40\x64\ \xbb\xe3\x29\xe0\xa4\x3f\xdb\xb4\x0b\x9f\x0e\xdb\xca\x05\x27\x5a\ \xd5\xe0\xaf\x28\xca\xc8\x24\xa5\xe4\x57\x7f\x7a\x8a\x57\xdf\xd8\ \x69\x75\x28\x01\x65\xc5\xe5\x33\x71\xbb\x0c\x3f\xfb\x5f\x1b\xda\ \xe4\x78\xc6\x9f\x0d\xfa\x35\x01\xc8\xcd\x15\xdd\x23\x71\x16\xc0\ \x27\x61\x4b\x99\x46\x5d\x9b\x1a\xfc\x15\x25\x18\x95\x35\x8e\xfc\ \x0d\x4e\x52\x4a\xee\xba\xff\x69\x75\xdc\x6f\x90\x5c\x4e\x07\xcb\ \x2f\x9d\x69\x78\x3f\x02\xfe\x50\x50\x20\x7a\xfc\xd9\xa6\xdf\xcf\ \x2b\xe8\x3d\xda\x1f\x00\xbf\x06\x69\xb5\xbd\x27\x04\xa7\xfc\xb2\ \xe7\x52\x51\x94\x40\x54\xd5\x22\xd8\x59\x29\xd0\x47\x68\x16\x70\ \xfa\xc9\x7f\xcd\xab\x23\x7e\x1b\x97\xdf\x2d\x98\x55\x44\x5c\xac\ \xc7\xe8\x6e\xba\x7b\x5d\xda\x7d\xfe\x6e\xd4\xef\x09\x40\x49\xb6\ \xa8\x11\x92\x27\xfc\xdd\xae\x55\x7a\x7d\x50\xd6\xa8\x9e\xfc\x15\ \x25\xd8\x55\x35\x0b\x36\x1f\xd7\x46\xdc\xe9\x1f\x9f\x4f\xe7\x67\ \xbf\x7f\x4c\x3d\xf9\x0f\x81\x10\x82\xeb\xae\x9c\x6f\x7c\x3f\xf0\ \xf7\xe9\x89\xe2\x84\xbf\xdb\x35\xa4\x62\x81\x4f\xd3\xee\x32\xa2\ \x5d\x2b\x74\x7b\xfb\x96\x00\x14\x45\x51\x1a\x3a\x60\xe3\x11\x8d\ \xda\x11\xb2\x17\xa8\xab\xbb\x87\x7f\xff\xe5\x03\xbc\xb4\x71\xbb\ \xd5\xa1\x04\xa4\x99\x53\xf3\x19\x93\x95\x6a\x7c\x47\x52\xbb\xc7\ \x88\x66\x0d\x49\x00\x4a\x52\x45\xa9\x40\x6e\x36\xa2\x6d\xb3\x85\ \x3a\x51\x35\xff\x15\x45\xf9\x58\x97\x17\xb6\x96\x0b\xde\xad\x11\ \xf4\xea\x56\x47\x33\x74\xad\xed\x1d\xdc\xf9\x93\xfb\xd8\xb2\x6b\ \x9f\xd5\xa1\x04\xac\xeb\x97\x2f\x30\xa1\x17\xb1\xa1\x28\x5d\xec\ \x31\xa2\x65\xc3\x6a\x16\xea\x82\x11\x31\x0b\xe0\x74\x40\x74\x98\ \xd5\x51\x28\x8a\x62\x27\x12\x38\xd6\x20\x78\xed\x90\xc6\xa1\xfa\ \xc0\x2b\x0a\x76\xbc\xe2\x04\x5f\xf9\xde\xdd\xbc\x77\xe0\xa8\xd5\ \xa1\x04\xac\xa2\x82\xb1\x14\x8e\x1f\x6d\x78\x3f\x12\xfd\x6e\xa3\ \xda\x36\xac\x9a\x7d\x71\x8a\x63\xed\xee\x6a\x7d\x1f\x90\x6f\x54\ \x1f\x66\x49\x8e\x92\x34\x75\xaa\x79\x00\x45\x51\xce\xd6\xe3\x83\ \x7d\xb5\x82\xfd\x75\x82\xa8\x10\x88\x09\x91\xb8\x9d\x7d\x0f\x0e\ \x0e\x01\x2e\x0d\x22\x42\x20\x36\x4c\xe2\x34\xbc\x44\xfc\xc0\xbc\ \xb9\xfd\x03\x7e\xf2\xdb\xbf\xd1\xd9\xd9\x6d\x75\x28\x01\xed\xc6\ \xcf\x5e\x66\x46\x37\xfb\x8b\x53\x1d\x2f\x1a\xd5\xb8\x61\x09\x80\ \x10\x42\x2f\xad\xf2\xfe\x1a\xc4\xfd\x46\xf5\x61\x96\x54\x8f\xe4\ \x40\x9d\x4a\x00\x14\x45\xe9\x9f\x94\xd0\xd2\x05\x2d\x5d\xfd\x7f\ \x4f\x84\x3a\x05\x33\x32\x75\x62\x2d\x9c\x4d\xd4\x75\xc9\xc3\x4f\ \xbd\xca\x43\x4f\xae\x43\x4a\xb5\xb1\x69\x38\xa6\x4e\xca\xa5\x78\ \x62\xae\xf1\x1d\x49\xf9\x73\x21\x84\x61\x0b\x4d\x86\xe6\xa4\x7a\ \x8d\xe3\xaf\x40\x85\x91\x7d\x98\xc1\x13\x02\xd1\xa1\x56\x47\xa1\ \x28\x4a\xa0\xea\xdb\x37\xa0\xd1\x65\xd1\x52\x41\x43\x53\x2b\xff\ \xf2\xd3\xfb\x78\xf0\x89\x57\xd5\xe0\xef\x07\xb7\x5c\xbb\xd4\x8c\ \x6e\x2a\x43\x9a\x1d\x8f\x19\xd9\x81\xa1\x09\x40\x49\x89\xe8\x15\ \x60\xc8\xee\x45\xb3\x65\xc4\xa8\x5f\x1a\x45\x51\x86\xae\xdb\x0b\ \x1f\x9c\x30\x7f\x26\x71\xfb\xbb\x07\xf9\xf2\x77\x7f\xc5\xf6\x3d\ \x07\x4d\xef\x7b\x24\x9a\x55\x52\xc0\xc4\xbc\x6c\xc3\xfb\x11\x82\ \x5f\xf9\xbb\xf0\xcf\x27\x19\xbe\x2a\xd5\xee\xd6\xee\x03\x1a\x8d\ \xee\xc7\x68\x69\x1e\xa9\x4e\x03\x28\x8a\x32\x2c\x55\x2d\x82\x96\ \x2e\x73\xfa\xea\xe8\xe8\xe2\x57\x7f\x7a\x92\xef\xfc\xf7\x7d\x34\ \x34\xaa\x1b\xfd\xfc\x41\xd3\x34\x6e\xbd\xee\x0a\x33\xba\x6a\x70\ \x3b\xb5\x07\x8c\xee\xc4\xf0\x04\x60\x76\x82\x68\x05\x7e\x6b\x74\ \x3f\x46\x0b\x73\x41\x8c\x3a\x0d\xa0\x28\xca\x30\x48\x09\x47\x1b\ \x8c\x7f\x94\xd8\xb6\xfb\x00\x5f\xb8\xf3\x97\xac\x5d\xb7\x45\x4d\ \xf9\xfb\xd1\x55\x97\x5e\xcc\xe8\xcc\x14\xe3\x3b\x92\xdc\x5d\x90\ \x28\xda\x8c\xee\xc6\xb0\x4d\x80\x67\xd2\xbb\xb4\x5f\x6b\xa1\xfa\ \xb7\x80\x18\x33\xfa\x33\x4a\x5a\xb4\xa4\x51\x9d\x06\x50\x14\x65\ \x18\x2a\x9b\x05\x05\x49\x12\x97\xc3\xff\x6d\x9f\x6a\x6a\xe1\x77\ \x7f\x7d\x8e\x75\x6f\xee\xf2\x7f\xe3\x41\x2e\x22\x3c\x8c\x9b\xae\ \xb9\xdc\xf8\x8e\x04\xcd\xa2\x47\x33\xe5\xa1\xd9\x94\x83\x29\x25\ \x63\x44\x33\x82\x7b\xcd\xe8\xcb\x48\xd9\xb1\x12\x8f\xda\x0c\xa8\ \x28\xca\x30\x78\x75\xfc\x7e\xb1\x58\xaf\xd7\xc7\xdf\x9f\xdd\xc0\ \xb5\xdf\xfa\x99\x1a\xfc\x0d\xf2\xa5\x55\x8b\x88\x8d\x8e\x32\xbe\ \x23\xc9\xaf\x8b\x72\x44\x93\xf1\x1d\x99\x34\x03\x00\x20\xba\xb5\ \xff\x93\x6e\xfd\x36\x02\x78\x16\xc0\xa9\xc1\x9c\x1c\x9d\x3d\x55\ \x82\xaa\x16\x35\x13\xa0\x28\xca\xd0\xd4\xb6\x41\x5a\xf4\xf0\xdb\ \x91\x52\xb2\x79\xe7\x5e\xfe\xf0\xc8\xf3\x94\x57\xd7\x0d\xbf\x41\ \xa5\x5f\x59\xe9\x49\x7c\xf6\x8a\x79\xc6\x77\x24\x68\x16\xdd\xc6\ \x94\xfd\xed\x8f\x69\x09\x40\x51\x8e\x68\xda\x55\xe9\xfb\xbd\x10\ \x7c\xdf\xac\x3e\x8d\xe0\xd2\xa0\x24\x43\x92\xd0\x00\xfb\xea\x04\ \x3d\x3e\xab\x23\x52\x14\x25\xd0\xd4\xb5\x09\x24\xc3\xdb\x58\xfc\ \xde\xfe\xa3\xdc\xf7\xf7\x97\x78\x77\xff\x11\xbf\xc5\xa5\x7c\x9a\ \x10\x82\xef\xac\x5e\x85\xcb\x69\xc0\x9a\xcd\x27\xfb\x92\xdc\x65\ \xd6\xd3\x3f\x98\x98\x00\x00\x78\xd1\x7e\xed\x42\xbf\x0d\x30\xfc\ \xee\x44\x23\x09\x20\x3b\x4e\x92\xe2\x91\xec\xa9\x11\xd4\xa8\xd9\ \x00\x45\x51\x06\xa1\xcb\xdb\x57\x38\x68\x28\xf5\x45\xb6\xbf\x7b\ \x90\x47\xd6\xbc\xce\xee\xbd\x87\xfd\x1f\x98\xf2\x29\x8b\xe7\x95\ \x98\x53\xf4\x07\x1a\xe9\xd1\x7e\x63\x46\x47\xa7\x99\x9a\x00\xcc\ \x48\x17\xa7\x76\x57\xf9\x7e\x2d\xe1\xc7\x66\xf6\x6b\x94\x10\x27\ \x4c\xcf\x90\x7c\x58\xdf\x57\x0e\x54\x51\x14\x65\xa0\xea\xdb\x05\ \xd1\xa1\x03\xdb\xa1\xaf\xeb\x3a\x1b\xde\x79\x97\xbf\xaf\xdd\xc0\ \xa1\x63\x95\x06\x47\xa6\x9c\xe6\x89\x8a\xe0\x1b\x5f\xbc\xd2\x94\ \xbe\x84\xe0\x7f\xcd\x7c\xfa\x07\x93\x13\x00\x00\xb7\x4b\xfb\xbf\ \xee\x5e\xfd\x6b\x40\x92\xd9\x7d\x1b\x41\x00\x79\xf1\x92\xce\x1e\ \x38\xd6\xa8\x92\x00\x45\x51\x06\xa6\x75\x00\x25\x5e\x1a\x1a\x5b\ \x78\xf9\x8d\x1d\xac\x5d\xb7\x85\x9a\xba\x06\xe3\x83\x52\xce\x72\ \xe7\xea\x55\xc4\x78\x22\xcd\xe8\xaa\xae\xdd\xa5\x99\xbe\x51\xde\ \xf4\x04\xa0\x20\x51\xb4\xed\xaa\xf6\xfd\x5c\xc8\x91\x71\x5b\xe0\ \x69\x13\x53\x24\x4d\x5d\x82\xc6\x4e\xab\x23\x51\x14\x25\x10\xb4\ \x75\x0b\xfa\xee\x15\x3c\x9b\xd7\xe7\x63\xdb\xee\x03\xbc\xbc\x69\ \x3b\x9b\x77\xec\xc5\xeb\x53\x1b\x8d\xac\x30\x77\xc6\x24\x16\xcd\ \x2a\x32\xa5\x2f\x09\xff\xf5\x51\xcd\x1c\x53\x99\x9e\x00\x00\x84\ \x36\x6a\xbf\xef\x8e\xd1\xbf\x05\xe4\x58\xd1\xbf\x11\x1c\x02\x8a\ \xd3\x74\x36\x1e\xd1\xd0\x55\xdd\x0d\x45\x51\x2e\xa0\xf5\x8c\xcb\ \xf8\x74\x5d\xe7\xbd\x03\xc7\xd8\xb0\x65\x0f\xeb\xb7\xec\xa6\xb9\ \xa5\xdd\xba\xc0\x14\xa2\x3d\x11\xfc\xcb\x2d\x9f\x35\xab\xbb\xe3\ \x9e\x0e\xcd\x92\x4b\xf3\x2c\x49\x00\x0a\x0a\x44\x4f\x69\x95\xf7\ \xbf\x41\x18\x5e\xea\xd0\x4c\x51\x21\x30\x3e\x51\xaa\xfd\x00\x8a\ \xa2\x5c\x50\x6b\x7b\x27\x6f\x6c\x3d\xc8\xe6\x9d\x1f\xb0\xa5\x74\ \x9f\x1a\xf4\x6d\xe4\x3b\xb7\x7c\x96\xb8\x18\x13\xce\xfc\x03\x48\ \xf9\xa3\xdc\x5c\x61\xc9\xdd\xcc\x96\x24\x00\x00\x87\x53\x1d\x7f\ \x19\x5b\xad\x7f\x1b\x28\xb4\x2a\x06\x23\x8c\x8d\x97\x54\x36\x9b\ \x57\xef\x5b\x51\x94\xc0\xd0\xd9\xd1\x46\x4d\x65\x19\x95\x65\x47\ \x29\x3f\x76\x88\xda\xea\x4a\xa4\x34\xec\xa6\x57\x65\x88\x96\x2d\ \x9c\xc1\x82\x8b\x27\x9b\xd5\xdd\x9e\xc3\x69\x8e\x47\xcd\xea\xec\ \x93\x2c\x4b\x00\xae\x11\xc2\xb7\xa7\xb2\xf7\x76\x5d\x68\x1b\xac\ \x8a\xc1\x08\x1a\x30\x25\x45\xe7\xad\x63\x5a\x3f\xab\x7b\x8a\xa2\ \x8c\x74\x5e\x6f\x2f\x2d\x4d\x0d\xd4\xd7\x9d\xe0\x54\x5d\x0d\xf5\ \x75\x35\x9c\xa8\xae\xa0\xb9\xf1\x94\xd5\xa1\x29\x17\x90\x99\x9a\ \xc8\xb7\x6f\x5c\x69\x5e\x87\x9a\x76\xfb\x35\x42\x58\xb6\xc9\xc3\ \xb2\x04\x00\x60\x4a\xba\x6b\xe3\xae\x2a\x7d\xad\x40\x2e\xb7\x32\ \x0e\x7f\x8b\x0b\x87\xcc\x18\x49\x59\x93\x5a\x0a\x50\x94\x91\xe0\ \x83\x3d\xdb\xa9\xad\xa9\x38\xeb\x67\x5e\x6f\x2f\x3d\xdd\xdd\x74\ \x76\x76\xd0\xdd\xd9\x4e\x7b\x5b\x2b\x2d\x4d\x8d\xb4\xb7\xa9\x9b\ \xf7\x02\x91\xdb\xe9\xe4\xc7\xb7\x7f\x81\xb0\x50\xb7\x29\xfd\x09\ \x78\xa2\x28\x45\xbc\x61\x4a\x67\xe7\x60\x69\x02\x00\xa0\x3b\xc5\ \x77\x1c\x5e\xb9\x04\x08\xb1\x3a\x16\x7f\xca\x4f\x92\xd4\xb4\xaa\ \x4a\x81\x8a\x32\x12\xec\x7f\x6f\x27\xfb\xad\x0e\x42\x31\xd4\xb7\ \x6e\x5a\x41\xde\xe8\x74\xb3\xba\xeb\x42\xd3\xfe\xcd\xac\xce\xce\ \xc5\x94\xcb\x80\xce\x67\x5a\x92\x38\x2a\xc0\xd4\xea\x47\x66\x08\ \x71\x42\x7e\xa2\x5a\x04\x50\x14\x45\xb1\xbb\x4b\xe7\x14\xb3\xfc\ \xb2\x99\xe6\x75\x28\xf8\x55\x51\x8a\x38\x6e\x5e\x87\xfd\xb3\x3c\ \x01\x00\x68\x77\x6b\xff\x8d\xa0\xc6\xea\x38\xfc\x2d\x2b\x56\x12\ \x1b\x66\x75\x14\x8a\xa2\x28\xca\xb9\x8c\xce\x4c\xe5\xdf\xbe\xfa\ \x39\x33\xbb\xac\x0a\x71\x6a\xbf\x30\xb3\xc3\x73\xb1\x45\x02\x30\ \x3b\x41\xb4\x22\xe5\x7f\x58\x1d\x87\xbf\x09\x01\x93\x53\x74\x84\ \xda\x0a\xa0\x28\x8a\x62\x3b\x31\x9e\x48\x7e\xf6\x6f\x37\x12\x1a\ \x62\xce\xba\x3f\x80\x14\xf2\x7b\x05\x89\xa2\xcd\xb4\x0e\xcf\xc3\ \x16\x09\x00\x40\x51\xaa\xe3\x21\xa4\xdc\x61\x75\x1c\xfe\x16\x13\ \xd6\x37\x13\xa0\x28\x8a\xa2\xd8\x87\xdb\xe5\xe2\x67\xff\x76\x13\ \x69\x49\xf1\xa6\xf5\x29\x84\xdc\x56\x9c\x62\xdd\xb1\xbf\x4f\xb2\ \x4d\x02\x20\x84\xd0\x85\x70\xdc\x4e\x7f\xb5\x31\x03\x5c\x7e\xa2\ \xc4\x6d\xfc\x4d\x92\x8a\xa2\x28\xca\x00\x08\x21\xf8\xf7\xaf\x7f\ \x9e\x49\xe3\x4c\x2d\x46\x2b\x7d\x38\xfe\x45\x08\x61\x9b\x31\xce\ \x36\x09\x00\x40\x51\x9a\xd8\x22\xe0\x49\xab\xe3\xf0\x37\xb7\x03\ \x26\x24\xd9\xe6\xdf\x5c\x51\x14\x25\xa8\x7d\xed\xfa\x65\x5c\x3a\ \xa7\xd8\xdc\x4e\x25\x7f\x2b\x49\x15\x9b\xcd\xed\xf4\xfc\x6c\x95\ \x00\x00\x48\xa1\xfd\x2b\x30\xe2\xae\xd4\xc9\x8e\x91\x78\x46\xd4\ \x41\x47\x45\x51\x94\xc0\xb3\x6c\xe1\x0c\xae\x5b\xbe\xc0\xec\x6e\ \xdb\x34\xa9\xfd\xbb\xd9\x9d\x5e\x88\xed\x12\x80\xe2\x54\x51\x06\ \xfc\xa7\xd5\x71\xf8\x9b\x10\x50\xa0\x66\x01\x14\x45\x51\x2c\x33\ \xa3\x68\x3c\xdf\xfd\xca\x35\xa6\xf7\x2b\x05\xff\x31\x25\x43\x54\ \x99\xde\xf1\x05\xd8\x2e\x01\x00\x68\x4e\xd5\xfe\x0f\xd8\x65\x75\ \x1c\xfe\x96\x14\x25\x89\x0b\xb7\x3a\x0a\x45\x51\x94\xe0\x33\x6e\ \x4c\x06\x3f\xb9\xf3\xcb\x38\x1c\x26\x0f\x7b\x52\xee\x38\x92\xa2\ \xfd\xd6\xdc\x4e\x07\xc6\x96\x09\xc0\x7c\x21\xbc\x48\xed\x66\x04\ \xbd\x56\xc7\xe2\x6f\x93\x92\x75\xd4\xa9\x40\x45\x51\x14\xf3\xe4\ \x66\xa7\x71\xd7\x0f\x6f\x25\x3c\xcc\xf4\x75\x58\xaf\x26\x1c\xb7\ \x5a\x59\xef\xff\x7c\x6c\x99\x00\x00\x14\xa7\x8b\x77\x85\xe4\x2e\ \xab\xe3\xf0\xb7\xd8\xb0\xbe\x99\x00\x45\x51\x14\xc5\x78\x63\xb3\ \xd2\xf8\xcd\x8f\xbe\x86\x27\x2a\xc2\xfc\xce\x05\x3f\x9f\x92\x26\ \x76\x9b\xdf\xf1\xc0\xd8\x36\x01\x00\xe8\xd4\xb5\x1f\x03\x87\xad\ \x8e\xc3\xdf\xf2\x13\xa5\x2a\x0e\xa4\x28\x8a\x62\xb0\x31\x59\xa9\ \xd6\x0d\xfe\x70\xa8\xb9\x5b\xfb\x1f\x2b\x3a\x1e\x28\x5b\x27\x00\ \x33\x33\x44\xa7\xd0\xf5\x5b\x18\x61\xb5\x01\x3c\xa1\x90\xe1\x19\ \x51\xff\x97\x14\x45\x51\x6c\x25\x33\x2d\x89\xbb\x7e\x78\x2b\xd1\ \x1e\x4b\x06\x7f\x29\xa5\xfe\xb5\xf9\x39\xa2\xcb\x8a\xce\x07\xca\ \xd6\x09\x00\x40\x51\x86\x6b\x13\xf0\x90\xd5\x71\xf8\xdb\xf8\x44\ \x89\x43\xcd\x02\x28\x8a\xa2\xf8\x5d\x66\x5a\x12\xf7\xfe\xf8\xeb\ \xc4\xc5\x7a\xac\x09\x40\x70\xdf\xd4\x74\xd7\x06\x6b\x3a\x1f\x38\ \xdb\x27\x00\x00\x7a\x97\x76\x27\x60\xbb\x23\x14\xc3\x11\xee\x86\ \xec\x38\x35\x0b\xa0\x28\x8a\xe2\x4f\x79\x39\xe9\xdc\xfb\x9f\xdf\ \xb0\x72\xf0\xaf\x11\xdd\xf6\x3b\xf3\xdf\x9f\x80\x48\x00\x4a\xc6\ \x88\x66\x21\xe5\x1d\x56\xc7\xe1\x6f\x79\xf1\x12\xa7\x2a\x11\xac\ \x28\x8a\xe2\x17\x25\x85\x79\x7d\x83\x7f\x4c\x94\x65\x31\x48\xe4\ \x37\x8a\x72\x44\x93\x65\x01\x0c\x42\x40\x24\x00\x00\x45\xe9\xce\ \x27\x25\x62\xad\xd5\x71\xf8\x53\x88\x13\xc6\x8e\x52\xb3\x00\x8a\ \xa2\x28\xc3\xb5\xe4\x92\x69\xfc\xea\x07\x5f\x21\x3c\x3c\xd4\xba\ \x20\x84\x78\x6a\x6a\xaa\xf3\x19\xeb\x02\x18\x9c\x80\x49\x00\x00\ \x64\xaf\xf8\x3a\x50\x6f\x75\x1c\xfe\x94\x1b\x2f\x89\x30\xef\x26\ \x4a\x45\x51\x94\x11\xe7\xfa\xe5\x0b\xf9\xfe\x37\xae\xc5\xe9\xb0\ \x74\x4a\xb5\xce\xe5\x10\xb7\x59\x19\xc0\x60\x05\x54\x02\x50\x92\ \x2d\x6a\x04\x72\xb5\xd5\x71\xf8\x93\x43\xc0\xc4\x64\x35\x0b\xa0\ \x28\x8a\x32\x58\x9a\xa6\xf1\xdd\xaf\x7c\x96\xaf\xdd\xb0\x0c\x61\ \xed\xd9\x6a\x29\x91\xab\x27\x25\x89\x5a\x2b\x83\x18\xac\x80\x4a\ \x00\x00\x8a\xd2\x9c\xcf\x0a\xc9\x9f\xac\x8e\xc3\x9f\x52\xa2\x24\ \x49\x91\x2a\x09\x50\x14\x45\x19\xa8\xb0\xb0\x10\xfe\xf7\xdf\x6f\ \xe1\xaa\xcb\x66\x5a\x1d\x0a\x02\x7e\x3f\x35\xcd\xf9\xbc\xd5\x71\ \x0c\x56\xc0\x25\x00\x00\x3e\x4d\xbb\x03\x38\x60\x75\x1c\xfe\x34\ \x29\x45\x1d\x0b\x54\x14\x45\x19\x88\xcc\xd4\x44\xfe\xf4\xd3\xdb\ \x99\x51\x34\xde\xea\x50\x00\xf6\xfb\xfa\x6e\xb1\x0d\x38\x01\x99\ \x00\x94\xa4\x8a\x0e\x5d\x68\xd7\x03\x3d\x56\xc7\xe2\x2f\x91\xee\ \xbe\xfd\x00\x8a\xa2\x28\xca\xb9\xcd\x9d\x3e\x91\x3f\xff\xfc\x76\ \x72\x32\x92\xad\x0e\x05\xa0\x5b\x48\xed\xba\x92\x54\xd1\x61\x75\ \x20\x43\x11\x90\x09\x00\x40\x49\xaa\x28\x45\xf0\x23\xab\xe3\xf0\ \xa7\x71\x09\xea\xb6\x40\x45\x51\x94\xfe\x38\x1c\x1a\x5f\xbb\x61\ \x19\x3f\xfd\xd7\x9b\x89\x08\x0f\xb3\x3a\x9c\x3e\x92\xef\x17\xa5\ \x8b\x3d\x56\x87\x31\x54\x01\x9b\x00\x00\x14\xa5\x68\xbf\x04\x61\ \xfb\x6a\x4b\x03\x25\x04\x94\xa4\xe9\xb8\x02\xfa\x5f\x45\x51\x14\ \xc5\xbf\xe2\x63\xa3\xf9\xed\x7f\x7e\x83\xeb\x97\x2f\xb4\x3a\x94\ \x33\x88\xd7\x8a\xd2\xb4\xbb\xad\x8e\x62\x38\x02\x7a\xa8\x11\x42\ \xe8\x9a\x2e\xbe\x08\x34\x58\x1d\x8b\xbf\x84\xbb\x61\x6a\xba\xba\ \x2c\x48\x51\x14\x05\xa0\x78\x62\x2e\x0f\xfe\xef\x77\x28\x1c\x3f\ \xda\xea\x50\xce\xd4\xe8\x45\xdc\x2c\x84\xd0\xad\x0e\x64\x38\x02\ \x3a\x01\x00\x98\x92\x21\xaa\xa4\x90\xb7\x58\x1d\x87\x3f\x25\x47\ \x49\x72\x55\x81\x20\x45\x51\x82\x98\xdb\xe5\xe2\x6b\x37\x2c\xe3\ \xee\xff\xf7\x55\x4b\x2b\xaa\xe2\xe9\xdb\x00\x00\x1a\x00\x49\x44\ \x41\x54\xfb\xf5\x47\x48\x79\xeb\xf4\x34\x51\x61\x75\x1c\xc3\x15\ \xf0\x09\x00\xc0\xd4\x54\xe7\x1a\x09\x0f\x5b\x1d\x87\x3f\x4d\x48\ \x94\x24\x44\xa8\x24\x40\x51\x94\xe0\x33\x26\x2b\x95\x3f\xfd\xfc\ \xdb\x5c\xbf\x7c\x21\x9a\x66\xaf\x61\x4a\x48\xfe\x54\x94\xee\x7c\ \xd2\xea\x38\xfc\xc1\x69\x75\x00\xfe\x12\xea\xd2\xbe\xd9\xdd\xab\ \x4f\x07\xf2\xad\x8e\xc5\x1f\x84\x80\xe2\x74\xc9\xc6\xc3\x82\x1e\ \x9f\xd5\xd1\x28\x81\x2a\x26\x2e\x1e\x4f\x4c\xac\xd5\x61\x28\xca\ \x80\x38\x1d\x0e\xbe\xb8\xea\x52\xbe\xb4\xea\x52\x1c\x0e\x7b\x0d\ \xfc\x00\x08\xde\xd7\x1c\xda\x9d\x56\x87\xe1\x2f\x23\x26\x01\x28\ \x48\x14\x6d\x7b\x2a\xe5\x0a\x5d\xd3\xb7\x23\x89\xb6\x3a\x1e\x7f\ \x08\x73\xc2\xe4\x54\xc9\x8e\x0a\xb5\x21\x40\x19\x1c\xa7\xcb\xc5\ \xf4\xd9\x0b\x99\x3e\x7b\x11\x4e\xa7\xcb\xea\x70\x14\xe5\x82\xb2\ \x33\x92\xf9\xe1\x6d\xd7\x32\x7e\x4c\xa6\xd5\xa1\x9c\x4b\x93\xf4\ \x69\x2b\x27\xa7\x8a\x76\xab\x03\xf1\x97\x11\x93\x00\x00\x4c\x49\ \x17\x87\x4a\xab\xbc\x5f\xa4\xef\xd2\xa0\x11\x31\x6a\xa6\x79\x24\ \x47\x23\x04\xa7\x46\xcc\x47\x4e\x31\x5a\xee\x84\x42\xe6\x2f\x5e\ \x81\x27\x26\xce\xea\x50\x14\xe5\x82\xdc\x4e\x27\x9f\xbf\x6a\x3e\ \x5f\xbe\xfa\x52\xdc\x2e\xdb\x26\xab\x52\x0a\x79\xd3\xd4\x0c\x71\ \xd8\xea\x40\xfc\x69\x44\x25\x00\x00\xc5\x69\xce\xe7\x4a\x2b\x7d\ \x3f\x47\x10\x10\xf7\x31\x0f\xc4\xc4\x24\x9d\x37\x8f\x6a\xa8\x1d\ \x01\xca\xf9\xc4\xc6\xc5\x33\x7f\xe9\x2a\x46\xe7\x8e\x88\x55\x30\ \x25\x08\x14\x17\x8c\xe5\xce\xd5\xab\xc8\xb6\x47\x51\x9f\xf3\xf9\ \x49\x20\xdd\xf2\x37\x50\x23\x2e\x01\x00\x28\x4a\xd3\x7e\xb8\xbb\ \x5a\x16\x81\x5c\x6c\x75\x2c\xfe\x10\x1b\x06\x31\x61\xd0\xd8\x69\ \x75\x24\x8a\x1d\xb9\x5c\x6e\xa6\xcd\x5e\xc0\x8c\x39\x97\xe2\x70\ \x8c\xc8\x5f\x69\x65\x84\x89\x8f\x8d\xe6\xab\x37\x2c\x63\xf1\xbc\ \x12\xab\x43\x19\x00\xf1\xda\xe1\x54\xf1\x5f\x56\x47\x61\x84\x11\ \xf9\x6d\x21\x84\xd0\xf7\x56\xc8\xeb\xbb\x35\xb9\x13\xc8\xb1\x3a\ \x1e\x7f\x48\x8a\x92\x34\x76\x8e\x88\x55\x0d\xc5\x8f\xc6\xe4\x15\ \xb0\xf0\x8a\xab\xd5\x74\xbf\x12\x10\x1c\x0e\x8d\x95\x97\xcf\xe6\ \x96\x6b\x97\x12\x1e\x16\x62\x75\x38\x03\x51\xa6\x0b\x71\xdd\x35\ \x42\x8c\xc8\xad\xd8\x23\x32\x01\x00\x28\xc8\x10\x0d\xbb\x2b\xe5\ \x4a\x29\xf4\x2d\x80\x4d\xea\x46\x0e\x5d\x6c\xa8\xd5\x11\x28\x76\ \x32\x2a\x31\x99\x85\x4b\x56\x91\x39\x3a\xcf\xea\x50\x14\x65\x40\ \x2e\x2a\x9e\xc0\x6d\x5f\xb8\x32\x10\xa6\xfb\x4f\xeb\x92\x42\x5b\ \x55\x92\x2a\xea\xad\x0e\xc4\x28\x23\x36\x01\x00\x28\x4a\x17\x7b\ \x4a\xab\xbd\xb7\x22\xc5\x5f\xad\x8e\x65\xb8\x3c\xa1\x92\x11\xb2\ \xaf\x51\x19\x06\x97\xdb\xcd\xb4\x59\x6a\xba\x5f\x09\x1c\xf9\xb9\ \x59\x7c\xf5\x86\x65\x14\x17\x8c\xb5\x3a\x94\x41\x91\xc8\xaf\x4f\ \x4d\x15\xbb\xac\x8e\xc3\x48\x23\xfe\x1b\xa4\x38\xd5\xf9\x48\x69\ \xa5\x6f\x16\x82\x5b\xad\x8e\x65\x38\xc2\x5c\x10\xe1\x86\xf6\x11\ \x73\xff\xa1\x32\x58\x63\xf2\x0a\x58\xb8\xec\xb3\x78\xa2\xd5\xb9\ \x7e\xc5\xfe\xb2\xd2\x93\xb8\xf9\x73\x4b\x98\x7f\x51\x21\x22\xf0\ \x6a\x9b\xdf\x3b\x35\xcd\xf9\x90\xd5\x41\x18\x6d\xc4\x27\x00\x00\ \x51\x9d\xda\xb7\xdb\x22\x7c\x53\xa4\x14\x33\xac\x8e\x65\x38\xb2\ \x62\x24\xfb\xea\x02\xee\x17\x49\x19\xa6\xd8\x51\x89\x2c\xba\xe2\ \x6a\xb2\xc6\x8c\xb3\x3a\x14\x45\xb9\xa0\xa4\xf8\x58\xbe\xb4\xea\ \x52\x96\x2d\x9c\x61\xbb\x2a\x7e\x03\x23\xb7\x84\x34\x39\xbe\x63\ \x75\x14\x66\x08\x8a\x04\x20\x37\x57\x74\x6f\xaf\x93\xcb\x9d\xbd\ \xfa\x56\x20\xcb\xea\x78\x86\x6a\x6c\xbc\xa4\xa2\x59\xd0\xda\x6d\ \x75\x24\x8a\x19\xdc\xa1\x61\xcc\x9a\xbf\x84\xa2\xe9\x73\x02\xf4\ \x8b\x54\x09\x26\x69\x49\xf1\x5c\x77\xd5\x7c\x96\x2e\x98\x81\xcb\ \xe9\xb0\x3a\x9c\xa1\x3a\xe6\x72\x3a\x56\x16\x14\x88\xa0\x98\x6b\ \x0d\x8a\x04\x00\x60\x7a\xa2\x38\xb1\xab\x5a\x2e\x11\x52\x7f\x1b\ \x08\xc8\x39\x54\x4d\x40\x71\x9a\xce\xdb\xc7\x35\xbc\x01\x7d\x07\ \x95\x72\x5e\x42\x90\x5f\x58\xc2\x25\x97\x5d\x45\x78\xa4\xbd\x2e\ \x41\x51\x94\x4f\x1a\x93\x95\xca\xb5\x57\xce\xe7\xd2\xd9\xc5\xf6\ \x2c\xdf\x3b\x50\x82\x66\x87\xa6\x5d\x39\x29\x49\xd4\x5a\x1d\x8a\ \x59\x82\x26\x01\x00\x98\x9a\x2a\xf6\x97\xd6\xc8\x15\xe8\xfa\xab\ \x40\x40\x9c\x41\xf9\xa4\xd8\x30\x98\x9e\x21\xd9\x56\x2e\xf0\xa9\ \xca\x40\x23\x4e\x62\x72\x1a\x0b\x96\x5e\x4d\x7a\x96\xad\xae\x3e\ \x55\x94\x4f\x29\x1c\x3f\x9a\xeb\x97\x2f\x60\xe6\xd4\xfc\x40\x5c\ \xe3\x3f\x9b\xa0\x17\x5d\x5f\x35\x39\xd5\xf1\x81\xd5\xa1\x98\x29\ \xa8\x12\x00\x80\xe2\x14\xf1\x46\x69\x95\xf7\x46\x10\x8f\x12\xa0\ \xdb\xea\x13\x23\x25\xd3\x33\x61\x47\xa5\xc0\x3b\x22\x4f\xa7\x06\ \x9f\x90\xd0\x70\xe6\x2c\xba\x82\xc9\x25\x33\x11\x22\x80\x9f\xa2\ \x94\x11\xcd\xe1\xd0\x98\x3d\x6d\x22\xd7\x5e\x39\x9f\x89\x79\xd9\ \x56\x87\xe3\x2f\x12\x5d\xae\x2e\x4e\x77\xad\xb7\x3a\x10\xb3\x05\ \x5d\x02\x00\x50\x9c\xe6\xfc\x47\x69\x95\x6f\x3c\xf0\xff\xac\x8e\ \x65\xa8\x92\x22\x25\x73\x73\x24\xef\x94\x69\x74\xf6\x5a\x1d\x8d\ \x32\x64\xa7\xa7\xfb\x2f\xbf\x8a\xf0\x08\x35\xdd\xaf\xd8\x53\x5c\ \xac\x87\x25\xf3\xa6\xb1\xe2\xf2\x99\x24\x27\x8c\xb0\xa2\x53\x92\ \x1f\x15\xa7\x3b\x03\xfe\xa8\xf8\x50\x04\x65\x02\x00\x50\x94\xaa\ \xfd\x78\x77\x8d\x9e\x8d\xe4\x8b\x56\xc7\x32\x54\x9e\x10\x98\x37\ \x5a\xa7\xb4\x4a\x50\xd7\x16\x90\x93\x19\x41\x2d\x29\x25\x9d\x85\ \x57\x7c\x96\xd4\x8c\x6c\xab\x43\x51\x94\x7e\x8d\x1b\x93\xc1\x67\ \x97\xce\x65\xd1\xec\x22\x9c\x8e\x80\xdd\xd8\x77\x3e\xff\x28\x4a\ \xd3\xfe\xdb\xea\x20\xac\x12\xb4\x09\x80\x10\x42\xee\xdc\x29\x57\ \x6b\x29\x32\x0d\xe4\x42\xab\xe3\x19\xaa\x50\x27\x5c\x9c\x25\x29\ \x6b\x80\xf7\x6b\x05\x3e\xb5\x39\xd0\xf6\x42\xc3\x23\x98\xbb\x68\ \x19\x93\x8a\x2f\x0e\xfc\xb5\x53\x65\xc4\x89\xf1\x44\x72\xd9\x9c\ \x62\xae\xba\x74\x26\x59\xe9\x49\x56\x87\x63\x1c\x29\xdf\x88\xea\ \x74\xdc\x28\x84\x08\xda\xdd\x54\x41\x9b\x00\x00\x94\x94\x88\xde\ \x9d\x47\xe4\x2a\x11\x22\x37\x0b\xc1\x44\xab\xe3\x19\x2a\x01\x64\ \xc7\x49\xe2\x23\x24\xbb\x6b\x34\x75\x75\xb0\x4d\x09\x21\x98\x50\ \x58\xc2\xfc\xc5\xcb\x09\x0b\x8f\xb4\x3a\x1c\x45\xf9\x98\xdb\xe9\ \xa4\x64\x72\x1e\x8b\x2f\x99\xce\x9c\x69\x13\x03\xf9\x18\xdf\x40\ \xed\x73\xfa\x1c\x2b\x72\x73\x45\x50\x1f\xaa\x0e\xea\x04\x00\xa0\ \x64\x8c\x68\xde\x5d\x23\x3f\x23\x75\x7d\x2b\x10\xd0\xe9\x6e\x64\ \x08\xcc\xce\xd6\x29\x6b\x14\xec\xab\x15\xf4\xa8\x0d\x82\xb6\x91\ \x94\x9a\xc1\xa2\x2b\x3e\x4b\x4a\x7a\xc0\x96\xa1\x50\x46\xa0\x71\ \x63\x32\xb8\x7c\x6e\x09\x97\xcf\x99\x4a\xb4\x27\xc2\xea\x70\xcc\ \x72\x02\xa1\x2d\x2d\xcc\x12\x8d\x56\x07\x62\xb5\xa0\x4f\x00\x00\ \x8a\x52\xc4\xf1\x5d\xd5\xf2\x0a\x81\xbe\x1e\x49\xb4\xd5\xf1\x0c\ \x87\x00\xb2\x63\x25\xa9\x1e\xc9\xc1\x93\x82\x63\xa7\x04\x6a\x55\ \xc0\x3a\xe1\x11\x51\xcc\xbd\xf4\x33\x4c\x9c\x32\x1d\xd4\x74\xbf\ \x62\x31\x4d\x13\x4c\x18\x93\xc5\xbc\x19\x93\x98\x3f\x73\x0a\x29\ \x89\x23\x6c\x43\xdf\x85\x35\x09\xa9\x2d\x29\x4a\x13\x65\x56\x07\ \x62\x07\x2a\x01\xf8\xc8\xd4\x54\xb1\xab\xb4\x52\x2e\x41\xe8\xeb\ \x80\x80\x9f\x9f\x75\x3b\x60\x52\xb2\x24\x27\xb6\xaf\x7c\x70\x4d\ \x8b\x20\x68\x17\xba\x2c\xf0\xf1\x74\xff\xe5\x2b\x08\x8b\x08\x9a\ \x27\x2b\xc5\x86\x34\x4d\x63\x62\x5e\x36\x97\x5c\x3c\x99\xf9\x17\ \x4f\x26\x21\x2e\xa0\x9f\x71\x86\xa3\x43\x0a\xed\xca\xe2\x34\xb1\ \xc7\xea\x40\xec\x42\x25\x00\x67\x28\x4e\x17\xef\x94\x56\xf6\x2e\ \x47\x68\x2f\x00\x23\xe2\x02\xde\xc8\x90\xbe\xc2\x41\x2d\x5d\x7d\ \x33\x02\xd5\x2a\x11\x30\x5c\x7a\xd6\x68\x16\x5e\x71\x35\x09\x49\ \x69\x56\x87\xa2\x04\xa9\x88\xf0\x30\x4a\x0a\x73\x99\x33\x6d\x12\ \xb3\x4a\xf2\x89\x8a\x08\xb7\x3a\x24\xab\x75\x6a\x52\x5f\x36\x25\ \xcd\xf1\x96\xd5\x81\xd8\x89\x4a\x00\x3e\xa1\x38\xdd\xb5\x7e\x57\ \x75\xef\x72\x21\xb5\x67\x09\xd0\x6a\x81\xfd\xf1\x84\xc2\xb4\x0c\ \x49\x43\x87\xe4\xc0\x49\xc1\xc9\x36\x95\x08\xf8\x5b\x44\x94\x87\ \x79\x8b\xae\x24\x7f\x72\x89\x9a\xee\x57\x4c\xa5\x69\x82\xdc\x9c\ \x74\x4a\x26\xe5\x31\xad\x30\x8f\xc9\xf9\x63\x82\x61\x23\xdf\xc0\ \x08\x7a\xa5\x2e\xaf\x99\x92\xee\xda\x68\x75\x28\x76\xa3\x12\x80\ \x7e\x4c\x4d\x75\xbd\xba\xab\xda\x7b\x9d\x90\xe2\x71\x46\xd8\x7f\ \xa3\xb8\x70\x98\x99\x25\x69\xed\x96\x94\x37\x0a\xaa\x5a\x04\x1d\ \xaa\x90\xd0\xb0\x68\x0e\x07\xc5\xd3\xe7\x30\x73\xfe\x12\xdc\x21\ \x23\x62\xe2\x48\x09\x00\x29\x89\x71\x14\x15\x8c\x65\xc6\x94\x09\ \x4c\x2b\xcc\xc5\x13\xa5\x96\x9a\xfa\xe1\x43\xca\x2f\x4e\x4d\x77\ \xbe\x60\x75\x20\x76\x34\xa2\x06\x37\x7f\x9a\x9a\xea\x5c\xb3\xbb\ \xd2\xbb\x5a\x0a\xf1\x20\x30\xe2\x6a\xb3\x46\x85\x40\x41\xb2\x24\ \x3f\x59\xd2\xd4\x09\x95\xcd\x82\xca\x66\x41\xb7\xd7\xea\xc8\x02\ \x4b\x46\xf6\x58\x16\x2e\x5d\x45\x7c\x52\xaa\xd5\xa1\x28\x23\x5c\ \x6a\xd2\x28\x26\x8d\xcf\x61\xf2\xf8\xd1\x94\x14\xe6\x91\x9a\x34\ \xca\xea\x90\xec\x4e\x0a\x21\xbf\x5a\x94\xea\x7c\xcc\xea\x40\xec\ \x4a\x25\x00\xe7\x51\x94\xee\xfc\xcb\xae\x2a\x5f\xa4\x80\x7b\xad\ \x8e\xc5\x28\x82\xbe\x0b\x86\x62\xc3\x24\x05\x49\x7d\xb3\x02\x07\ \xeb\x85\x2a\x2f\x7c\x01\x91\x9e\x68\x2e\xb9\x7c\x39\xe3\x27\x16\ \x5b\x1d\x8a\x32\x02\x85\x87\x87\x32\x3e\x27\x9d\xf1\xb9\x99\x14\ \x8e\x1b\x4d\xe1\x84\x1c\x3c\x91\x41\xbf\x8e\x3f\x18\x52\xc0\x6d\ \x45\xa9\xce\xfb\xad\x0e\xc4\xce\x54\x02\x70\x01\x53\xd3\x1c\xbf\ \xdb\x55\xed\x73\x09\xc9\x5d\x56\xc7\x62\x34\x4d\xf4\x15\x14\xca\ \x88\xe9\xdb\x27\x70\xf8\x94\x40\xaa\x8d\x02\x67\xd1\x1c\x0e\xa6\ \x94\xcc\x66\xf6\xc2\xa5\x6a\xba\x5f\xf1\x0b\xa7\xc3\xc1\x98\xec\ \x54\x0a\xc7\xe5\x30\x6e\x4c\x06\xe3\x46\xa7\x93\x95\x96\x84\xa6\ \xa9\x7d\x24\xc3\xf0\xef\x45\x69\x8e\xdf\x5b\x1d\x84\xdd\xa9\x04\ \x60\x00\xa6\xa6\x3a\xee\x2e\xad\xf6\x8d\x42\xf2\x43\xab\x63\x31\ \x83\x43\x83\x82\xa4\xbe\x5a\x02\xa5\x55\x1a\xad\x41\x5d\x2b\xeb\ \x9f\x32\xb2\x73\x59\xb4\xec\x6a\x46\x25\x24\x5b\x1d\x8a\x12\x80\ \x1c\x0e\x8d\xa4\xf8\x58\xb2\xd3\x93\xc8\xc9\x48\x21\x27\x23\x99\ \x9c\xf4\x24\x72\xb2\x52\x70\x3b\xd5\x57\xb1\xbf\x48\xf8\xcf\xa9\ \x69\x8e\x5f\x58\x1d\x47\x20\x50\x9f\xba\x01\x2a\x4e\x75\xfc\x47\ \x69\x95\xaf\x0d\xf8\xb9\xd5\xb1\x98\x25\x36\x0c\x2e\x19\xa3\xf3\ \x7e\x8d\xe0\x78\x63\xf0\x3e\x8d\x78\xa2\x63\x99\xb7\x78\x39\xe3\ \xf2\xa7\x58\x1d\x8a\x12\x00\xa2\x3d\x11\xa4\x27\x25\x90\x9e\x1a\ \x4f\x46\x4a\x02\x69\xc9\xf1\x64\xa5\x25\x92\x9d\x91\xac\x06\x7a\ \xe3\xfd\x62\x6a\x9a\xe3\xc7\x56\x07\x11\x28\x82\xf7\x5b\x7d\x88\ \x76\x55\xf9\xfe\x55\x40\xd0\x65\x97\xe5\x4d\x82\x77\x6b\x02\xe3\ \xb2\xa1\xb2\x23\x07\x79\xf2\xaf\xc3\x9f\xfd\x73\x38\x9c\x94\xcc\ \x9a\xcf\x45\x73\x2f\xc3\xe5\x72\xfb\x21\xb2\xc0\xa1\xeb\x3a\x4d\ \xa7\x4e\x72\xaa\xbe\x96\xc6\xfa\x3a\x4e\xd5\xd7\xd2\x74\xea\x24\ \x2d\xcd\x8d\xb4\xb7\xb5\xa0\xeb\x01\xf0\x41\x30\x48\xb4\x27\x82\ \xa4\x51\xb1\x24\xc6\x47\x93\x9c\x10\x47\xe2\xa8\x18\x12\xe3\x63\ \x49\x4b\x1a\x45\x46\x6a\x02\x91\xe1\x61\x56\x87\x18\x9c\x24\xff\ \xaf\x38\xdd\xf1\x13\xab\xc3\x08\x24\x2a\x1d\x1d\xa4\xa9\x69\x8e\ \x5f\x96\x56\xfa\x5a\x10\xfc\x8e\x11\x78\x3a\xe0\x5c\x32\x63\x24\ \x31\x61\x92\x77\xca\xb4\xa0\xd8\x20\x98\x93\x9b\xcf\x82\x25\x2b\ \x89\x1d\x95\x60\x75\x28\x96\xd0\x34\x8d\xb8\x84\x24\xe2\x12\x3e\ \x7d\x3d\x86\x94\x92\xf6\xb6\x16\x5a\x9b\x9b\x68\x6b\x6d\xa6\xb5\ \xa5\x91\xd6\xe6\x66\xda\xdb\x5a\xe8\xee\xec\xa0\xab\xb3\x83\xce\ \xae\x0e\xba\xbb\x3a\xe9\xea\xe8\xc0\xe7\xb3\xf7\xd1\x12\x21\x34\ \x42\xc3\xc3\x08\x0d\x0d\x27\x34\x2c\x9c\xf0\xf0\x48\xc2\x23\xa3\ \x88\x88\x8c\x26\x35\x3e\x82\x29\xd9\x51\xc4\xc5\xf4\xfd\x2f\x29\ \x3e\x96\x10\xb7\xcb\xea\x90\x95\xb3\x49\x21\xb8\xa3\x28\xcd\xf1\ \x1b\xab\x03\x09\x34\x2a\x01\x18\x82\xe2\x74\xc7\x1f\x77\x57\x7b\ \x5b\xa5\x14\x0f\x13\x44\xff\x0d\x3d\x21\x30\x6f\xb4\xce\x3b\x65\ \x1a\xcd\x5d\x56\x47\x63\x8c\xe8\xd8\x51\xcc\x5f\xbc\x82\xb1\xe3\ \x27\x59\x1d\x8a\x6d\x09\x21\x88\x8c\x8a\x26\x32\x6a\x60\x25\x65\ \x7b\x7b\x7a\xe8\xea\xea\xa0\xbb\xb3\x83\xce\xce\x0e\xba\xba\x3a\ \xe9\xed\xee\xc2\xeb\xed\xa5\xa7\xbb\x1b\x5d\xf7\xd1\xd5\xd9\x89\ \xae\xfb\xe8\xe9\x3e\xbd\xe1\x44\xd2\xdd\xd5\xf9\x71\x1b\xba\xcf\ \x87\x4f\xd7\x71\xb9\xce\x1e\x7c\xdd\xa1\x61\x88\x33\x26\x32\x1d\ \x4e\x07\x2e\xd7\x47\xf5\xbb\x04\x84\x86\x86\xe1\x74\xba\x71\xb9\ \x5c\xb8\x43\xc3\x70\xb9\xfb\xfe\x1c\x12\x1a\x4e\x68\x68\x18\xa1\ \x61\xe1\xe7\xdc\xcc\x29\x80\x39\xa3\x75\xe2\xd4\x03\xbd\x9d\xf9\ \x24\xf2\x96\xe2\x54\xe7\x43\x56\x07\x12\x88\x82\x66\xf0\xf2\xb7\ \xa2\x54\xe7\xa3\xbb\x2a\xbd\xbd\x42\x13\x7f\x43\x12\x34\x8f\x04\ \xa1\x4e\x98\x93\xad\xb3\xad\x42\x70\xb2\x7d\xe4\xac\x20\x39\x1c\ \x0e\x26\x97\xcc\x66\xf6\xa2\x2b\x70\xbb\x47\x4c\x01\x48\x5b\x70\ \xb9\xdd\xb8\xdc\x6e\xa2\x3c\x31\x56\x87\x32\x28\x29\x1e\xa9\x06\ \x7f\x7b\xeb\x41\xca\xeb\xa7\xa6\x3b\x9f\xb2\x3a\x90\x40\xa5\x12\ \x80\x61\x98\x9a\xee\x7c\x62\x77\xa5\xb7\x5d\x0a\xf1\x24\x10\x34\ \x5f\x15\x4e\x07\x5c\x9c\x25\xd9\x5e\x01\x27\x5a\x03\x3f\x09\x18\ \x93\x57\xc0\xfc\xa5\xab\x88\x89\x55\x85\x55\x94\x3e\x1a\x90\x9f\ \xa4\xce\xc0\xda\x58\xb7\xf8\xff\xed\xdd\x6d\x70\x5c\xd5\x79\x07\ \xf0\xff\x73\xee\xae\x24\xdb\xb2\x5e\x2d\x59\xda\xd5\xe2\x37\x6c\ \x6c\x64\x90\x57\xf2\x2b\xd8\x99\x18\x28\xae\x71\x9a\x74\x02\x69\ \x9b\x69\x93\x69\x87\xbc\x4d\x69\x53\x32\xd3\x4e\x68\x3f\x04\xdb\ \x9d\x29\xc3\x4c\x98\x14\xe7\xa5\x81\xa4\x69\x52\xd3\x21\x75\x20\ \x0d\x31\x34\x38\x09\x38\x30\x40\xec\xc1\x92\x4d\x8c\x0c\x06\x1b\ \xc7\xb5\x91\x8d\x85\x25\x59\x96\xb4\xd2\xee\xde\xf3\xf4\x83\x70\ \x6b\xc2\x9b\x25\xed\xee\xd9\x7b\xf7\xff\x9b\xf1\x17\x8d\xb4\xfa\ \x5b\x7b\xf6\x9c\xe7\x9e\x7b\xee\x39\xd0\x3f\x4e\xb6\x44\x1e\x71\ \x1d\x24\xc8\x58\x00\x4c\x51\xb2\x25\xf2\x58\xd7\x29\xdd\xa8\xd6\ \xee\x14\x60\xa6\xeb\x3c\x85\x62\x64\xfc\x6c\x81\x67\x8f\x09\xfa\ \x52\x1f\xfc\xfd\xc5\xa8\xa6\x6e\x16\xd6\x6f\xfc\x38\x16\x2c\x6a\ \x75\x1d\x85\x8a\x4c\x73\x95\xa2\xb2\xb4\xd6\x7d\x06\xc9\x30\x60\ \xff\x30\x19\x8f\xfe\xd2\x75\x90\xa0\x63\x01\x90\x03\xed\xcd\xf2\ \xd4\xfe\x53\x7a\xbd\x5a\xfb\x38\x80\x92\x39\x60\xdb\x13\x60\x45\ \xc2\xe2\xc9\x23\x06\x99\x00\x2d\x0a\x8f\x44\xa3\x58\xb9\xf6\x7a\ \xac\x5c\x7b\x03\x22\x91\x92\xb9\x7b\x43\x13\x30\xbb\x64\x4a\xf9\ \xc0\x19\x10\x98\x4d\xc9\xb8\xf7\x9c\xeb\x20\x61\xc0\x02\x20\x47\ \x92\xcd\xf2\x7c\x67\x8f\xde\x28\x6a\x1f\x03\xf0\xce\xa5\xd3\x21\ \x35\x2d\x0a\x2c\xa8\x1f\xdf\x39\x30\x08\x16\x2c\x6a\xc5\x75\x9b\ \x6e\x41\x75\x4d\xc9\xd4\x69\x34\x09\x65\xec\x19\x8b\xd1\x69\x03\ \x73\xd3\xb2\xb8\xec\x77\x1d\x24\x2c\xd8\xcc\x73\xa8\x23\x26\x9d\ \x2f\x9c\xd6\x35\xbe\x6f\x1f\x03\xb0\xc4\x75\x9e\x42\x99\x5b\xa7\ \x38\xfc\x66\x71\x6f\x1b\x5c\x5b\xdf\x80\xeb\x36\x7e\x1c\xf3\x16\ \x5e\xe9\x3a\x0a\x05\x40\xd6\x77\x9d\x80\x7e\x47\x37\xc4\x6c\x5a\ \x16\x93\xe3\xae\x83\x84\x09\x0b\x80\x1c\x6b\x6b\x92\x63\xbf\x39\ \xae\xd7\x66\xa2\xfa\xb0\xa8\xae\x77\x9d\xa7\x10\x2a\x22\xe3\xc7\ \x0c\x9f\x1d\x76\x9d\xe4\x9d\xa2\xd1\x32\xac\x58\x7b\x1d\x56\xad\ \xfb\x3d\x78\x1e\x9b\x3b\x5d\x9a\xa1\xb4\xeb\x04\xf4\xff\xe4\x49\ \x49\xcb\xcd\xc9\x79\x32\xe0\x3a\x49\xd8\xb0\x47\xcc\x83\xab\xe7\ \x48\x7f\x77\xb7\xfe\xfe\x58\xad\x7e\x07\x8a\x4f\xbb\xce\x53\x08\ \x0d\xd3\x15\x67\x8b\xec\xb1\xc0\x05\x8b\x5a\x71\xfd\xa6\x5b\x50\ \xc5\xe9\x7e\x9a\xa0\x61\x16\x00\x45\x41\x81\xef\x57\x0c\xc8\xe7\ \x5b\x5b\x85\xef\x48\x1e\x14\x57\x8f\x1d\x32\xaa\x2a\x5d\x3d\xf6\ \x4e\x01\xbe\x82\x90\xff\xad\x7b\x87\x05\xcf\xfe\xb6\x38\xfe\x8b\ \x6f\x9e\x39\x85\x91\xe1\xf3\xb8\x6c\xde\x22\xd7\x51\x28\xa0\xe2\ \x55\x8a\x15\x89\x22\xbe\xa7\x15\x7e\xaa\xc0\xd6\xf6\x98\xd9\x22\ \x22\x7c\x23\xf2\x84\x33\x00\x79\xf4\x56\xc3\xdd\xbc\xff\x64\xf6\ \x98\x8a\xdc\x0f\x20\xb4\x0f\x16\xd5\x4f\x57\x18\x11\xd8\x22\xf8\ \xa8\xce\x6a\x6c\x06\xd0\xec\x3a\x06\x05\x58\x75\xc9\xec\xea\x51\ \x94\xd2\x10\xfd\x4c\x47\x2c\xb2\xdd\x75\x90\xb0\x2b\x99\xbd\xec\ \x5d\x4a\xb6\x44\x7e\xa0\x6a\x37\x02\x08\xed\x3d\x2c\x23\x40\xf5\ \xbb\xef\xa8\x4a\x14\x28\x22\x40\xac\xaa\x08\x2a\xd9\xd2\xd4\x2f\ \xd6\x6e\x68\xe7\xe0\x5f\x10\x2c\x00\x0a\xa4\xa3\x25\xfa\x24\x8c\ \xb9\x16\xc0\x6f\x5d\x67\xc9\x97\xea\x0a\x76\x9a\x14\x7c\x31\x6e\ \x02\xe4\xca\x31\x15\x73\x6d\x32\x11\xfd\x95\xeb\x20\xa5\x82\x05\ \x40\x01\xb5\x37\xcb\xa1\x6c\xd4\xac\x01\x74\x8f\xeb\x2c\xf9\x50\ \xc5\x19\x00\x0a\x38\x03\x60\x49\x03\x0b\xd9\xc2\xd3\xe7\x3c\xcf\ \xac\xee\x88\xc9\x4b\xae\x93\x94\x12\x16\x00\x05\xb6\xb2\x51\x4e\ \x9f\x8b\x79\xeb\x00\xdc\xed\x3a\x4b\xae\x55\x97\xb3\xe3\xa4\x60\ \x9b\x57\xaf\xa8\xe4\x59\x50\x05\x25\x8a\xfb\xcb\x07\xbc\xf5\x6d\ \x4d\x72\xc6\x75\x96\x52\xc3\x45\x80\x0e\xac\x17\xc9\x02\xb8\xa3\ \xb3\x27\xfb\xa2\xa8\xdc\x07\x60\xba\xeb\x4c\xb9\x50\x35\x6d\xfc\ \x51\x07\x96\x01\x14\x44\x65\x1e\x70\x05\xaf\xfe\x0b\x69\x14\xd0\ \xdb\x92\x2d\x91\xef\xb9\x0e\x52\xaa\x38\x03\xe0\x50\x47\x2c\xf2\ \x80\xa8\xb9\x16\xc0\x31\xd7\x59\x72\x21\x6a\xc6\xb7\x06\x26\x0a\ \xa2\x25\xb3\x15\x65\x9e\xeb\x14\x25\xe3\x7f\xc4\x98\x0f\xb5\xc7\ \x39\xf8\xbb\xc4\x02\xc0\xb1\x64\x8b\x1c\xc8\xa8\x59\x01\xc8\x2e\ \xd7\x59\x72\xa1\x8a\x0b\x01\x29\x80\x66\x94\x01\x73\x6b\xd8\x76\ \x0b\x42\xe5\x67\xe5\xd6\x24\x93\xcd\xf2\xbc\xeb\x28\xa5\x8e\x05\ \x40\x11\x58\xd5\x22\x67\x93\x31\xd9\x08\xe0\x0e\x00\x01\x3a\x57\ \xef\x9d\xf8\x28\x20\x05\xd1\xa2\x59\x0a\x29\x8e\x7d\xac\xc2\x4c\ \x01\xdc\x9d\x8c\xcb\x47\x5a\x13\xd2\xe7\x3a\x0c\xb1\x00\x28\x1a\ \x22\xa2\xed\x71\xef\x6e\x55\xfd\x18\x02\xbc\x5f\xc0\x74\x3e\x3e\ \x45\x01\x33\x2d\x02\x24\x6a\x79\xf5\x9f\x67\x83\x2a\x7a\x73\x7b\ \xdc\xbb\x43\x44\x02\x7d\x91\x13\x26\x2c\x00\x8a\x4c\x47\x4b\xe4\ \x51\xb1\x66\xa5\x2a\x5e\x74\x9d\x65\x32\x2a\xa3\xec\x48\x29\x58\ \xe6\xd6\x29\x3b\xc2\xfc\x7a\x59\xc5\xac\xee\x88\x45\xfe\xcb\x75\ \x10\x7a\x3b\xb6\xfb\x22\x94\x4c\xc8\xab\x15\x65\x66\x0d\x80\xc0\ \x2d\x90\xe1\x39\xea\x14\x24\x46\x80\xb9\xbc\xfa\xcf\x1b\x05\xbe\ \xe3\x79\x66\x39\x9f\xef\x2f\x4e\x2c\x00\x8a\x54\x6b\xa3\x0c\xb5\ \xc7\xbd\x5b\x55\xf4\x66\x00\x67\x5d\xe7\xb9\x54\x5c\x45\x4d\x41\ \x12\xaf\x56\x94\xb3\x68\xcd\x87\x01\x40\x3f\xd9\x11\xf7\x3e\xd7\ \xd6\x24\x45\x78\x50\x38\x01\x2c\x00\x8a\x5e\x47\x2c\xf2\xe3\x6c\ \xd4\x2c\x05\xe4\x71\xd7\x59\x2e\x05\x17\x52\x51\x90\xcc\xaf\xe3\ \xd5\x7f\xee\xc9\x13\xc6\x9a\xa5\xed\xf1\xc8\x0f\x5d\x27\xa1\xf7\ \xc7\x02\x20\x00\x56\x36\xca\xe9\x64\x4c\x6e\x12\xc1\xed\x00\xc6\ \x5c\xe7\x21\x0a\x83\xba\xe9\x40\x2d\x4f\xfd\xcb\x1d\x41\x46\x81\ \x2d\xc9\x98\xdc\xb8\x2c\x21\xaf\xbb\x8e\x43\x1f\x8c\x05\x40\x40\ \x88\x88\x26\x63\xde\xbd\x9e\x67\x96\x43\x70\xd0\x75\x1e\xa2\xa0\ \x5b\x50\xcf\xab\xff\x1c\x7a\xc9\xa8\x59\xd5\x11\xf7\x36\x73\x95\ \x7f\x70\xb0\x00\x08\x98\xb6\x26\x79\x71\xd4\x37\xab\x00\x6c\x03\ \x77\xdd\x25\x9a\x94\xda\x69\x3c\xf2\x37\x67\x14\xdb\x3d\xcf\xac\ \x58\x16\x97\xfd\xae\xa3\xd0\xc4\xf0\x8e\x6d\x80\x75\xf6\x64\x36\ \x88\x9a\xef\x03\x68\x72\x9d\xe5\x82\xd1\x2c\xf0\xf8\x61\xd6\x95\ \x54\xbc\x0c\x80\x75\xf3\x2d\xa7\xff\xa7\xae\x57\xa1\xb7\x76\xc4\ \x23\x3b\x5d\x07\xa1\xc9\x61\x4f\x1d\x60\x1d\xb1\xe8\x2e\x9b\x31\ \xed\x80\xfc\xd4\x75\x96\x0b\x7c\x5e\x54\x51\x91\x5b\xda\xac\x1c\ \xfc\xa7\x4a\xe5\xe1\x68\xc4\x5c\xc5\xc1\x3f\xd8\x38\x03\x10\x12\ \xfb\x4f\x66\x3f\xa1\x22\xdf\x02\x30\xcb\x65\x8e\xc1\x51\xe0\xc9\ \xa3\xac\x2b\xa9\xf8\x08\xc6\x4f\xfb\x5b\xdc\xc8\x2a\x75\x0a\x4e\ \x43\xf5\xaf\xdb\x5b\x22\x0f\xb9\x0e\x42\x53\xc7\x9e\x3a\x24\x92\ \x2d\x91\x1f\x79\x9e\x69\x85\x62\xbb\xcb\x1c\x9c\x01\xa0\x62\x34\ \x2d\x02\x2c\x4f\x70\xf0\x9f\x02\x85\x62\x7b\xb9\x35\xad\x1c\xfc\ \xc3\x83\x33\x00\x21\xd4\x75\x32\x7b\x0b\x44\xbe\x0e\x07\x6b\x03\ \xfa\x53\xc0\x53\xaf\xb1\xae\x24\xf7\x44\x80\xda\x0a\xa0\xa5\x46\ \x31\xb7\x56\x61\xd8\xdb\x4d\x8a\x02\x87\x45\xcc\xe7\xda\x63\xf2\ \xb4\xeb\x2c\x94\x5b\xfc\x48\x84\xd4\xbe\xa3\x5a\x6d\x2a\xec\x56\ \x00\xb7\x01\x28\xd8\xfe\x7c\xe7\x52\xc0\x6e\x16\x00\x94\x67\x82\ \xf1\x83\xa7\x2a\xa2\x40\x85\x37\xbe\x9b\xdf\x85\x7f\x15\x11\xa0\ \x22\xa2\x98\x59\x01\x78\xec\xe1\xa6\x22\x0b\xe0\x9e\x73\x69\xb3\ \x79\xfd\x3c\x19\x75\x1d\x86\x72\x8f\x1f\x8f\x90\xdb\xd7\xa3\xed\ \x46\xed\xfd\x00\x3a\x0a\xf1\xfb\xce\x8d\x02\xbb\xb9\x06\x80\xf2\ \xc0\x08\x90\xa8\x51\x34\x57\x01\xf5\xd3\x14\x51\x6e\x3b\x9d\x37\ \x0a\xec\x87\x98\xcf\x76\xc4\xa4\xd3\x75\x16\xca\x1f\xf6\xd4\x21\ \xb7\x3c\x26\x5d\xf6\x94\x59\x03\xe0\x0e\x00\x23\xf9\xfe\x7d\xdc\ \x0a\x98\x72\x4d\x64\xfc\xc4\xbe\x0d\x8b\x2c\x92\x31\x45\x53\x25\ \x07\xff\x3c\x1a\x56\xc1\x97\x8e\xc6\xcc\x0a\x0e\xfe\xe1\xc7\xee\ \xba\x84\x1c\x38\xa1\x71\x2b\xf6\x2e\x08\x3e\x95\xaf\xdf\x31\x92\ \x01\x7e\xfe\x0a\xeb\x4a\xca\x8d\xa6\x4a\xc5\x95\xb3\x15\x55\x15\ \xae\x93\x84\x9e\x42\xe4\x21\x40\xfe\xae\x3d\x26\xc7\x5d\x87\xa1\ \xc2\x60\x01\x50\x82\x0e\x9c\xcc\xac\xf7\x61\xb6\x89\x60\x69\xae\ \x5f\xdb\x02\xd8\x79\xc8\x40\xb9\xd8\x9a\xa6\xa0\x7e\x06\xb0\xb8\ \x41\xd1\x30\x83\x0d\x29\xff\x74\x9f\xc0\xbb\x3d\x19\x97\x67\x5d\ \x27\xa1\xc2\x62\x01\x50\xa2\x76\xab\x46\x6a\x4e\xd9\xdb\x14\xd8\ \x02\x45\x75\x2e\x5f\x7b\xd7\x2b\x06\xa9\x4c\x2e\x5f\x91\x4a\x81\ \x00\x98\x5d\xa9\x58\xd4\xa0\xa8\x9b\xee\x3a\x4d\x49\xe8\x51\xc1\ \x96\xf6\x66\xf3\x5d\xee\xdf\x5f\x9a\x58\x00\x94\xb8\x83\x6f\xe8\ \xec\x6c\xc6\x6e\x55\xc1\xad\xc8\xd1\xd3\x02\xcf\x1d\x17\x9c\x19\ \x62\xd3\xa2\x4b\x23\x18\xdf\x97\x7f\x51\x83\xa2\x9a\x53\xfd\x85\ \x30\x2a\x82\x7b\x8c\x31\x77\xb5\x35\xc9\xb0\xeb\x30\xe4\x0e\x7b\ \x69\x02\x00\xec\xeb\xd1\xc5\x06\xba\x15\xaa\x9f\x98\xea\x6b\x1d\ \x3c\x2d\x38\x7a\x96\x4d\x8b\x3e\x58\xe3\x5b\xf7\xf8\x6b\x38\xf0\ \x17\x84\x40\x1e\x35\x9e\x7c\xb1\xad\x49\x8e\xb9\xce\x42\xee\xb1\ \x97\xa6\xb7\xe9\x7a\x3d\x73\x03\x60\xbe\x0a\xa0\x6d\xb2\xaf\x71\ \xbc\x5f\xb0\xbf\x87\x4d\x8b\xde\xdb\xf4\x32\xa0\xad\x49\x31\x7b\ \x26\xef\xf1\x17\x82\x02\xfb\x45\xcc\xed\xdc\xcc\x87\x2e\xc6\x5e\ \x9a\xde\x61\x87\xaa\xb7\xa0\xc7\xff\xb4\x40\xee\x04\x30\x67\xa2\ \x3f\xdf\x37\x02\x3c\x7d\x8c\x4f\x02\xd0\x3b\x09\x80\xf9\xf5\x8a\ \x2b\x1b\x15\x1e\x9b\x48\x21\x1c\x83\xea\xe6\x64\xdc\x7b\x80\xf7\ \xf9\xe9\x77\xb1\x00\xa0\xf7\xd4\xdd\xad\x65\xa3\xb5\xf6\xcf\x45\ \xb1\x05\x13\xd8\x56\x38\xed\x03\xff\xfd\x32\x7b\x77\x7a\xbb\x32\ \x0f\x68\x8f\x2b\x9a\x78\xd5\x5f\x08\x6f\x02\xf8\xea\xb9\xb4\xb9\ \x97\xbb\xf8\xd1\x7b\x61\x01\x40\x1f\xe8\x85\xd3\x3a\xc3\xf7\xed\ \x5f\x61\x7c\x33\xa1\x9a\x4b\xf9\x99\x9d\x87\x0c\x0f\x06\xa2\xff\ \x53\x11\x01\xae\x99\x6b\x51\x55\xee\x3a\x49\xb8\x29\x70\x5e\x80\ \x6f\xa5\x2b\xcc\x3f\xad\xae\x97\x41\xd7\x79\xa8\xb8\xb1\x00\xa0\ \x4b\xf6\x4c\xaf\xce\x9c\x9e\xb6\x7f\x89\x4b\x28\x04\x76\x1d\x36\ \x48\x65\x0b\x93\x8b\x8a\x5b\x45\x04\x58\x3b\xcf\xa2\xb2\xcc\x75\ \x92\xf0\xba\x30\xf0\x47\xb2\xe6\xee\xab\xe7\x48\xbf\xeb\x3c\x14\ \x0c\x2c\x00\x68\xc2\x2e\x2a\x04\xbe\x0c\xa0\xf6\xdd\xbe\xe7\xf1\ \xc3\x06\xa3\x2c\x00\x4a\x9e\x08\xb0\xe6\x32\x45\x63\x25\xa7\x83\ \xf2\x81\x03\x3f\x4d\x05\x0b\x00\x9a\xb4\xfd\xc7\xb4\x06\x65\xf6\ \x6f\x14\xf8\x22\x80\xba\x0b\x5f\x3f\x33\x24\x78\xee\x38\x9b\x16\ \x01\x0b\xea\x15\x57\x35\x71\xf0\xcf\x83\xb3\x50\xdc\x6b\xc7\xcc\ \xb6\xe5\x0b\xe4\x9c\xeb\x30\x14\x4c\xec\xa5\x69\xca\x76\x1f\xd3\ \x8a\xea\xa8\xff\x47\x2a\xf2\x0f\x02\x5c\xf1\x9b\x53\x82\xd7\xfa\ \xd8\xb4\x4a\x9d\x27\xc0\x8d\x8b\x2c\xca\x23\xae\x93\x84\xca\x69\ \x05\xee\xd3\x51\xf3\x35\x0e\xfc\x34\x55\xec\xa5\x29\x67\x54\xd5\ \x74\xf5\xf8\x9b\x0e\x9d\x31\x5f\x7f\xb5\x57\x26\xfc\xf8\x20\x85\ \x4b\x53\xa5\x62\xf5\x1c\x5e\xfd\xe7\xc8\x11\x11\x7c\xa3\x72\xd8\ \x7c\x7b\xe1\x42\x19\x73\x1d\x86\xc2\x81\x05\x00\xe5\xc5\xbf\x77\ \xe9\xad\x83\x29\xbd\xb3\x77\x44\x12\x3c\x18\xa8\x34\x2d\x6e\x54\ \x2c\x6e\xe0\x9b\x3f\x15\x2a\xb2\x1b\x6a\xbf\xd6\x1e\xf3\x1e\x15\ \x11\xfe\x31\x29\xa7\x58\x00\x50\x5e\xfd\xdb\x41\xfd\x70\x6a\x08\ \xff\xdc\x3b\xa4\x6d\xbe\xb2\xb9\x95\x92\xe5\x2d\x8a\x96\x6a\x8e\ \x59\x93\x90\x86\xc8\x23\xd6\x97\x7b\x96\x27\x64\xaf\xeb\x30\x14\ \x5e\xec\x91\xa9\x20\x76\x74\xeb\xe5\x83\x43\xf8\xe6\x9b\x23\xb8\ \x7e\x24\x93\x9b\x43\x87\xa8\xb8\xad\x4c\x28\x62\x55\x2c\x00\x26\ \xe0\x0d\x05\xbe\x5d\x16\x31\xff\x72\xd5\x6c\x79\xc3\x75\x18\x0a\ \x3f\x16\x00\x54\x50\xbb\x77\x6b\xc5\x89\x99\xf6\x2b\x7d\xa3\xf2\ \xd9\xfe\x94\xcc\xe2\xed\x81\xf0\xea\x88\x2b\x12\x35\x7c\x83\x2f\ \x41\xa7\x0a\xee\x1f\xf3\xcd\xf6\x6b\x12\x92\x72\x1d\x86\x4a\x07\ \x0b\x00\x72\xe6\x27\x2f\xe8\xba\x33\x29\xbd\xeb\xcc\xb0\xac\x49\ \xfb\xe0\xde\xc1\x21\xb3\xa4\x51\x71\x05\xd7\x00\xbc\x3b\xc1\x39\ \xb1\xf8\x4f\x5f\xcd\x37\x96\x27\xe4\xa0\xeb\x38\x54\x9a\x58\x00\ \x90\x73\x3b\xba\xb5\x2e\x95\xb2\xff\x38\x30\x2a\x9f\xec\x4b\x49\ \x2d\x67\x05\xc2\x21\x51\xad\xe8\x68\xe1\x9b\x79\x11\x85\xea\xd3\ \x00\xbe\x67\x8d\xf7\xd0\xf2\x98\x8c\xb8\x0e\x44\xa5\x8d\x05\x00\ \x15\x95\x07\xbb\x74\xf9\xf9\x2c\xb6\xf6\x8f\xe0\x86\xe1\x34\xa2\ \xae\xf3\xd0\xe4\x55\x96\x01\x37\x2c\xe4\x01\x74\x00\x5e\x07\xf0\ \x80\x5a\xf3\xdd\x8e\x84\x1c\x71\x1d\x86\xe8\x02\x16\x00\x54\x94\ \x76\xa8\x7a\x63\x9d\xf6\x4b\xe7\xd2\xf2\x85\xfe\x94\xcc\xcf\xf8\ \x6c\xab\x41\x23\x00\x6e\x5a\x62\x11\x2d\xc1\x9b\x3b\x0a\x9c\x17\ \xc5\x4f\x44\xec\xf6\x65\xb1\xc8\x13\x3c\x8a\x97\x8a\x11\x3b\x55\ \x2a\x7a\xf7\xed\xd3\xea\xa8\xb5\x7f\x3b\x92\x35\x9f\xea\x1b\xc1\ \x9c\x2c\xbb\xd2\xc0\x58\x37\xd7\xa2\x7e\x86\xeb\x14\x05\xe3\x03\ \xb2\x1b\x6a\xb7\x97\x97\x79\x3f\x6e\x6d\x94\x21\xd7\x81\x88\xde\ \x0f\x0b\x00\x0a\x94\x07\xf7\xeb\xdc\x54\xc6\x6e\x1e\x4c\x9b\x8f\ \x0c\x8c\xa0\x9e\xb5\x40\x71\xbb\xba\x59\x31\xbf\x2e\xd4\xeb\x00\ \xb2\x80\xfc\x4a\xc4\xee\x28\xf3\xbd\x87\x5b\x13\xd2\xe7\x3a\x10\ \xd1\xa5\x62\x01\x40\x81\xf5\x1f\xcf\xeb\xfc\x51\xb5\x5f\x1e\x4e\ \x9b\x8f\xf6\xa7\xd0\xe4\x87\x7a\x9c\x09\xa6\x39\xb5\x8a\x64\x2c\ \x74\x6f\x8c\x0f\xe8\x1e\x11\xf9\x51\xc4\x33\x3f\xe4\x33\xfb\x14\ \x54\x2c\x00\x28\x14\x1e\xec\xd2\xd8\x98\xda\xbf\x1f\x1a\x35\x1f\ \xeb\x1f\x45\x0b\xd7\x0c\x14\x87\xc6\x4a\xc5\x35\xe1\x38\x0f\x60\ \x10\x22\xbb\x14\xf6\xa7\x9a\xf2\x76\xf2\x20\x1e\x0a\x03\x76\x92\ \x14\x3a\xdb\x5e\xd5\xf2\xca\x7e\x7c\x7e\x2c\xab\x7f\x7a\x2e\x8d\ \x65\x23\x69\x29\x73\x9d\xa9\x54\xd5\x4d\x03\x3e\x34\x3f\x98\x37\ \x6a\x44\xf1\x1a\x80\x5f\x5a\xd1\x47\x2b\x06\xbc\x5d\xad\xad\x92\ \x76\x9d\x89\x28\x97\x58\x00\x50\xe8\x3d\x70\x40\x6f\x1c\x19\xd3\ \x2f\x0c\xa7\x65\xed\xe0\x98\x36\xf8\x96\xcd\xbe\x50\x66\xcd\x00\ \xd6\xce\x0d\x4c\x01\x30\xa4\x22\x4f\x19\xe8\x2f\xc4\x9a\x9f\x2d\ \x6b\x91\x57\x5c\x07\x22\xca\x27\xf6\x84\x54\x52\x76\xee\xd3\xe9\ \xc3\x11\xfb\x67\xc3\x19\xf3\x27\xe7\xc7\xd0\x31\x90\x42\x95\x0d\ \xc5\x0c\x75\x71\x8a\x57\x29\x56\x24\x8a\xf6\x0f\xec\x43\xb5\x4b\ \x8c\xfc\x5c\xc5\xfc\xa2\xbc\x0f\xbf\xe6\x55\x3e\x95\x12\x16\x00\ \x54\xd2\xfe\xb5\x4b\x63\x9a\xb5\x9f\x19\xf3\x65\xe3\x48\x5a\xae\ \x1c\x4a\xa3\x8a\x3b\x11\xe6\xce\x8a\x84\x22\x5e\x3c\x07\x02\xf9\ \x00\x0e\x00\x78\x56\x54\x9f\x29\x53\xef\x09\xae\xda\xa7\x52\xc6\ \x02\x80\xe8\x22\x8f\x1c\xd4\xd9\xe7\xc6\xec\x5f\xa4\x7c\xb3\x71\ \x38\x8d\xa5\x43\x63\xa8\xcd\x5a\x7e\x4e\x26\x23\x56\xa5\x58\xe9\ \xf6\xea\x7f\x00\x90\xbd\x0a\xdd\x23\xb0\xcf\x78\x5e\xe4\xd7\x6d\ \x4d\x32\xec\x32\x10\x51\x31\x61\xc7\x46\xf4\x3e\x76\x74\x6b\x59\ \x6a\x14\x9b\xc6\x7c\xfb\xd1\x74\x46\x56\xa7\xb2\x32\x67\x28\x8d\ \x69\x9c\x25\x78\x7f\x65\x1e\x70\xdd\xe5\x16\x15\x91\x02\xfd\x42\ \x41\x06\x8a\x6e\x28\xf6\x0a\x74\x8f\x35\xde\xde\xf6\x66\xbc\x2c\ \x22\x7c\xa7\x88\xde\x03\x0b\x00\xa2\x09\xda\xb6\x47\xab\x66\x18\ \xfc\x41\xd6\xda\x0d\x59\x6b\x92\xa9\x2c\xe6\x8c\xa4\x51\xc9\x99\ \x82\x71\x02\x60\xd5\x1c\x45\x53\x65\xde\xc6\xde\x21\x00\x87\xa1\ \x38\x24\x06\x9d\x3e\x4c\x67\xda\x47\x27\x8f\xd2\x25\x9a\x18\x76\ \x58\x44\x39\xb0\x79\xb3\x9a\xcb\x6e\xc2\xea\x2c\xec\x86\x6c\x56\ \x56\xa5\xad\x2c\x4c\x67\x31\x3b\x95\xd1\x19\xbe\x96\xd6\xc7\x6c\ \xe1\x2c\x45\xeb\xec\x9c\x0c\xfe\x63\x00\x8e\x42\xa4\x5b\x55\x0f\ \x19\xd5\x6e\xf1\xbc\x43\x6d\x4d\x78\x89\x7b\xeb\x13\x4d\x5d\x69\ \xf5\x4c\x44\x05\xb6\x63\x87\x7a\xa9\xcb\xb1\xd2\xfa\x76\x6d\x06\ \x26\x99\xce\xe0\x8a\x6c\x56\x63\x63\x56\x6a\xc6\x7c\x94\x67\x43\ \xb6\x61\x51\xed\x34\x60\xdd\x7c\x8b\x09\x9c\xff\x33\x08\xe0\x35\ \x88\x1c\x81\xea\x11\x11\x3d\x0a\x5f\x8f\x18\x89\x1c\xb9\x3a\x8e\ \xd7\x39\x85\x4f\x94\x3f\xa1\xea\x7c\x88\x82\xe6\x07\x7b\x75\x91\ \x6f\xd0\x61\xad\x6d\xf3\x55\x16\x66\x14\x2d\xea\x4b\x43\x56\x51\ \x93\xce\x62\xfa\x98\x8f\xb2\xa0\xdc\x5a\x10\x01\x3e\x3c\xcf\xa2\ \x7a\x1a\x00\xc0\x02\xe8\x55\x45\xaf\x88\xbc\x01\xd5\x1e\x05\x4e\ \x08\x70\x42\xa1\x27\x55\xbd\xe3\x5e\x16\x27\x92\xf3\x64\xc0\x6d\ \x6a\xa2\xd2\x15\x88\x8e\x85\xa8\x94\x6d\xee\xd6\xca\xc4\x18\x16\ \x4b\x16\x0b\x44\xec\x65\x6a\x4d\xb3\x2f\x68\xb0\x16\xf5\x59\xd5\ \x3a\xa8\x54\x66\xad\xce\x54\x48\xd4\x2a\xca\xad\x22\x6a\x15\x9e\ \x6f\x35\xaa\x16\xc6\x42\xde\x76\x41\x9e\xb1\x90\x8b\x17\x31\x46\ \x0c\x60\x0c\x14\x6f\x7d\x2d\xe2\xa9\x42\x45\x01\x58\x23\xb0\x62\ \xd4\x37\x40\xd6\x33\x92\x8e\x18\x8c\x1a\xd1\x51\x4f\x64\xd0\x18\ \x1d\x14\xc1\x80\x81\xf6\x89\x67\x7a\x23\x6a\x4f\x2e\x69\x34\x4f\ \xd9\x72\xf4\xae\x68\xc0\x19\x4e\xd3\x13\x15\xb7\xff\x05\x82\x22\ \xaa\x2f\x56\xbc\x39\x1a\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\ \x60\x82\ \x00\x00\x07\xda\ \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\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ \x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x07\x57\x49\x44\ \x41\x54\x58\x85\x9d\x97\x6b\x50\x54\xe7\x19\xc7\x7f\xef\xd9\x0b\ \xec\x72\x0f\x77\x64\xb9\x09\x2a\xa0\x46\x44\xb4\x01\x43\x84\x18\ \x31\xd5\x54\x21\xd3\x66\xd2\xcc\x34\x56\x33\xd3\x69\xcd\x4c\x6a\ \x9c\xd6\x74\x3a\xcd\x74\xfc\xd0\xd4\xde\x92\x69\xda\xa9\xe9\x34\ \x37\xe3\x2d\x35\xa3\x24\xc6\x4b\xea\x2d\x4a\x0c\x04\x04\x2d\xa0\ \x48\x50\x50\x58\x40\x60\x57\xb9\x2e\xcb\x5e\xce\xdb\x0f\xe8\xba\ \xcb\x6e\x84\xf4\xf9\xf4\x9e\xe7\xfd\x3f\xcf\xff\x7f\xce\x79\xce\ \xf3\x3e\x47\x30\x43\xfb\xb8\x49\xc6\x0f\x3b\xd4\xad\x23\x0e\x65\ \x8d\xdd\x29\x53\x9c\x6e\x61\xb0\xbb\xd1\x48\x09\xb3\x22\xa4\x3d\ \x3f\x49\x6d\x92\x42\x5c\x12\xc8\xd3\x36\xbd\xe6\xc8\xf2\x58\x31\ \x32\x93\xbc\x62\x3a\xc0\xbe\x26\xb9\xc2\x3a\xca\x5b\xd6\x51\xe6\ \xb8\xe5\xa4\x4f\xaf\x01\x83\x0e\x0c\x3a\x89\x51\x37\xb9\xce\x8a\ \x91\xde\x61\x36\x60\x9f\x50\x95\x1d\x79\x26\xd1\xf6\x7f\x09\xf8\ \xb2\x4b\x1a\x5a\x7a\xe5\x71\xf3\x90\x28\x76\x4b\xdf\x3d\x8d\x80\ \xd2\x4c\x15\xa3\x0e\xae\xf4\x0b\x86\xec\x50\x98\x2a\xfd\x93\x08\ \x9c\x52\xf2\xc6\xb0\x43\x79\xb5\x24\x5d\xd8\x67\x2c\xe0\x40\x93\ \x2c\x1c\x9b\x90\x95\x42\x10\xdb\x3f\x2a\x18\x99\xb8\xbf\x17\x16\ \x04\x2e\x15\xe6\xc4\x4a\x12\xc3\x24\xc1\x5a\xff\x78\x55\x95\x28\ \x8a\x77\x6a\x59\xa3\x3a\x35\x15\x4b\xd2\x44\xef\xb4\x02\x76\xd7\ \xcb\x67\x1c\x2a\xef\x76\x0d\x61\x88\x36\x4a\xcc\x7d\x23\xdc\xea\ \xed\x62\xc0\x62\x21\x42\x19\x21\x44\xa7\x62\x34\x04\x93\x9e\x1c\ \x4f\xf1\xb2\x05\x3e\xb1\x36\x9b\x9d\x77\x0e\x7c\xc6\xac\x84\x18\ \xca\xcb\x8a\xa6\xa6\x36\x4b\x55\x59\x93\x6f\x12\x8d\xde\x4e\x1f\ \xfd\x7b\xea\xe4\xca\x1b\x83\xec\x1d\x19\x9f\x50\x9a\x2f\xd6\xd0\ \x78\xa1\x1a\x4b\xbf\x9f\x68\x00\x72\x32\x53\x3d\x02\xa4\x94\x9c\ \xa8\x6a\xe0\xef\xbb\x3e\xc1\x3a\x38\xcc\x4b\x1b\xcb\x03\x85\x24\ \x0b\x45\x3d\x72\xa4\x5d\x2e\x5f\x93\x21\x6e\xfa\x09\x78\xeb\x82\ \x8c\x30\x8f\xca\xc3\xad\x5f\xb7\x2a\x47\x0f\xee\xc6\x36\x3a\xa3\ \x22\x06\x60\xfb\x5f\x77\x73\xa2\xaa\x61\x26\xd0\xe4\x20\x97\x7a\ \xec\xcb\x2e\x99\x5f\x68\x12\xe3\x3e\x02\x54\x87\x3c\x59\x75\xae\ \x2a\xf8\xf4\xf1\x83\x20\x03\x14\xd4\x03\xac\xbb\xd7\x3a\x63\x6c\ \x98\x81\xec\xd6\x01\xb9\x0f\x58\x0f\xa0\x00\x7c\x78\x49\x96\xd4\ \x5d\xbe\xbe\xe4\xcc\x14\x72\x45\x11\xac\x2c\xca\x63\xfb\xd6\x0d\ \x3c\xb3\xf6\xb1\x6f\x25\x2a\x90\xb9\x25\x7c\x7e\x5d\xa1\xdd\x2a\ \xd6\xfd\xab\x4e\x2e\x85\xbb\x4f\x20\x54\xe7\xde\x72\xfe\x3f\x87\ \x90\x5e\xe4\xc6\xe0\x20\x5e\xdb\xb6\x89\xfc\x05\x59\x00\x58\xef\ \x0c\xcd\x88\x24\x3b\x33\x85\xa5\x0b\xe7\x06\xdc\x33\x0f\x0a\xc6\ \x1c\x93\x6b\xbb\x4b\xbe\x0b\xe4\x6a\x6b\xac\x32\xfc\x7a\x73\xc7\ \x13\x5d\x66\xb3\x0f\xf8\x95\xcd\xcf\x7a\xc8\x67\x62\x11\xe1\x21\ \xfc\xe4\x87\x6b\x59\x5b\xba\x0c\x97\xea\xa6\xa1\xb9\x8d\xb6\x1b\ \x3d\x58\x07\x87\x09\x35\x18\x50\x43\xe2\x08\x49\xc8\x41\xa7\xd3\ \x03\x70\xc7\x26\xb2\xf7\x36\xc9\x78\xad\xde\xee\x5e\xd3\xd0\x7c\ \x2d\xd8\x3b\xd9\xdc\xd9\x26\x4a\x1f\x79\x78\xc6\xe4\xab\x4b\x0a\ \x58\x59\x94\x47\x70\xb0\x9e\xbd\x1f\x9f\x66\xdf\xe1\x33\x0c\x0d\ \x8f\xf9\xe1\x8c\xa1\x61\xac\x5e\xf7\x2c\x19\x73\x72\x71\xba\x11\ \xb6\x09\xf5\x57\x0a\x88\xd2\x76\x73\x9f\x0f\xf0\xd1\x82\xf9\xd3\ \x92\xce\x9f\x9b\xea\x59\x57\x94\x15\x71\xe5\xeb\x9b\x3c\xbf\xe5\ \x0f\xec\xdc\xf3\x69\x40\x72\x00\xdb\xe8\x08\x95\xfb\xdf\xc6\xdc\ \x79\x1d\x00\xa7\x53\x79\x5c\xdb\x37\xc2\xf2\x6e\xcb\xb8\x0f\x30\ \x29\x2e\xfa\x1b\x89\x33\x52\x92\xd8\xb2\xa9\x9c\xbc\xdc\x4c\x00\ \x7a\xfa\xac\xbc\xf9\x5e\x25\x55\x75\xcd\xd3\x8a\x06\x50\xdd\x6e\ \x4e\x1e\x3e\xc0\x86\x9f\x6d\xc3\xe6\x24\x4d\x7b\xb9\x5f\xc9\x9c\ \x70\xf9\x36\x44\xad\xce\xbf\xbf\x86\x87\x18\xf9\xf9\xc6\x0a\xca\ \xcb\x8a\xd0\x68\x14\x26\x1c\x4e\xf6\x54\x9e\x62\x4f\xe5\x69\x26\ \x1c\x4e\x0f\x4e\xa7\xd5\xf0\xc4\xa3\xf9\xac\x5e\x51\x80\x29\x31\ \x96\x7e\xeb\x20\x7f\xd9\x75\x92\xd6\x96\xfb\x02\x2d\xfd\xbd\xf4\ \x76\x77\x62\x48\x4f\x35\x68\x8b\xd2\xa4\x7a\x2c\xd2\x80\xf7\x91\ \x75\xab\xdf\xff\xbb\x2e\x7b\x6c\x89\x67\x7d\xae\xb6\x99\xbf\xbd\ \x5f\x49\x4f\x9f\x2f\x6e\x59\xde\x3c\x5e\xfa\x71\x39\x29\x49\x71\ \x1e\x5f\x74\x54\x04\x6b\x7f\xb0\x09\xeb\xce\x3f\x62\xe9\xeb\xf1\ \xf8\xcd\x37\xae\x91\x9c\x92\xaa\xd1\x06\x69\x24\x0b\x33\x62\xa9\ \xae\xb9\x9f\xe8\xf8\xd9\x7a\x2a\xbe\xbb\x82\x6b\x56\x0d\x63\x4e\ \x88\x0c\x86\xb8\x50\xc9\xcd\x9e\x01\x3e\xd8\x77\x90\xaf\x2e\x5d\ \xf5\x21\x4e\x8a\x8f\xe6\xc5\xe7\xd7\x53\xbc\xd4\xbf\x76\x14\x01\ \x39\x09\x82\xdc\x85\x4b\x38\x7b\xe2\x13\x8f\xdf\x6a\xe9\x07\x26\ \xfb\xc0\x70\xde\xfc\xcc\x18\xef\xa0\xf6\xce\x1e\x5e\xdc\xfe\x0e\ \x8b\x8b\x56\x11\x16\x1e\x8e\xd5\xd2\x4f\x4b\x63\x3d\x57\x9b\xea\ \x71\xbb\xdd\x1e\x5c\x90\x5e\xc7\x73\xeb\x4b\x79\x6e\xfd\xe3\x04\ \xe9\x75\xb8\xdd\x2a\x87\x3e\x3b\x4f\x58\x88\xc1\xe7\x89\xcd\x8d\ \x95\xa4\x25\x45\x71\xd6\x8b\xc3\x31\x31\x8e\x5e\x83\x5b\x8b\x94\ \x1d\xb9\x59\xa9\x31\xe9\xa6\x04\x3a\xba\x6e\x79\x00\x57\x5b\x2e\ \x73\xb5\xe5\xb2\xdf\x1d\xdd\xb3\x45\x0b\x17\xb0\x75\xd3\x3a\xd2\ \x67\x4d\x16\xec\xc5\xcb\xd7\x78\xfd\xed\x43\xb4\x77\xf6\x04\x3c\ \x8c\x92\xc3\x5d\x3e\xd7\x1a\x54\x52\x22\xa5\x59\x3b\x39\x46\x51\ \xf0\xf2\x0b\x4f\xb3\x65\xfb\x4e\x5c\x5e\x77\x18\xc8\xa2\xa2\x63\ \x29\x7d\xb2\x82\xf4\xac\x1c\xae\xd9\x20\x4d\xaa\xbc\xf9\xde\x21\ \xfe\x7d\xe4\xdc\x03\xe3\x7a\xfb\x2c\x3e\xd7\x19\x09\x46\xb2\x63\ \xe5\x71\x45\x20\x4f\x03\xe4\xe5\x66\xf2\xbb\x6d\x1b\x79\x28\x32\ \x3c\x60\x02\x83\x31\x94\xe2\x95\x4f\xb1\x61\xf3\x2b\x24\x26\xa7\ \x71\xa9\xee\x0b\xc6\x1c\xd0\x71\x47\xd0\xdc\x7a\x33\x60\x8c\xb7\ \x55\xd5\xfa\x7e\xa6\x69\xc9\xf1\x08\xe4\x29\x6d\x90\x4e\xf3\xe9\ \x84\x53\x1d\x03\x42\x0a\x17\xe7\xf0\xcf\x3f\xfd\x9a\x5d\x27\x5a\ \x19\xb4\x0e\x60\x9f\x18\x27\x24\x34\x9c\xb8\x84\x59\xcc\x32\xa5\ \x23\x14\x85\xa6\x86\x1a\xaa\x4e\x1d\x26\x32\x32\x86\x45\x05\xcb\ \x69\xec\x15\x0c\x06\x1c\xb6\xee\xdb\xc9\xf3\x17\x69\xbb\xd1\xed\ \xe3\xcb\x5f\x90\x35\xae\xd7\x6b\x8e\x69\x73\xe3\xc4\x68\x43\xb7\ \x7b\x3f\xb0\x09\x40\x55\x82\x98\x93\xe3\xdf\x86\x6f\x0f\xf4\x71\ \xf4\xd0\x6e\x6e\x75\x77\x02\x10\x19\x79\xbf\x6e\xa7\x9e\xde\xa3\ \x5e\x23\x5c\xed\x7f\x5b\xd9\xf1\x8f\xfd\x3e\xfb\x39\x99\xa9\x64\ \xa5\x27\xef\xce\x8d\x13\xa3\x5a\x00\xa1\x2a\x3b\xa4\x46\xfd\x11\ \x12\x5d\x7c\xa8\x24\xca\x20\xb8\xe3\xdb\x1c\xb9\xd1\x7e\xd5\x43\ \x3e\x9d\x35\xb6\xdf\xe6\xe4\xf9\x8b\x9c\xa9\xbe\xc4\xb9\xaf\x9a\ \x7c\x4e\x59\x45\x51\xd8\xbc\xe1\x7b\x4e\x55\xab\xfc\x1e\xee\x1e\ \xc7\x79\x26\xd1\x56\xdf\xed\x7e\x43\xc0\x2f\x34\x0a\x2c\x4a\x54\ \xf9\xbc\x43\xf9\xb6\x73\x89\xc7\xea\xaa\xcf\x52\x57\xed\xef\x57\ \x14\xc1\xcb\x2f\x3c\xcd\xc3\xf3\x32\xfe\xbc\x38\x5e\xb4\xc3\xdd\ \x81\x04\x60\xd8\xa1\xbc\x0a\xb2\x06\x20\xc2\x00\x69\x91\x0f\x66\ \x8f\x88\x8e\x79\xe0\xfe\x54\x8b\x8e\x0c\xe7\xf5\xdf\xfc\x94\xf5\ \xab\x1e\xa9\x0e\xb3\x29\xbf\xbd\xe7\xf7\x34\xfd\x92\x74\x61\xaf\ \xed\x97\xe5\x5a\xa7\x5a\x0b\x98\xe6\xc5\x49\x86\xec\x82\xdb\x53\ \x5e\x45\x48\x68\x38\x2b\xca\xd6\x91\xbd\x20\x7f\x5a\x52\x21\x04\ \xb3\x53\x92\x78\x6a\xe5\x77\x78\xb2\xa4\x00\x63\x70\x50\x8f\xa2\ \x2a\xdf\xcf\xca\x12\x9e\x2a\xf1\x1b\xcb\xeb\xbb\xe4\x42\xa1\xa8\ \x47\x80\x64\x09\xf4\x0c\x09\xea\xcd\x82\x86\xda\x2f\x18\xbc\x63\ \xa1\x70\xc5\x6a\xf4\x41\x3e\xe3\x03\x6d\x2d\x8d\xdc\xb6\xf4\x31\ \x61\xb7\x13\x1f\xae\x65\x76\x82\x91\xa4\x84\x68\xb2\x67\x9b\x88\ \x8a\x08\xbb\x07\xeb\x52\x55\x65\xcd\x12\x93\x68\xf2\x11\x19\x48\ \x79\x43\xaf\x8c\x45\x75\x7f\x04\xa2\x18\xe0\x5c\x87\x82\x75\x4c\ \x22\xc4\xb4\x7f\x72\x14\xa7\xab\x3c\x64\x9c\xea\x95\xd5\x2e\x9d\ \xa6\x62\x69\x9c\xb8\x35\x75\x47\x99\xea\x00\x58\x9c\x28\x06\xc2\ \x6c\x9a\x55\xc0\x76\x60\x4c\xaf\x99\x9e\x3c\x3a\x04\x16\x25\xca\ \xa9\xe4\x0e\x24\xaf\x85\xd9\x34\x25\x81\xc8\x61\x06\x3f\xa7\xb5\ \xfd\x32\xe1\x4a\xa7\xfc\xa8\x73\x50\x14\xba\xd4\xc0\xf8\xb4\x28\ \xc9\xa2\x24\x9f\xa2\x1d\x13\x92\x3d\x2e\x9d\xb2\xa3\xe0\x6e\xb5\ \x7f\x93\x4d\xff\x4c\xef\xda\x07\x17\x64\xa2\x4b\xaa\xbf\xb4\x39\ \x45\x99\xdd\x25\x4c\xe3\x4e\x8c\x6e\x15\x45\x05\xe6\x27\xc8\x91\ \xcc\x68\xf5\x8a\x44\x5c\x94\xaa\x3c\x13\xac\xd7\x1c\xcd\x8d\x13\ \xa3\x33\xc9\xfb\x3f\xd5\x8a\xed\x13\x27\x02\xa0\xfb\x00\x00\x00\ \x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x10\xb6\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x40\x00\x00\x00\x40\x08\x06\x00\x00\x00\xaa\x69\x71\xde\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ \x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x10\x33\x49\x44\ \x41\x54\x78\x9c\xcd\x9b\x79\x7c\x55\x45\x96\xc7\xbf\x75\xef\xcd\ \xcb\xbe\xef\x8f\x40\xd8\x95\x1d\x42\x02\x88\x0e\x04\x11\x05\x45\ \x11\x5a\x47\xe4\x23\xbb\x1f\x46\xdb\x71\x9b\xd6\x19\x9d\xed\xe3\ \xa7\xfb\xd3\xfd\x69\x5b\xc7\xb5\x6d\x07\x51\x36\x31\x2a\x22\x88\ \x8d\xf4\x20\x9b\x18\x10\x24\x31\x21\x40\x80\x84\x26\x10\x42\xf6\ \x7d\x7b\xc9\x5b\xee\xad\xf9\x23\x24\x64\x79\xb9\x79\x59\xb0\xfb\ \xf7\xd7\xab\x3a\x55\xa7\x4e\x9d\x5b\x75\xea\xd4\x39\xf5\x04\x37\ \x19\x9b\x4f\xc9\x71\x86\xcb\x58\xed\x70\x29\x49\x0e\x9d\xa1\x76\ \x9d\x08\xa7\x0b\x8b\x21\x51\x9d\x06\x42\x00\x9a\x82\xf4\x52\x71\ \xcd\x1c\x22\x73\xfc\xbd\xe5\x19\xe0\x02\x42\x66\xab\x8a\x9a\x3a\ \x29\x46\x94\xdd\x4c\xf9\xc4\x80\x73\x94\x52\x6c\xce\x60\x55\x93\ \x93\xa7\x6a\x9b\x18\x6f\x73\xe2\xed\x69\xd7\xd1\x91\x92\xb1\x51\ \xb2\x23\x37\x49\xb6\x50\xf8\x16\x94\x4f\x13\xac\x22\x7d\xa0\xc5\ \x1d\x30\x05\x6c\xc8\x92\xc3\xa4\x9d\x77\x6a\x6c\xcc\x6b\x70\x78\ \x3e\x69\x21\x20\x36\x50\x52\xd1\x28\x98\x33\xd2\xc0\x57\x33\x6d\ \x7e\x4e\xc0\xa6\x46\x8b\xb2\xfe\x8e\x48\x51\xdf\x6f\xa1\x19\x00\ \x05\x6c\x3d\x2e\xc7\xd4\x49\x36\x55\xd9\x98\xe6\x32\xcc\xf9\xc5\ \x04\x4a\xa2\x03\xa1\xb0\x16\x62\x03\x41\x15\x10\xe6\x27\x29\xaa\ \x13\x54\x35\x41\x62\x9c\xc4\xa2\x7a\x34\x6c\xb5\x84\x77\xbc\x5c\ \xca\xdb\x13\xe3\x45\x75\x7f\xe4\xef\xb3\x02\x5e\xc9\x96\x96\xe8\ \x3a\x63\x4b\xb9\x4d\x79\xc4\xa5\x7b\xc6\xc7\xd7\x0b\x46\x86\x4b\ \xe2\x82\x25\x42\x80\x45\x85\x9c\x72\xc1\xf9\xb2\x96\xee\xe1\xfe\ \xf0\x0f\x43\x8d\xde\x88\x51\x25\x04\xbf\x9e\x1c\xab\xbc\x2b\x84\ \xe8\x55\xc7\x56\xf4\x49\x01\x3f\x15\xc8\x91\xb9\x55\xc6\xbe\xf3\ \xa5\xca\xf0\xde\xf4\xb3\xa8\x70\xf7\x68\x03\x4d\x69\x29\x4b\xa0\ \xbc\x41\xd0\xec\x82\xba\x66\xa8\x77\xc0\x6d\x43\xa4\x29\x0f\xf7\ \x90\xc7\x0c\x5d\x5d\x99\x38\x44\x5c\xea\x6d\xcf\x5e\x2b\x60\xc7\ \x59\xfd\xf9\x9a\x26\xf1\xfb\xaa\x46\x61\x89\x0d\x92\x54\x37\x41\ \x45\xa3\x67\x6c\x84\x80\xd1\x11\x12\x45\x80\x97\x0a\xc3\xc3\xfa\ \x32\x59\xf7\x90\x50\x2f\x90\xeb\x12\x06\x69\x9f\xf5\xa6\x9f\xb9\ \xc9\x69\x87\xed\x52\xaa\xae\x2c\xf6\xf8\x6a\x72\x7e\x4e\xbd\x20\ \xd2\x5f\x32\x2c\x54\xe2\xd4\x05\x15\x8d\xdd\xf7\xf3\xb7\xc0\x90\ \x10\x49\x49\xbd\x40\x08\xd0\xd4\x16\xa3\x17\x60\xf1\x5c\x48\xbb\ \xc3\x89\xb7\xc5\xcb\xb4\x8d\x80\x40\x10\x9f\x66\x14\xe9\x49\x53\ \x62\x95\x17\x3d\xdd\x12\x1e\x7d\xba\x6d\x27\x64\x50\xb5\x2e\x4f\ \x4f\x88\x95\xf1\x27\xae\x2a\xd8\x1c\x10\xe0\x0d\x0a\x50\x67\x87\ \x48\x7f\x89\xa6\x42\x71\xdd\x0d\x76\x01\x16\x48\x18\x64\x10\xe6\ \x07\xf5\x8d\x36\x6a\x9a\x20\xdc\x4f\xc1\xcf\xcf\xc7\x93\x21\x01\ \xb8\x5c\x50\xc2\x9b\x1f\x7e\xc9\xb2\x45\x77\x32\x23\x61\x8c\xc7\ \xfd\x80\x14\xa3\x58\x59\x95\x98\x28\x9c\x3d\x35\xec\x71\x05\xfc\ \x39\x5d\xfa\xe5\xd9\xc9\x6d\xb4\x8b\xe8\x8b\x15\x60\x73\x80\x97\ \x02\xf1\x21\x92\x06\x07\xd4\x3b\x04\xe5\x8d\x82\xaa\xf2\x52\xf2\ \x2f\xe7\x52\x51\x56\x4c\x6d\x55\x25\xcd\x0d\x95\x54\x56\x56\xe1\ \x74\xe9\x5d\x78\xfa\xf9\x78\x13\x15\x19\xca\xff\xfc\xc7\x3a\xa2\ \x23\x42\xbb\xd0\x6d\xb6\x66\x36\x7e\xb1\x8f\x1d\x7b\x53\x71\xe9\ \x3a\xcb\x16\xdd\xe9\xe9\xc4\x5b\xb1\x4c\x58\x65\x70\x7a\xba\x5c\ \xdc\x93\x12\x4c\x15\xf0\x8a\x94\x4a\xfe\x0f\x9c\xae\x6c\x24\x1a\ \xa0\xa0\xa6\xe5\x0b\x3b\x0d\x38\x53\x2c\xc9\xcb\x3d\x47\x4e\x76\ \x26\x57\xf3\x72\x69\x6c\xa8\xf3\x58\x3a\x5b\xb3\x9d\x2b\x05\x25\ \xd8\xed\x8e\x0e\xf5\x52\x4a\xf6\xa7\x66\xf0\xde\xd6\xaf\xa9\xac\ \xf1\x9c\x9f\x3b\x08\x29\xef\x13\xb1\x72\x8b\x94\xf2\x31\xb3\xed\ \x60\xaa\x80\xe8\x1f\x49\x2b\x6e\x64\x44\xfb\x3a\x97\xd3\x49\x56\ \xfa\x31\xd2\x8e\x1d\xa2\xa1\xbe\xb6\x5f\x42\xb6\xc7\xa5\xfc\x22\ \xde\xf8\x70\x27\x59\xe7\x7b\x6d\xc8\xcd\xf0\xe8\xe1\x4b\x46\x3d\ \xf0\x4f\xdd\x35\xe8\x56\x01\x1b\xd3\xe4\x1b\x57\xaa\x49\x68\x5f\ \x97\x97\x9b\xcd\x81\x3d\x5f\x50\x57\xdb\x2f\xdf\xa3\x03\xec\x0e\ \x27\xef\x6f\xfb\x33\xbb\xf6\x1d\x43\xd7\xfb\x74\x94\x9b\xc2\x69\ \x88\x75\x1f\xa5\x49\xfb\xda\x24\xf1\x8c\x3b\xba\x5b\x05\xa4\xa4\ \xcb\x3b\xf2\x6a\x79\xae\xb5\x6c\xe8\x3a\x07\xf7\x7e\x49\x56\xfa\ \xb1\x01\x17\xb0\xb4\xbc\x8a\x1d\x7b\x53\x07\x9c\x6f\x2b\xc2\xfc\ \x24\x5e\x8a\x5c\x9d\x56\x2a\xdf\x4a\x8a\x16\x79\x9d\xe9\x4a\xe7\ \x8a\xed\xdb\xa5\x5a\x62\x63\x8f\xe3\xba\x77\xe7\x74\x3a\xf8\x62\ \xeb\xfb\x37\x65\xf2\x3f\x07\x54\x01\x21\xbe\x04\x94\x57\xcb\x5d\ \xdb\xb7\xcb\x2e\x8e\x76\x17\x05\xd4\x0e\x35\x36\xd7\x34\x13\x0c\ \x2d\x5f\xfe\xeb\xcf\x37\x51\x70\xe5\xe2\xcf\x21\xeb\x4d\x43\x95\ \x0d\x32\x8b\xc4\xc4\xda\x78\xe3\xa3\xce\xb4\x0e\x0a\xd8\x7d\x41\ \x5a\xcb\x1a\x94\x65\xad\xe5\xd4\x83\xdf\x70\xf9\xe2\x39\x8f\x07\ \x0a\x09\x0a\x20\x79\xc6\x44\x1e\x5f\xba\x80\x7b\xe7\x4c\xeb\x97\ \xd0\x03\x85\xf3\x65\x82\xef\x2f\x2b\xd8\x5d\x50\xd2\xa0\x2c\xff\ \x24\x43\xc6\xb7\xa7\x77\xb0\x01\xe5\x75\x6c\xb5\xbb\x5a\x94\x72\ \x2d\x3f\x8f\xf4\x1f\x0e\x79\x34\xc8\xd0\xc1\x31\xac\x5c\x32\x8f\ \x39\x33\x27\xa1\xa9\x2d\xab\xec\x44\xc6\x79\xf6\x1e\x3e\x39\x30\ \xb3\xe8\x23\x1a\x9d\x90\x5b\x71\xc3\x39\x73\xea\x28\x0d\x4e\xb6\ \x00\xc9\xad\x75\x6d\x0a\xd8\x7e\x51\x46\xe6\x17\xb5\x10\x34\x05\ \xd2\xbf\xdb\x8d\x94\xe6\xbe\xba\x10\x82\x47\x16\xce\x66\xdd\xb2\ \x7b\xb1\x78\x99\xbb\xaa\x7f\x0b\x54\xda\x04\x9d\xa7\x50\xd6\xc0\ \xac\xf7\x4f\xc9\x41\x4f\x4e\x16\x85\xd0\x4e\x01\xa3\xfc\x8c\x95\ \x23\x47\xa2\xd6\xdb\x21\x2b\x3b\x87\xbf\xe6\x5d\xe9\x71\x80\xa7\ \x56\x3c\xc0\xd2\xfb\x93\x07\x54\xe8\x81\x42\x79\xa3\xe0\x6c\x71\ \x57\x4f\xdf\xa9\x23\x34\x3b\xef\x01\x0f\x42\x3b\x05\x18\x92\x95\ \x42\x40\xa0\x37\xec\x3f\x72\xa2\xc7\x01\x96\xde\x9f\xfc\xb3\x4c\ \xde\x4b\x53\x09\x0a\xf2\x73\x4b\xb3\x35\xd9\xa9\x6f\xb4\xa1\xaa\ \x0a\x81\xfe\x7e\x78\x5b\xbc\x30\x24\xfc\x78\x55\x50\xd6\x20\xe8\ \x6e\xfd\x56\x36\xb1\xa0\xf5\xb7\x06\x90\x59\x2c\x93\xa4\x61\x8c\ \x87\x16\xc7\xe4\xf8\x4f\xe7\x4d\x85\x8a\x8a\x08\xe1\xf1\xa5\x0b\ \x4c\xdb\x0c\x04\xa6\x4d\xba\x85\xe7\xd6\x2e\x61\x88\x35\x0a\x80\ \xe2\xb2\x2a\x8e\x67\x9e\xe7\x44\xc6\x39\x2e\x5c\x2a\xa0\xaa\xe6\ \x46\x54\x4c\x51\x04\x71\x31\x91\x0c\x1d\x39\x86\x61\xe3\xa7\x13\ \x19\x3d\xa8\x5b\xbe\x36\x07\x96\xad\x69\x72\xf9\x8a\x24\xf1\x71\ \xcb\x0a\x90\xc6\xa3\xad\xc4\xb3\x39\x57\xb0\x35\xdb\x4d\x05\x7b\ \x62\xd9\x42\x7c\xbc\x7b\x71\x9f\xed\x25\x62\x22\xc3\x78\x7a\xe5\ \x22\x66\xcf\x98\x08\xc0\x99\x9c\xcb\x6c\xdd\xb1\x9f\x13\xa7\x2e\ \x74\x6b\x97\x0c\x43\x72\xb5\xa8\x8c\xab\x45\x65\xa4\xa6\x7e\xcf\ \xb8\xc9\xd3\xb8\x73\xc1\x12\x2c\xde\xee\x6f\x9f\x8d\x3a\xcf\x00\ \x2d\x0a\x90\x92\xbb\x5b\x09\x17\xaf\x14\x9a\x0a\x17\x1c\xe4\xcf\ \x9c\x99\x93\xfb\x34\xb1\x9e\x60\xd1\x34\x1e\x5d\x74\x27\xcb\x97\ \xcc\xc5\xc7\xdb\x42\x79\x55\x2d\x7f\xda\xfa\x35\xfb\x8f\x66\xf4\ \x8a\x8f\x94\x92\xb3\x99\x3f\x52\x5a\x74\x8d\x87\x56\x3c\x81\x7f\ \x40\x50\x97\x36\x75\xcd\x8c\x07\xd0\xb2\x4a\x64\x94\xae\x1b\x63\ \x5b\x09\x97\xf2\x8b\x4c\x99\x4f\x9f\x7c\x2b\x5e\x9a\x67\x91\xcb\ \xde\x60\x66\xc2\x58\x9e\x59\xb3\x98\xb8\x98\x08\x9c\x2e\x9d\x94\ \xaf\x0e\xb1\x79\xc7\xb7\x3d\xae\x46\x33\x94\x97\x16\xf2\xf5\x67\ \x1b\x79\x64\xf5\xd3\x28\x6a\x47\x99\x1b\x1d\xf8\x7c\x98\x26\xc7\ \x6b\xba\x4b\x9f\x85\x10\x02\x5a\x82\x1b\x7f\x2d\x32\x8f\x36\x8f\ \x1b\x35\xb4\xcf\x02\xb9\x83\x9f\x9f\x0f\xaf\xbe\xf4\x38\xb7\x27\ \x8e\x03\xe0\x64\x56\x0e\x6f\x7d\xb4\x93\xab\x45\x03\x93\x0f\x29\ \x2c\xb8\xcc\x89\xd4\x6f\x99\x99\xdc\xd5\x66\x29\x18\xcb\x35\x29\ \xc4\x78\x01\x48\x09\x99\x85\x0a\x8d\xb6\x66\x53\x86\x83\x63\x23\ \x07\x44\xb0\x56\x44\x84\x06\x13\x91\x18\x4c\x49\x79\x15\xef\x6e\ \xd9\xcd\x91\x13\xa7\x07\x94\x3f\x40\xda\xb1\xc3\x24\x4c\x9f\x8d\ \x8f\x6f\xc7\xd3\xc4\xe1\x12\xd3\xb4\xbc\x4a\x31\xaf\xa6\x19\xaa\ \x6d\x82\x06\x07\xd8\x1d\xe6\x0a\x08\x0c\xf0\xed\xb7\x40\x16\x4d\ \xe3\x91\x07\x92\x89\x89\x0e\xc7\xe1\x74\x92\xb2\xfb\x30\xdb\x76\ \x1d\xa4\xb9\x53\x80\x64\xa0\xe0\x74\xd8\x39\x75\xf2\x28\x33\x66\ \xdf\xdd\xa1\xbe\x59\x17\xc3\xb5\x2b\xd5\x4c\xae\xb7\xdf\x70\x18\ \xbc\x54\xf3\xfd\x6d\xf1\xf2\x38\x8e\xea\x16\xed\xf7\xfa\xb1\xf4\ \x6c\xde\xde\xb4\x8b\xa2\xd2\x4a\x8f\xfa\x46\x85\x87\x90\x7c\xdb\ \x24\x26\x8f\x19\x41\x54\x44\x30\x0d\x8d\xcd\x5c\xb8\x54\xc0\xe7\ \x7b\x8e\x50\x5d\x6b\xbe\x75\x2f\x9e\xcf\xea\xa2\x00\x97\x2e\xc3\ \xb5\x61\x61\x34\x9d\x29\xc1\xaf\xf5\x74\xb1\x58\xcc\x8f\x37\xbb\ \xb3\xc7\x38\xa3\x5b\xc4\xc5\x44\xf0\xcc\x9a\xc5\xcc\x4c\x18\xcb\ \xb5\x92\x0a\x5e\xfc\xed\x07\x1c\xcf\x34\xf7\x37\x5a\x11\x1e\x12\ \xc4\xe3\x4b\x17\xb0\x60\x4e\x52\xdb\x5d\xa3\x15\x89\x13\x47\x33\ \x3f\x39\x89\xb5\x2f\xff\x91\xca\x8a\xf2\x6e\x79\x94\x96\x14\x52\ \x5f\x5b\x43\x60\x70\x48\x5b\x9d\xc3\x10\xde\xda\xf0\x30\x29\xbc\ \x14\x38\x53\x22\x70\xe8\x20\x34\x73\x05\x54\x55\x37\x78\x24\x74\ \x2b\x7c\x7d\x2c\xac\x58\x32\x8f\x47\x1e\x48\xc6\xd0\x0d\x3e\xf8\ \x74\x2f\x9f\xed\x3e\x8c\xc3\xe5\xea\xb1\xaf\xa6\xaa\x3c\x7c\xdf\ \x2c\x56\x3f\x7c\x0f\x7e\xbe\xdd\xa7\x1b\x7d\xfd\x83\xb8\x67\xf1\ \x63\xa4\x6c\x78\xb3\x7b\x66\x52\x52\x5c\x98\xdf\x41\x01\xba\x8e\ \xaa\x01\x01\x83\x43\x5a\xd2\x55\x35\xcd\x90\x1a\x16\x40\xbe\x49\ \x58\x2e\xef\x6a\x31\x77\x24\x8d\xeb\x51\x78\x21\x04\x77\xdd\x3e\ \x85\x5f\xae\x78\x80\xa8\xf0\x10\x0e\x9f\xc8\xe2\x8f\x9b\x77\x53\ \x5a\xe1\x59\x38\x2d\x71\xe2\x68\x9e\x5f\xb3\x84\xf8\xb8\x68\x0c\ \xc3\x3c\x54\xe6\xe7\x05\x71\x71\x43\x19\x34\x78\x18\x85\x05\x97\ \xbb\x6d\x57\x55\x5e\xda\xa1\xac\x1b\x88\xb6\x78\x80\x10\x10\xea\ \x0b\x13\x46\x44\x99\x0e\x76\xe2\x54\xcb\xb2\x35\x68\xb9\x70\x9c\ \x29\x69\xf1\xbb\x3b\x67\x87\x92\x26\x8d\xe6\x95\xe7\x57\xd0\xd4\ \x64\xe7\xb9\x5f\xbf\xcf\x7f\xbd\xbe\xd9\xa3\xc9\x47\x47\x84\xf2\ \x9b\x17\x56\xf1\xd6\x7f\x3f\x89\xaf\xaf\x37\xaf\xbc\xb9\x95\xb4\ \xac\x5c\xd3\x3e\x42\xc0\xad\xd1\x92\x61\xa3\xcd\x73\x07\x95\x15\ \xa5\x5d\xea\x34\xa0\x01\x08\x6b\xad\x88\x8f\x8b\x36\x65\x72\xfa\ \x7c\x1e\xc7\x4f\xe7\x53\x69\x19\x86\x4b\x07\x1f\x0d\xf2\xae\xdb\ \xb0\xd8\xa0\x96\x4c\x50\x98\x1f\x5c\x2e\x77\x71\xe4\xe0\x3e\xf6\ \x1d\x38\x82\x4b\xef\x9a\x1b\xe8\x0c\x8b\xa6\xb1\x74\xd1\x1c\x56\ \x2c\xb9\x0b\x45\x55\xf8\x78\xe7\x01\xb6\xee\xdc\x4f\x53\xb3\x83\ \xf9\xb3\x93\x7a\xec\x3f\x3a\x42\x92\x78\x4b\x2c\x47\x0f\x76\xdf\ \xa6\xa9\xb1\xe3\xf6\x55\x15\xa4\x06\xd4\xd3\x4e\x01\x63\x47\x0e\ \x41\x08\x61\x1a\x0b\x78\xf5\xfd\x14\x1e\x5e\xf5\x34\xfe\x01\x41\ \x68\x2a\x34\x5f\xdf\xce\x45\x75\x02\x5d\xd7\xc9\x3e\x75\x92\x63\ \x87\xf6\x7a\x9c\x2b\xb8\x6d\xca\x18\x9e\x5d\xbb\x84\xb8\x98\x08\ \x7e\xc8\x38\xc7\x3b\x1b\x77\x71\xad\xa4\xc2\xa3\xbe\xed\x31\x66\ \x70\x80\x29\xdd\xe9\xec\x78\xcc\xaa\x2a\xba\x06\x54\x00\x6d\x61\ \xa2\xe8\x88\x50\x46\xc6\x5b\x4d\xef\x04\x15\xe5\x65\x6c\x7e\xef\ \x55\x12\x67\x26\x13\x6d\x1d\x8c\xb7\xb7\x2f\x75\x75\xd5\x14\xe6\ \x5f\x22\xe7\xec\x29\x8f\x27\x6e\x8d\x0e\xe7\xd9\xd5\x8b\xb9\x3d\ \x71\x1c\xd7\x4a\x2a\xf8\xd7\xdf\x6d\xe0\x87\x0c\xcf\x43\x70\x9d\ \xe1\xd3\xc3\x09\xe6\xb0\x77\x74\xab\x2d\x8a\xb4\x6b\x40\x2e\x30\ \xb5\x3d\x61\xf6\x8c\x89\x3d\x5e\x8a\x9a\x6c\x0d\xa4\x1e\xd8\xd3\ \x37\x41\xbd\x2d\x3c\xb6\x78\x2e\xcb\x16\xcd\x01\x21\x58\x9f\xf2\ \x0d\x9f\x7f\xfd\x9d\x47\x27\x83\x19\x7a\x3a\xa2\x0d\xd9\x71\x2b\ \xfa\x5a\x44\xb1\x26\x20\xa7\xf3\x62\x5f\x72\xcf\xed\x7c\xb2\xfb\ \x10\x4d\x4d\x7d\xbf\x88\x74\x87\xd1\x63\x27\x71\xf7\x7d\x0f\x92\ \x38\x32\x14\x2f\x2f\x49\x41\x61\x29\x1f\xef\x3c\x30\x20\xbc\x6b\ \xeb\x6d\xa6\xf4\x60\x7f\x1f\x62\x02\x24\xde\x5e\x30\x2c\x54\x12\ \xe2\xc3\x16\x4d\x0a\x79\x16\xd9\xd1\x82\x07\x05\xfa\xb3\x74\x61\ \x32\x9b\xbe\xd8\x37\x20\x82\x01\x84\x86\x47\x31\xf7\xbe\x5f\x30\ \x74\xc4\xad\x00\xec\x3e\x72\x96\xa4\x31\x71\x0c\xed\x7a\x53\xed\ \x33\x0a\x8a\xba\x77\x84\x00\xa2\x42\x7d\x99\x11\x7f\xe3\x73\x4b\ \x21\xb3\x35\x55\x51\x53\x75\xdd\x90\x74\x4a\x95\xaf\x7c\x68\x1e\ \x69\x59\x39\x9c\xcd\xbd\xd2\x2f\xa1\x2c\x16\x6f\x6e\x9b\x7d\x0f\ \x09\xb7\x25\xa3\xaa\x2a\xd5\x55\x15\x1c\xfe\xcb\x4e\xf2\x72\xb3\ \x89\x7a\xf6\xdf\x19\x64\x6e\xb7\x7a\x85\xec\xdc\xee\x7d\x00\x68\ \xf1\x28\xdb\xc1\x90\xa8\xa9\xda\xa4\x18\x51\xf6\xd3\x35\x3d\x5b\ \x88\x96\x00\x41\x2b\x34\x55\xe5\x77\x2f\xae\xe6\x57\xbf\xfd\xa0\ \x47\x7b\xd0\x1d\xc6\x4c\x98\xca\xec\xbb\x17\x11\x10\x14\x8c\xd3\ \xe9\xe0\xf8\x77\xfb\x48\x3b\x76\x10\x5d\x6f\xd9\xeb\x4e\xe3\x46\ \xc6\xb9\xbf\x70\xb8\x5c\x9c\x3c\x65\xee\x2f\x8c\x88\xb7\xb6\xfd\ \x96\x90\x95\x68\x15\x15\x1a\x80\xa2\xb0\x5f\xca\x8e\x0a\x00\x08\ \x0b\x0d\xe2\xbd\x5f\xff\x33\xaf\x6f\xfc\x8a\xfd\x47\x4e\xf6\x18\ \x26\x07\x40\x08\x46\x8c\x1a\xcb\xb4\x59\xf3\x18\x34\x78\x18\x00\ \xb9\xe7\xb2\xf8\x6e\xdf\x57\xd4\xd5\x54\x75\x69\x5e\x54\x37\x30\ \x0a\x38\x78\x34\x93\xfa\x46\x73\x1b\x30\x6a\xa8\xb5\x7d\xf1\x00\ \x5c\x0f\x8a\x4a\x94\x14\x30\x9e\x77\xd7\xc9\xcf\xcf\x87\x07\x7f\ \xf1\x28\xd6\xb1\xc9\x9c\xc9\xfc\x91\xcb\x17\xcf\x53\x5d\x59\x8e\ \x94\x37\xdc\xd3\x80\xa0\x60\xa2\x63\x07\x33\x7c\xd4\x58\x86\x8f\ \x1e\xd7\xe6\x6f\x57\x57\x96\x71\xf0\x9b\x2f\xb9\x72\xe9\x42\xb7\ \x42\x35\xf7\xcf\xf0\x03\x2d\xd1\xe1\xf5\x9f\x7c\x63\xda\xc6\xdf\ \xcf\x97\x71\xb7\x0c\x6d\x2b\x2b\x52\x49\x81\xeb\x0a\x48\xb0\x8a\ \xf4\x9f\xae\xe9\x67\x3b\x6f\x83\x56\xc4\x04\x42\x44\xb4\x95\x39\ \xf3\x17\x33\x67\xfe\x62\x5c\x2e\x27\x0e\x7b\x33\x86\x6e\xe0\xe3\ \xeb\x87\xe6\x26\x29\x52\x5c\x98\xcf\x67\x1f\xbd\x8d\xee\x81\x17\ \xd8\x5f\xfc\xef\x27\x7b\xa8\xa8\x36\x7f\xab\x70\x7b\xe2\x58\x2c\ \x5a\xdb\x55\xfe\xdc\x94\x38\x71\x0a\xda\xe5\x06\x15\xc1\x96\xee\ \x3a\x17\x75\xf2\x6b\x34\xcd\x0b\x3f\xff\x40\x02\x82\x82\xdd\x4e\ \x1e\xa0\xd9\xd6\x38\x60\x93\xaf\x6e\xea\x9e\xb6\x6d\xd7\x41\x76\ \xfe\xdf\xd1\x1e\x79\x2c\x9c\x3b\xe3\x46\x41\xd2\x96\x24\x6d\x53\ \x40\xa3\x45\x59\x0f\xb8\xbd\xad\x34\xde\x9c\x40\x8d\xc7\xc8\x29\ \x17\xa4\x5e\x51\x28\xa8\xbd\x61\x2f\xec\x0e\x27\x6f\x6f\xdc\xc5\ \xfa\x14\xf3\xa5\x0f\x30\x69\xcc\x08\x12\xc6\x8d\x6c\x2d\x56\xd9\ \xbc\x95\x0d\xad\x85\x36\x05\x5c\x7f\x7b\xfb\xae\x3b\x06\x13\x62\ \x25\xbe\x7f\xc3\xd4\x9f\x04\x2a\x1b\x21\xa7\x4c\xe0\x70\xb9\xf8\ \xcb\x77\x69\xac\x7e\xe1\x75\xbe\xd8\xfb\x7d\x8f\x86\x59\x51\x14\ \x7e\xb9\x62\x61\x7b\x66\x6f\xb5\x7f\x67\xdc\x21\xbe\xa5\xb9\x94\ \xb7\x5c\x9a\xf1\x24\xd0\x21\xf2\x19\xe4\x0d\x09\x83\x24\x3f\x5c\ \xe9\x3e\xdd\x74\x33\x91\x93\x7d\x8a\xa2\x6b\xf9\x54\x96\x14\xf2\ \xc6\xb5\x4b\xd4\x35\x98\x5b\xfb\xf6\x78\x6c\xf1\xdc\xf6\x91\xec\ \x52\xc3\xae\xbc\xd3\x9e\xde\x41\x01\x13\xe3\x45\x75\x46\xa1\xeb\ \x25\x10\x5d\x1e\x12\x44\xfa\x4b\xac\xc1\x50\x58\x3b\x30\xc7\x56\ \x6f\x70\x36\xf3\xc7\x3e\xf5\xbb\x3d\x71\x1c\x6b\xff\x71\xfe\x8d\ \x0a\x21\x5f\x4c\x1c\x21\x3a\x58\xcb\x2e\x2f\x44\xa6\x58\xd5\x4d\ \x20\xdd\xbe\x87\x19\x1f\x2d\x51\xbb\xf4\xf8\xfb\xc4\xb4\xc9\xb7\ \xf0\x9b\x5f\xad\x44\x6d\x13\x58\x7e\x3f\x25\x56\xdd\xd6\xb9\x5d\ \x97\xe9\x08\x21\xa4\xa1\xab\x2b\x11\x74\x39\x57\x5a\x5f\x7b\xff\ \xbd\xe3\xe1\x7b\x67\xf1\xda\xcb\xeb\xda\xbf\x59\xa8\xd1\x35\x75\ \xb5\x10\xa2\x8b\xf0\x6e\xbf\x67\xe2\x10\x71\x09\x43\x3e\xee\x8e\ \x76\x4b\xa4\x24\xd0\xe3\xbf\x43\xb8\x87\xa2\x28\x24\xcc\x98\x45\ \x50\x70\x58\xcf\x8d\x7b\x81\xd8\xa8\x30\xde\xf8\xcf\x27\x78\x76\ \xcd\xe2\x76\x5f\x1e\xa4\x90\x6b\xdd\xbd\x10\x03\x93\x77\x82\x09\ \x71\xda\x8e\x8c\x22\xfd\x0d\x24\xff\xd2\xbe\x5e\x11\x30\x2a\x42\ \x92\x59\xd8\x37\x83\x18\x37\x64\x04\x73\xef\x7b\x88\xc8\x18\x6b\ \xcf\x8d\x3d\x44\x74\x44\x28\xcb\x16\xdd\xc9\xfd\x77\x4d\xef\xf2\ \x52\x45\xc0\x1f\x12\xac\xda\xce\xee\xfa\x9a\x66\x39\xa6\xc4\x2a\ \x2f\x64\x14\x19\x61\x02\x56\xb5\xaf\x1f\x12\x22\xf1\xd1\xe0\x74\ \x89\xa0\xc1\xc3\x90\x81\x7f\x40\x10\xc9\xf7\x2c\x62\xcc\x84\xa9\ \x2d\x51\xcc\x7e\x22\x2a\x22\x84\xc4\x09\xa3\x99\x3b\x73\x0a\x49\ \x93\x46\xa3\x28\x6e\x17\x73\xca\x64\xab\xf2\xb2\x19\x1f\x53\x05\ \x08\x21\x64\x7a\xba\x5c\x27\xac\x32\x0a\x29\xef\xed\x20\x40\x80\ \x64\xee\x08\x49\x69\x83\xe0\x74\xb1\xc0\xd6\x4d\x30\x46\x51\x55\ \x12\xa6\xcf\x62\x66\xf2\x7c\xb7\xb9\x7a\xcd\xcb\x42\x78\x54\x0c\ \x75\x35\xd5\x38\x1d\x5d\xb5\xa9\xaa\x1a\xb1\x51\x61\xc4\xc5\x86\ \x63\x8d\x0c\x63\x78\xbc\x95\xa9\x13\x46\xf5\x98\xa3\x14\x88\x3d\ \x7a\xb1\x58\x25\x06\x99\x3f\x9b\xf7\xe8\x53\x1c\x96\x52\x0b\x2e\ \x32\xd6\x03\x6b\xdc\xd1\x9b\x5d\x70\xf8\x52\xcb\x53\xb4\x56\x5c\ \xbe\x78\x8e\x93\x47\x0f\x71\xd7\xc2\x87\x08\x8f\x8c\xf1\x64\x18\ \x00\x1c\xf6\x66\xa4\x34\x10\x08\x2c\x3e\xbe\xf8\x59\x60\xde\xa8\ \x1e\xfe\x8c\xd4\x09\x42\xb2\x4d\x2f\x51\xd6\x78\xf2\x5c\xde\x63\ \xbe\x52\x4a\x91\x51\x64\xfc\x41\xc0\x0b\xee\xe8\xa7\x8a\x05\x57\ \xaa\x6e\xb0\x73\x39\x9d\xdd\xde\x13\x7a\x83\xd8\x20\xc9\xf4\xc1\ \x1e\x5b\x1b\x29\xe0\xb5\xc9\x56\xe5\x25\x77\x16\xdf\x1d\x3c\x3e\ \xd5\x85\x10\x72\xea\x20\xf5\x45\x21\xe4\x62\xdc\xdc\x19\x02\x3b\ \x05\x64\x07\x62\xf2\xd0\x62\x70\x3d\x44\x9d\x94\x72\xe9\x94\x41\ \xea\xbf\x79\x3a\x79\xe8\x85\x02\x5a\x31\xc5\xaa\x7d\xa5\x6b\x4a\ \x22\xc8\x0e\x2f\x9c\x6f\x86\x83\x34\x38\x44\x12\xe6\x51\x36\x5e\ \x7e\xaf\xaa\xca\xe4\xa9\x71\xda\xf6\xde\x8e\xd1\x67\x73\x2c\xa5\ \x14\x99\x85\xfa\x72\x84\x78\x0d\x88\xba\x5a\x2b\xc8\xb8\xd6\x7f\ \xeb\x1e\xee\x0f\xb7\x46\x4a\x42\x7c\x24\x5e\x3d\xbf\xc4\xa9\x92\ \x82\x97\x13\x62\x95\x0d\xbd\xf9\xea\xed\xd1\x6f\x89\x33\x2f\xcb\ \x10\x69\x31\x9e\x29\x69\x10\x2f\x9c\xc8\x17\x81\xfd\xe1\x35\xc5\ \x2a\x89\x0f\xf5\x68\x1e\x95\x48\xde\x36\xec\xca\x3b\x9d\x7d\xfb\ \xde\x62\xc0\x6e\x36\x9b\x4e\xca\x18\x17\xf2\x4f\xd5\x36\xb1\xa0\ \xc1\x81\xe7\xff\x8c\xba\x0e\x4d\x85\x85\xb7\x9a\x67\x81\xa5\xe4\ \x2c\xb0\xd1\xc7\xa2\x6c\x18\x17\x25\x7a\x97\xa7\xef\x06\x37\xe5\ \x6a\xb7\x35\x4d\x2e\x6f\xd2\x79\xaa\xce\xce\xc4\x06\x3b\xbe\x9e\ \xae\xcd\xfb\xc7\x1a\xa8\x1d\x25\x32\x80\xd3\x12\xf6\x2b\x52\x49\ \x69\x0d\x63\x0d\x24\x6e\xfa\xdd\x76\xeb\x4f\x72\x8c\xcb\x30\x56\ \x39\x75\x91\x60\xd7\x19\xe5\x70\x89\x08\x97\x81\xc5\x65\xa0\xb9\ \x0c\x84\x04\xbc\x14\x64\xb0\xaf\x6c\x9a\x19\x6f\x9c\x03\x91\x23\ \x24\x17\xa4\x22\xb3\x25\x6a\x6a\xa2\x55\xf4\x3e\x4b\xda\x0b\xfc\ \x3f\xcf\xc2\x42\x82\x41\x31\x16\x33\x00\x00\x00\x00\x49\x45\x4e\ \x44\xae\x42\x60\x82\ \x00\x00\x03\xb1\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xff\x61\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ \x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x03\x2e\x49\x44\ \x41\x54\x38\x8d\x5d\x93\x5d\x4c\x9b\x75\x14\xc6\x7f\xff\xb7\x6f\ \x8b\x6d\x0a\x2b\x8c\x41\xd9\x04\x5c\x1d\x96\x16\x27\xac\x2c\xab\ \xba\x10\x03\xce\x69\x9c\x2e\x12\x8c\x2e\x3a\x13\x89\x0e\x83\x9a\ \x5d\xe0\xc7\x05\xd1\x99\x18\xbd\x51\xb6\xcc\x45\x13\xa2\x75\xd3\ \x69\x1c\x31\xce\x68\xc0\x1b\x98\xd3\x18\x97\xe9\x36\x98\xb8\x15\ \x5a\xfa\xa1\x80\xb4\x0c\x3b\x69\xf9\x18\xb4\xd0\xf7\xef\xc5\xe4\ \x0d\xf3\x5c\x9d\x9c\xe7\x39\x1f\x4f\xce\x39\x82\xff\xd9\xd1\xf3\ \xf2\xd1\xf4\x32\xaf\xcd\x66\x70\x0a\x30\x59\x4d\x52\x7a\xcb\x64\ \x40\x4a\x4e\xa9\xaa\xe2\xab\xb6\x8b\xcb\xab\xf9\x62\xc5\xe9\xbe\ \x20\x2d\x33\xaa\xec\x09\x27\x44\x7d\x56\x03\xa3\x02\xf7\xdc\xaa\ \x31\xbb\x28\x28\xc9\x93\x48\x29\x11\x42\x64\x05\x74\x9a\x92\x4a\ \x5b\x55\x95\xc8\xe8\x05\x8e\x5d\x94\xb6\xf9\x05\x6d\xf8\xdc\x80\ \xdf\x1e\x1d\x19\x42\xa6\x53\x90\x4d\xf3\x54\xe3\x0e\xb6\xd5\x38\ \xf9\xe1\x97\x41\xce\x0f\x06\x79\xf5\xb9\xc7\x56\xfa\x9e\x4e\x8d\ \x88\x5d\xf5\xf5\x62\x51\x05\x98\x9c\x9c\x3e\xf3\x91\xef\xa8\x7d\ \x32\x36\x76\x83\x9c\xfb\xea\x6a\xe9\xfd\xa9\x9f\x37\x8f\x7c\xce\ \x9d\x1e\xd7\x2a\x44\x36\x2c\x6f\x90\x3e\x60\xaf\xf2\x8d\x5f\xd6\ \x75\x75\x9d\x70\x4f\xc6\xc6\x70\x94\xad\xe7\xf0\x81\x56\xb6\x54\ \x6d\xd2\xa9\x8b\x99\x25\x00\x8a\xd7\xe6\xeb\xb1\x85\x25\x38\x3b\ \xa6\x3c\xf9\xd9\x45\xf9\x88\x9a\x8c\x47\x9e\x0d\x8d\x04\x31\xdf\ \x94\x43\x47\xfb\x3e\x8a\x0a\x6d\x74\xf5\xfc\xa8\x93\xd7\xda\x72\ \x39\xb0\x7f\x2f\xaa\x6a\xe0\xf5\x8e\x4f\xb8\x9a\x9c\xc3\x5c\x58\ \x86\xb7\xee\x7e\x66\x2d\xe6\x37\xd4\x68\x7c\xe6\x01\x80\x6a\xb7\ \x83\xa2\x42\x9b\x9e\x78\x47\xa5\x03\xcf\xed\x9b\x98\x9b\xbf\xc6\ \xa7\x27\xfb\x18\x0a\x8d\xae\x92\x10\x61\x32\x1e\xe3\xe9\x67\x5a\ \x5d\xea\x5c\xd6\x58\x00\x60\x50\x14\x1d\x6e\xd9\xf3\x20\x45\x85\ \x36\x3e\xfc\xe2\x3b\x7a\x4e\xff\x8a\xd5\x62\x66\x7f\x73\x23\x3b\ \xee\xae\xe1\xec\xc8\x55\x3a\xde\xeb\x64\x34\x12\x24\x36\xf5\xb7\ \x49\xdd\xee\x2a\x48\x77\x82\x3a\x70\x29\xcc\xd7\x3f\xff\x81\x31\ \x77\x1d\x61\x7f\x84\xde\xde\x5e\xe6\xaf\x2d\xf0\x50\x83\x97\x96\ \x27\x76\x31\x95\x48\x52\x90\x9f\x47\x83\x27\x8f\xaf\x1c\x15\x84\ \x02\x7e\x92\xff\x24\x50\x37\xde\x6c\x1f\xad\xdd\x5c\xe1\xee\xbf\ \x14\xe2\xd0\xe1\x23\xfa\x14\xeb\x4b\xcb\x69\x6d\x6d\x62\x4b\xb9\ \x99\xf6\x77\x8e\x61\xb1\xe4\xd0\xd1\xde\x82\xd9\x08\x6b\x72\xb2\ \x00\x6c\xb0\xa4\x13\xaa\x94\xf4\xbd\xfd\x4a\xb3\xfb\xd0\xf1\x53\ \x0c\x87\xc7\xb1\x58\xad\xdc\xe6\xaa\x46\xd3\xb2\x84\x26\x66\x99\ \xf8\x6b\x82\xdf\x03\x51\x7d\x8d\x57\x12\xd3\xf8\x83\x51\x00\x9c\ \xe5\x6b\xba\x55\x55\x55\x7c\x56\x8b\xf9\xc5\xa6\xc6\xdd\x86\x50\ \xe2\xfa\x61\xf6\x75\x7f\xc9\xe0\x85\x33\xec\xdc\xbd\x87\xf9\xff\ \x8e\x75\x2a\xb5\xc4\xb9\xdf\x82\xbc\x7f\xfc\x5b\x16\x16\x33\x6c\ \xab\xae\x94\x9b\x9d\x8e\x83\x4a\xb5\x5d\x5c\x16\xd0\xe9\x5c\x27\ \x31\xab\xd7\xc7\x9f\x49\x4d\x03\x60\x34\x1a\x75\x49\xd1\x48\x98\ \xb6\xb7\x3a\x89\x8e\xc5\x71\x57\x94\xf3\xd2\xbe\x26\x9f\xa7\x44\ \x0c\xa9\x00\xa6\xa4\xd2\x26\x6d\xb2\xd2\x5b\xa6\xdd\x3b\x10\x53\ \xc8\xcd\xb3\xf1\xf0\xe3\xcd\x38\xdd\x35\xfc\x19\x0e\x50\x7a\x4b\ \x05\xc5\x36\x13\xa5\xc5\x36\xbc\x35\x2e\xee\xaa\x75\x7f\x2f\xae\ \x18\x5e\xd0\x7f\x01\xc0\xef\x97\xa6\xb4\x4d\x3b\x38\x9e\x12\xcf\ \xf7\x8f\xa3\x20\x74\x88\x7c\x33\x6c\x2f\xd7\x50\x0d\x64\x81\x0f\ \xb4\xb8\xf2\xf2\xd6\xad\x62\xe9\x86\x02\x2b\x76\x32\x20\xeb\xa7\ \x92\xbc\x3b\x93\xa6\x2a\xb3\x4c\x4e\xae\x09\xad\x6e\xa3\x36\x2c\ \x14\x7a\x11\xca\xc7\x9e\x12\x31\xb4\x9a\xff\x2f\xc7\xd3\x35\x8d\ \xdf\xe8\x52\x39\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \ \x00\x00\x0c\x01\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x30\x00\x00\x00\x30\x08\x06\x00\x00\x00\x57\x02\xf9\x87\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ \x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x0b\x7e\x49\x44\ \x41\x54\x68\x81\xb5\x9a\x79\x70\xd5\x55\x96\xc7\x3f\xe7\xf7\x7e\ \x2f\xfb\x1e\x08\x21\x0b\x90\x8d\x80\x49\x34\x31\x09\x8b\x10\xe8\ \xb8\x01\x5a\xb6\xb4\xd6\xa8\x88\x38\x8e\xd8\x3d\x88\xe5\x14\xf6\ \xbe\x59\xd3\xce\x54\x69\x6b\xd3\x5a\x25\x53\xad\xe8\xb4\x82\x2c\ \xda\x2d\x22\x4d\xf7\x40\x43\x77\xc9\xe2\x02\x91\x10\x76\x12\x48\ \x08\x31\xfb\x9e\x97\x3d\x6f\xfb\xdd\xf9\xe3\x25\x21\x21\xbf\x97\ \x0d\xfc\xfe\x45\xce\x3d\xf7\x9c\xef\xb9\xef\xfe\xce\xbd\xe7\x5c\ \x84\x9b\x80\xf7\x2f\xa8\x4c\x7a\x8c\x67\x7a\x5d\xb2\xd8\xee\x96\ \x38\xbb\x93\x10\x97\x81\xee\x34\x10\x01\x74\x0d\x35\x27\x4a\x35\ \x25\x44\x70\x5a\x50\x25\xa0\x8e\xbb\xac\x96\x43\xf3\xa2\xa4\xfe\ \x46\x7d\xcb\x64\x27\x6e\x3d\xad\x66\xf7\xd9\xd5\xab\x9d\x76\xb9\ \xa7\xb3\x8f\x40\x35\x8a\xae\x06\xa4\x46\x29\x52\xa7\x5e\xa7\xa5\ \xd4\x09\x44\xb6\x39\x95\xb6\x73\x7e\x9c\xb4\x4c\x86\xc7\x84\x03\ \xd8\x5a\xa4\x16\x76\xf4\xa9\x77\x5a\xba\x25\xdd\x30\x61\x1d\xe2\ \x07\x53\x03\x15\xb6\x5e\x21\x22\x40\x11\xee\x0f\x15\x6d\x70\xdb\ \x74\x45\xa0\x8f\x57\xb3\xdd\xa2\x78\x57\x94\xb6\x31\x33\x5e\x6a\ \xbe\x95\x00\xfe\xfc\x95\xf2\x6f\xd6\xd4\x9e\xc6\x4e\xb9\xd7\x8c\ \xf8\x00\x74\x0d\xe6\xcf\x50\x04\xfb\x2a\x7c\x75\x38\x51\x25\xd4\ \x76\x08\xa1\x7e\x90\x9f\x64\x8c\xe5\xa6\x47\xc1\xef\x42\x7a\xb4\ \x57\x52\x52\xc4\x7e\xd3\x02\xd8\x71\x5a\x2d\x33\x0c\xb5\xa5\xd2\ \x26\xd1\x76\xd7\xe8\xba\xba\x06\x77\x26\x1b\xf4\x38\x60\x4a\xa0\ \x47\x66\x77\x41\x6b\x8f\x30\x3d\x64\xb4\x8d\x36\x0c\xa7\x35\xa5\ \x3d\x9a\x19\x27\x97\xc7\x52\xd4\xc6\x52\xf8\xf8\xbc\xfb\xa7\xfe\ \x16\xe3\x2f\x55\x36\x89\xbe\x33\xc9\x20\x3e\xd4\x9c\x44\x90\x0f\ \x4c\x0f\xf1\xac\x7a\x79\x8b\x60\x1d\x62\xd9\x57\x67\x04\x79\x97\ \xdb\x3d\x9a\xdb\x4c\xb7\x18\x85\x27\x6b\x5d\xdf\xbb\xa1\x00\xde\ \x39\xae\x3e\xf0\xd5\xe4\xe5\x8b\x8d\x9a\xaf\xdd\x05\xa7\x6a\x85\ \xea\x0e\x61\x5a\x90\xf2\xa4\x17\xc0\xde\xdd\xc1\x14\xe7\x15\xc2\ \x7a\x4b\xe8\xad\x3d\x4f\x40\xe7\x05\x1c\xcd\x97\x69\x69\xaa\xc3\ \xe1\x74\x9a\xda\x3d\x76\xaa\x98\xb5\x3f\x7d\x7d\x54\x62\x02\xc1\ \xa2\xe4\xe3\xa2\x6a\xf7\xba\xd1\xf4\x74\x6f\x03\x7f\x3c\xa1\xb6\ \x56\xb6\xb1\xa6\xa9\x47\x70\xb8\x21\x22\x00\x3a\xfa\x14\x15\x65\ \x97\x39\x7a\xb5\x94\xaa\xab\xa5\x34\x35\xd4\xe0\xf4\x42\x12\xe0\ \xd7\xcf\xaf\x66\xf9\xd2\x9c\xc1\xbf\x6b\x1b\x5a\x78\x73\xcb\xa7\ \x7c\x71\xe2\x02\x7e\xbe\xde\xbf\xe8\x21\xb0\x20\xbc\x55\x54\xe3\ \xb6\xde\x1e\x6b\xd9\x34\xee\x00\xde\x3f\xe1\x7e\xad\xc2\xc6\x93\ \x0a\xcf\xfe\x75\x3a\xec\xfc\xe3\xf8\x71\x4e\x1e\x3b\x4c\x87\xad\ \x75\x3c\x8e\x87\xc1\xee\x70\xb2\x6b\xdf\x51\xb6\xec\x3a\x48\x6f\ \x9f\x63\xc2\xf3\x81\x37\x0e\x95\xba\xda\xf2\x53\xf4\xed\xd7\x0f\ \x8c\x08\x60\xfb\xd7\xea\xae\xf2\x76\x7e\xac\xfa\xb7\xec\xb9\x93\ \xc7\x38\x7c\xf0\x2f\xd8\xfb\x7a\x27\xe3\x98\x2b\xdf\xd4\xf2\x8b\ \xd7\xde\xa3\xb6\x61\x52\x69\x7e\x00\x96\x0e\xa7\xf6\xee\xce\x0b\ \xaa\xf6\xf1\x34\xf9\x6c\xe8\xc0\xb0\x6f\xe0\xaf\x85\x2a\xa0\xa1\ \x4f\xed\x75\x19\x88\xdb\xed\xe2\xff\x3e\xd9\xc6\x81\xbd\x1f\x4d\ \x9a\x3c\x40\x69\x45\xed\x8d\x92\x07\x20\x3e\x54\xf9\xa5\x84\x1a\ \xbf\xff\xe0\x80\x0a\x1c\x2a\x1f\x16\x40\x9d\x9b\xdd\xed\x7d\x12\ \xa0\x94\xc1\xbe\x4f\xb6\x51\x7c\xb6\xf0\x86\x1d\xdf\x2c\x28\x05\ \x65\xcd\x92\xe9\x08\x57\x3b\x87\xca\x07\x03\xf8\xe8\x8c\x4a\x6f\ \xec\xe2\x5e\x80\x63\x87\x0f\x72\xe9\xc2\xe9\x51\x0d\x66\xa5\x25\ \xf3\x83\x55\xf7\xf3\x9b\x17\x9e\xc4\x47\xf7\x9a\x0b\x6e\x1a\x8e\ \x55\x0a\xc5\x8d\x42\x7d\xa7\x3c\xb0\xfb\xa4\x9a\x3b\x20\x1f\xf4\ \x6c\xeb\x51\x5b\x1d\x6e\x91\x8e\xd6\x06\x0a\x3e\x3f\xe8\xd5\xd0\ \xec\xc4\x38\x7e\xbe\xee\x51\x66\x27\xc6\x0d\xca\x7e\xfb\x87\x8f\ \x60\x8c\x03\xee\x46\xd0\xde\x07\x8d\x5d\x9e\xbc\xed\x70\x23\x0d\ \x0e\xb5\x1d\xc8\x86\xfe\x5f\xa0\xa0\x5a\x45\xde\x12\xcd\xdc\xac\ \x18\x45\x59\xe1\xdf\x71\x7b\x39\x64\xf2\x72\xd3\xd9\xfc\xf2\x86\ \x61\xe4\xbf\x6d\x38\x0d\x28\x6e\x18\x7e\x61\x68\xee\x91\xac\x3f\ \x5f\x50\x33\xa0\xff\x17\xb0\x6a\xc6\x6a\xab\x0f\xfe\xe2\xea\xe5\ \xf3\x82\x33\xa6\x86\x66\x27\xc6\xf1\xd2\x8f\xfe\x15\xab\x6e\xb9\ \x29\xc4\xe6\x26\xc7\x0f\x27\xd5\xd6\x4e\x6d\x43\x0b\xad\xb6\x2e\ \x34\x11\x66\xc6\x46\x11\x18\x1e\xc5\xd7\x55\x16\x1c\xd7\xad\xa7\ \xd3\x8d\x74\x76\x1b\x2f\x03\x4f\x78\xb6\x90\x52\x6b\x40\xf8\xaa\ \xa8\x18\xa7\x6b\xe4\xea\x8b\x08\x2f\xac\x7d\xe8\xa6\xec\xf5\x88\ \xf0\x10\xd6\x3f\xf1\x00\xcb\x96\x64\x53\xd7\xd8\xca\xa7\x07\xbe\ \xe4\xf3\xaf\xcf\x51\x55\xd7\x34\x42\x37\x34\x2c\x82\xec\x3b\xbe\ \x43\x66\x6e\x1e\x9a\x36\xfc\xd2\x60\xeb\x93\x07\x01\xf4\xc2\x0a\ \x35\x1d\x8c\x6c\x80\xe2\xb2\x4a\x53\xa7\x73\x92\xe2\xc9\x48\x4d\ \xb8\x21\xe2\x16\x8b\xc6\xc3\x2b\xf2\x58\xfb\xc8\x32\x00\x36\x6d\ \xd9\xc3\xee\xbf\x7f\x39\xea\x9d\xa8\xdd\xd6\xca\x67\xfb\x76\x53\ \x56\x72\x9e\x95\x8f\xad\xc5\xc7\xd7\x6f\x70\xac\xa3\x4f\x82\x76\ \x9d\x55\x73\x74\x8b\x8f\x3b\x5f\x29\xcf\xcd\xa6\xb4\xa2\xd6\xd4\ \x50\x5e\x6e\xc6\x0d\x91\xcf\x4a\x4b\xe2\x85\xb5\x0f\x93\x10\x1f\ \xcd\xfe\xc3\x27\x78\x7b\xc7\xdf\x68\xb5\x75\x8e\x7b\x7e\x65\xf9\ \x65\xf6\xef\xde\xce\x83\x8f\xad\x65\xe0\x12\xa6\x80\x1e\xa7\xf1\ \x6f\xba\x52\x32\x1f\xa0\xca\x26\x54\x37\xf5\x98\x1a\x48\x4e\x88\ \x99\x34\xf9\xbc\xdc\x34\x96\x2f\xcd\xe1\x72\x79\x35\xcf\xfe\xea\ \x4d\xce\x5f\xae\x98\x94\x9d\xd2\x92\x73\x5c\x2a\x3e\x43\xea\x2d\ \x99\x83\xb2\x5e\x97\xe4\xeb\x55\x36\xc9\xeb\xe8\x83\x8a\x36\xc1\ \xe9\xe8\x33\x9d\x1c\x19\x1a\x3c\x61\x87\x33\x62\xa7\x91\x9a\x18\ \x87\xdb\x50\x6c\x7c\xe7\x63\xf6\xfe\xf3\x38\x86\x31\x66\x41\x33\ \x2a\x0a\x8e\xfe\x63\x58\x00\xdd\x0e\x49\xd0\x4b\x9a\x24\xbd\xbb\ \xff\x7e\xe5\x6b\x35\xbf\x5d\xfb\xfb\xfb\x8e\xdb\x89\xbf\xbf\x2f\ \x4f\xff\xcb\x32\x1e\x5e\xb1\x98\xfd\x87\x4e\xb0\xf9\xc3\x7d\x74\ \x74\x76\x7b\xd5\xcf\xce\x48\x61\xd9\x92\x1c\x66\x27\xc6\x22\x08\ \x27\xcf\x95\xf2\xfe\xae\x83\x74\x76\x8d\xdc\x0d\x8d\x75\xd5\xb4\ \xb5\x36\x13\x1e\x31\x05\x00\xb7\x41\xb0\x9e\x13\xaf\x7a\x8e\x55\ \x48\xa8\xc3\x0d\x4a\x33\xbf\xe2\xda\xda\xbb\x98\x11\x13\x35\x2a\ \x71\x11\xe1\xde\x25\xd9\xac\x5f\xf3\x00\xf5\x4d\xad\x3c\xfb\xeb\ \x4d\x5c\xba\x52\xe5\x55\x3f\x69\x66\x0c\x1b\x9e\xfe\x1e\x59\x69\ \xc9\x23\xe4\xfe\x53\x66\xb1\xf1\x8d\x4d\xa6\xe7\xd1\x37\x57\x4a\ \x08\x8f\x58\x0c\x80\xcb\xad\xac\x7a\xb8\x9f\xf2\xbf\x2b\x59\x51\ \xdf\x25\x7c\x1a\x18\x40\xa3\x89\xb3\x86\x16\xdb\xa8\xe4\x67\x27\ \xc4\xb1\xee\x89\xfb\x89\x9f\x3e\x95\xb7\xb6\xff\x8d\xfd\x87\x4f\ \xa0\x94\x97\xca\x2d\xc0\x9f\x67\x1e\x5b\xc1\xca\x65\x77\xa0\x5b\ \xcc\xcf\x94\x94\xc4\x59\x24\xa5\xa6\x73\xf9\xe2\xc8\x33\xa9\xb9\ \xb1\x6e\xf0\xdf\x0e\x43\x3c\xc9\xd5\x57\x87\x99\x61\x8a\xf4\xc4\ \xa9\xa6\x06\x8f\x1e\x3f\x8b\xdd\x05\x15\xad\xc2\x85\x7a\xe1\x62\ \x83\xd0\x37\xe4\xea\xf0\xe6\x4b\xeb\xb9\x5c\x5e\xc3\xaa\xe7\x5f\ \x61\xdf\xa1\xaf\x4d\xc9\x8b\x08\xf7\xe5\xcf\xe3\xc3\x4d\xbf\x60\ \x76\x62\x2c\xff\xf1\x9f\x7f\xf0\xba\x20\x33\xc3\x15\xe9\xe9\xb7\ \x9a\x8e\xb5\x36\x0f\x5f\x62\x1d\xe8\x02\x22\x00\x12\x67\x4c\x37\ \x9d\xf4\x45\xe1\x79\x76\x1c\xa9\x24\x22\x7a\x16\xfe\x3a\xf4\xba\ \xe0\x4a\x8b\xe0\x6f\x85\x8a\xab\x65\xec\xff\xeb\x27\x34\xd4\x99\ \xa7\x60\x80\xd4\xa4\x78\x7e\xf8\xcc\x43\x44\x4f\x8d\xe0\x7f\x3e\ \xd8\xcb\xc1\xa3\x27\xf1\xf5\xb1\x7a\xd5\xf7\xb7\xc2\xd2\xb4\x30\ \x76\x9b\x8c\xd9\x7b\xaf\x7d\x1b\x3e\x9a\x32\x74\x84\x36\x94\x27\ \x80\x79\x99\xa9\xa6\x06\x9d\x2e\x37\x3b\xb7\x6c\x66\xc1\x92\x7b\ \x99\x91\x90\x82\x52\x8a\xc6\xba\x6a\x2e\x9e\x2d\xa4\xaa\xa2\xcc\ \x2b\x91\x90\xe0\x40\xfe\x7d\xd5\x7d\xac\xc8\xcf\xe5\x93\xfd\x5f\ \xb0\xe1\xbf\xde\xa6\xb7\x77\x5c\xdd\x12\xa6\x86\x07\x99\x73\x71\ \x5e\xab\xe8\x74\x8b\x38\x75\xa5\xe4\x8a\xa0\x92\x00\xe2\xa7\x4f\ \x25\x65\x56\x2c\xa5\x15\x23\x7b\x4b\x7d\xbd\x3d\x1c\x3e\xb0\x67\ \x5c\xce\x35\x4d\xe3\xfe\xbb\x16\xf0\xec\xea\xfb\x69\x69\xeb\xe0\ \xa9\x1f\xff\x9e\xca\x9a\x86\x71\xcd\x1d\x40\x4f\xaf\x79\x4a\x37\ \xfa\x3f\x6c\x4d\x20\xc2\x9f\x1a\xcd\xd3\xab\xbc\x86\x35\x0f\xdf\ \x33\x21\x47\xd7\x23\x26\x7e\x16\xab\x7f\xf0\x23\xd2\x96\x3c\x4a\ \x7d\xa7\xc6\xa5\xf2\xea\x09\x93\x07\x68\xb5\x75\x99\xca\x23\x43\ \xfc\xb9\x63\xa6\x62\x79\xaa\xc1\xed\x31\xc6\x5e\x0d\xd4\xf1\xa1\ \x0a\xf9\x0b\x6e\x25\x2f\x37\x7d\xc2\x0e\x03\x82\x82\x59\xb1\xf2\ \x71\x1e\x5f\xbb\x01\x8b\xc5\xc2\x8e\xf7\x36\xb1\xe7\xc8\x05\x26\ \x7b\x76\x95\x56\x54\x9b\x07\x10\x16\x48\x54\x90\xc2\xc7\x02\x06\ \xea\x98\x6e\xd5\x2d\x9f\x39\x5d\x86\xa2\xbf\x4b\x27\x22\xbc\xf8\ \xfc\x6a\x7e\xfe\xda\x7b\x14\x9d\x2f\x1d\xd3\x91\x66\xb1\x90\x95\ \xbb\x98\x45\xf9\x2b\x00\x38\x74\x60\x0f\xa7\x0a\x8e\x62\x18\x06\ \x19\x99\x0b\x68\x99\x64\x39\x7d\xa4\xe0\x9c\xa9\x7c\x48\xa2\x51\ \x3e\x56\xcb\x11\x3d\x63\x9a\x34\x14\x55\xbb\x0a\x11\xc9\x1d\x18\ \x09\x08\xf0\xe3\x8d\x17\xd7\xf1\xda\xce\xaf\xf8\xe2\xe8\x61\xda\ \xdb\x46\x16\xe5\x11\x53\xa6\x91\x3c\x27\x83\xcc\xdc\x45\x84\x84\ \x86\x73\xf1\x4c\x21\x47\xfe\xb9\x97\xee\xce\x8e\x61\x7a\xd5\xed\ \x13\x6f\x80\x9f\xbe\x78\x85\xd2\xab\xe6\x3d\xde\x5b\x52\x66\x0e\ \xf0\x2f\xc8\x98\x26\x0d\x9e\x0b\xbe\xc8\x36\x20\x77\xa8\xa2\xc5\ \xa2\xb1\x68\xf1\x12\xe6\x66\xe5\xd1\xd6\xda\x44\x6b\x4b\x13\x86\ \xdb\x4d\x60\x70\x08\x61\x61\x91\x04\x04\x5d\xbb\x1f\xfd\x69\xcb\ \x26\xaa\xae\x9a\x67\x23\x35\xc1\x2d\x64\x77\x38\x79\xfd\x7f\x77\ \x9b\x9e\x25\x3e\x56\x2b\x0b\xb2\x3c\xe5\xb0\x20\xdb\xa0\xbf\x22\ \x73\x2a\x6d\xa7\x55\x8c\x57\x80\xc1\x96\x85\xd3\x80\x2e\x3b\x20\ \x42\x78\x64\x14\xe1\x91\xde\xaf\x12\xf5\xd5\xe6\x75\xc4\x68\x70\ \x1b\xd0\xda\xe3\xe9\xf8\x0d\xc0\xe1\x72\xf1\xe2\xc6\x2d\x94\x57\ \x9a\x9f\x29\x77\x2d\xca\x24\xc0\x73\x2f\xeb\x76\x28\xed\x4f\xd0\ \x5f\x13\xcf\x8f\x93\x16\x51\xbc\x3b\x54\x59\x17\xcf\x09\xfd\x6d\ \xe2\xe8\x55\x8d\x2f\x2b\x3c\x5b\xec\x62\xd9\x37\x7c\xff\x67\x6f\ \xf0\x55\xd1\x45\x53\x5d\xab\x6e\x61\xcd\x43\x77\x7b\xfe\x10\xde\ \x1e\x78\x10\x19\xa4\x28\x4a\xdb\xa8\xc4\xf8\x3e\xfd\xbf\x82\x08\ \x2c\x9a\x65\xf0\x59\x99\x36\xa2\x26\xbd\x19\x30\x0c\x83\xe2\xb3\ \x85\x14\xd8\x5a\xd8\x51\x53\xc2\xd9\x92\xf2\x51\xf5\x9f\x7e\x64\ \x39\x33\x62\xa2\x50\xd0\xe9\xd6\xb5\x8d\x03\xf2\xc1\x00\x32\xe3\ \xa5\xa6\xa8\xc6\xfd\xdf\xc0\x6f\x07\x64\x7e\x3a\xcc\x8d\x52\x9c\ \xa9\x9b\xf4\x4b\x94\x57\x0c\x74\xfe\xc6\x83\xa5\x0b\x6e\x65\xf5\ \xca\x3b\x01\x10\xc5\x4b\x43\xdf\xd6\x86\x15\x00\x46\x9d\xf6\xba\ \x82\x53\x43\x65\xb3\xc2\x15\xc1\xe3\x2f\x07\x6e\x3a\xee\xcb\x9f\ \xc7\x6f\x36\x3c\x39\x50\xd4\x17\x19\xf5\xda\x9b\x43\xc7\x87\x05\ \x90\x93\x23\x4e\x0c\xed\x11\x60\x30\x17\x8a\x40\x56\xcc\xc4\x4f\ \xa3\xd0\xf0\x48\xa6\x46\xc7\x4e\x8e\x35\x10\x12\x14\xc0\x2f\x9f\ \x5b\xc5\x2f\x9f\x5b\x35\xd0\xca\xe9\xb2\xa0\x3d\x9e\x93\x23\xc3\ \xfa\xf9\x23\x3e\xd3\xec\x78\x29\x3b\x59\xeb\x7a\x4a\x94\x7c\x0c\ \x58\xc0\x93\x29\xe2\x42\xd5\xb8\x72\xba\x6e\xb5\x72\xfb\xfc\xa5\ \x2c\x5c\xba\x0c\xab\x8f\x0f\x4d\xf5\x13\x7a\xb3\x23\x66\x5a\x24\ \x0f\xde\xb3\x90\xef\xde\xb3\x90\xe0\xc0\xc1\x14\xe5\x06\xb5\xfa\ \xb6\x58\xb9\x34\xc2\x9f\x99\x91\xec\x18\xfd\xd3\x93\xb5\xee\xf5\ \xa2\xd8\x3c\x20\xcb\x89\x53\xc4\x86\xc0\xa5\x66\xc1\xe6\xe5\x74\ \x4d\x9e\x93\x4e\xfe\xf2\x87\x08\x0d\x8f\x1c\x37\x61\x7f\x3f\x1f\ \x92\x67\xc6\x92\x9d\x91\x42\x76\x46\x0a\xb7\xcd\x4d\xbc\xbe\x07\ \xa4\x44\xd4\xba\xac\x18\x7d\xaf\xd9\xfc\x51\x97\xf4\x54\xb5\xeb\ \x29\x25\xf2\x2e\x43\x02\x55\xca\xf3\xd4\x54\x69\xbb\x36\x75\xeb\ \x5b\xaf\x92\x77\xf7\x03\x24\xa6\xdc\x32\xc2\x86\xcb\xe5\xc4\xd6\ \xda\x4c\x6f\x4f\x37\x2e\xa7\x03\x43\x29\x74\x8b\x4e\x4c\xa4\x1f\ \x77\xa7\x87\x11\x3e\x7a\xc3\xc0\xad\x14\xcf\x65\xc7\x59\x36\x7b\ \x53\x18\x73\x4f\x9c\xaa\x75\xad\x34\x94\x7c\x20\x30\xe8\xa9\xbd\ \x17\x0e\x95\x5f\x5b\x25\xc3\xed\x46\xf3\x52\x1e\x7a\x43\x5a\xb4\ \x22\x25\x72\xd4\x57\xcb\x0e\x50\x6b\x6e\x8f\x35\x5f\xf9\x01\x8c\ \xf9\x4a\x99\x15\xa3\xef\xd1\x0c\x2d\x7b\x68\x76\xf2\xbf\xae\xf6\ \x9f\x28\x79\x1f\x0b\x24\x84\x8d\x4a\xbe\x48\x19\x5a\xf6\x58\xe4\ \x61\x1c\x01\x00\x64\xc5\x4b\x69\x47\x8c\x36\x4f\x84\x0d\x0a\x3a\ \x2d\xe3\x9a\x35\x12\x91\x81\x90\x1d\xa7\xf8\x4e\x92\x81\x97\x1e\ \x71\x8f\x82\x97\x7c\x6d\xda\xc2\xec\x78\xf1\x5e\xea\x0d\xc1\x84\ \x4f\xa8\xc2\x0a\x35\xdd\xe2\x63\xfc\x64\x5f\xb1\xb6\xc1\xee\x1e\ \xff\xfc\xb4\x69\x8a\x94\x29\x5e\x57\xbd\x1b\x61\xb3\x4b\xd7\x7e\ \x37\xd1\xff\x00\x32\xe9\x23\xf6\xc3\x53\x6a\x56\x97\xd3\x78\xb5\ \xa3\x4f\xee\xeb\xb0\x4b\x90\x97\x2e\xca\x20\x16\x27\x18\x4c\x09\ \x18\x26\x52\xa0\x0a\x14\xb2\xdd\xcf\xd0\x3e\x4c\x8b\x97\x89\x3f\ \x7f\x72\x03\x01\x0c\xc5\xae\xb3\x6a\x4e\xbb\xdd\x58\xef\x72\x69\ \x77\xf4\x38\xd5\x4c\xbb\x21\x21\x2e\xb7\xb2\xba\x0c\x11\x05\x58\ \x35\x8c\xc5\x09\xc6\x37\x21\xbe\x72\x49\x94\x2a\x31\x44\x1d\xd7\ \x2d\x96\x43\xb7\x45\x8b\x59\x1b\x6a\x42\xf8\x7f\xd2\xb5\x8d\x07\ \x16\x9e\x74\x46\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \ \x00\x00\x19\xd5\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x60\x00\x00\x00\x60\x08\x06\x00\x00\x00\xe2\x98\x77\x38\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x02\x0b\x00\x00\x02\x0b\ \x01\xf7\x4d\xfa\xeb\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x19\x52\x49\x44\ \x41\x54\x78\x9c\xe5\x9d\x79\x60\x95\xd5\x9d\xf7\x3f\xe7\x79\xee\ \xbd\xd9\x57\x12\x02\xd9\x48\x08\x10\x20\xec\x84\x4d\x50\x36\x17\ \x16\x99\x22\x52\xec\x54\x5b\x44\x5c\xea\x54\xa7\xd5\xa9\xd3\xce\ \x74\x3a\xb5\x9d\xb7\x7d\x6b\x3b\x6f\xa5\x76\x46\xe5\xd5\xb8\x80\ \x5a\x14\x45\x05\x51\x51\x16\x95\x45\xd6\x40\xd8\x24\x21\x10\x42\ \xc8\x4a\xf6\xe5\xee\xf7\x39\xf3\xc7\x85\x90\xe5\xde\x9b\xfb\xe4\ \xde\x04\x3a\xfd\xfe\x95\x9c\xe7\x9c\xdf\x39\xf7\xfc\xce\xf2\x3b\ \xbf\xe5\x1c\xc1\x0d\x8a\x37\xf6\xcb\x54\x87\x91\x85\x4e\x8d\x5c\ \x97\x53\xe6\x38\x34\x91\x6e\x77\xc9\x38\xa7\x26\x8c\x2e\x0d\x55\ \x93\xa8\x0e\x0d\xa1\x00\xaa\x82\x34\x28\x38\x85\x82\x2b\x33\x4e\ \x56\x0c\x4b\xe0\x88\x40\x16\x69\xc8\x33\x8a\x50\x0b\x5d\x66\x8a\ \x72\xb3\x44\xd3\xf5\xfe\x4d\x9e\x20\xae\x77\x03\xae\xe2\x85\xaf\ \x65\x86\x41\xe5\x01\xbb\x4b\x2e\x32\xdb\xc5\xa8\x56\x3b\xe1\x52\ \xea\xa7\xa3\x0a\x98\x95\xa9\x11\x17\xd6\x39\x5d\x48\xce\x03\xdb\ \x41\x6e\x57\x5d\xea\xf6\x71\x43\x44\x43\x50\x1a\x1e\x20\xae\x2b\ \x03\x5e\xde\x27\x87\x49\x83\xf6\x74\xab\x4d\x2c\x6e\xb4\x8a\xd8\ \xde\x74\xb8\x27\xcc\xca\x90\x24\x44\xf8\x24\xe6\x02\xb9\x57\x08\ \xde\xc0\xa6\x6e\x9c\x98\x29\x1a\x83\x53\xb3\x7e\xf4\x3b\x03\x5e\ \xdd\x25\x43\x0d\xd1\x3c\xd9\x68\x95\x8f\xd4\x9a\x45\x7a\xb0\x3a\ \x3d\xcc\x00\x36\x27\x24\x44\x4a\x6e\x1a\xa2\x8b\xa8\x55\xc0\x66\ \x34\xf9\xfa\x84\x54\xf5\x53\x21\x84\x16\x9c\x16\xf9\x87\x7e\x63\ \xc0\x33\x7b\x64\x54\x84\xca\x1f\x9b\x2c\x72\xa5\xcd\x29\x8c\xc1\ \xa2\x1b\x61\x82\x11\x89\x92\xc4\x70\xc9\xbe\x52\x85\xf1\xc9\x92\ \x44\xdf\xa3\xdf\x17\x4e\x21\xe5\xef\x9b\x52\xd4\xb7\xe6\x0a\xe1\ \x0c\x56\x1b\x7d\xa1\xcf\x19\xf0\xcc\x19\x19\x15\xd5\x28\xd7\xd5\ \xb7\x89\x25\x76\x17\xaa\xde\xf2\x06\x15\x26\x25\x4b\xe2\xc3\x25\ \x56\x27\x14\x54\x28\x4c\x4c\xd1\x08\x35\xb8\xbf\x1b\x55\x68\xb1\ \xc2\xbe\x52\x05\x4d\xc2\x94\x34\x49\x5c\x98\xc4\xa0\x04\xd4\xec\ \x62\x90\xbf\x6b\x4a\x56\x5f\xef\x6b\x46\xf4\x29\x03\x9e\x3f\x20\ \x9f\x6a\x32\xf3\x1f\x16\x07\x21\xbd\xa5\x61\x50\x20\x3a\x14\x4c\ \xaa\x64\x5a\x9a\xa4\xc5\x0e\x8a\x80\x48\x93\xfb\x7b\x8b\x0d\x76\ \x97\x28\xd8\x5d\xd7\xca\xa4\x44\x4b\xa6\xa4\x05\x65\x6d\x2b\x10\ \x28\x3f\x9c\x98\x22\xf6\x06\x83\x98\x27\xf4\x09\x03\xde\x29\x90\ \x99\xa5\x8d\xf2\xcb\x16\x9b\x48\x0b\x16\xcd\x09\x83\x25\x19\xf1\ \x9d\x3b\x55\x4a\xf8\xaa\x44\xa1\xc1\xd2\x39\x6f\x46\x9c\x64\x42\ \x72\x90\x36\x17\x90\x48\xde\x50\x0d\xca\x4f\xc6\x0f\x12\x35\xc1\ \x22\x7a\x15\x41\x67\xc0\xd1\x0a\xe7\xd2\x36\xbb\x78\xf5\xb3\x22\ \x25\x36\x58\x34\x23\x4d\x70\xeb\x70\xdf\x7b\xa3\xc5\x09\x6d\x36\ \x28\xa9\x77\xff\xa4\x20\xcd\x80\x8e\xa8\x13\xc8\xd5\x13\x53\x0c\ \x1f\x06\x93\x68\xd0\x18\xf0\xcc\x19\x19\x15\xd9\x20\x77\x59\xec\ \x4c\x6e\xb5\x0b\xa2\x42\x40\x93\xd0\x6a\x77\x8f\xd4\x40\xa0\x08\ \x98\x31\x44\x62\x50\x24\x2e\x0d\x8e\x57\x2a\xcc\x1b\xd6\xaf\xc2\ \xca\x55\x48\xe0\xcf\x21\x8d\xca\x53\x39\x39\xc2\x1e\x0c\x82\x41\ \x61\xc0\xba\x7c\x39\x3b\xd2\x24\xf3\x92\x22\x65\xd6\xc9\x2a\x41\ \x45\x8b\x60\xd1\x48\x0d\xa3\xe2\xee\xfc\x03\x17\x05\x55\xad\xfa\ \xaa\x12\x40\xa8\x11\x2c\x8e\x6b\x69\x06\x15\x52\xa2\x24\x59\x03\ \x24\xd1\xa1\xc1\x68\x79\x2f\x21\xe4\x41\x21\xd4\x7b\x26\x0e\x16\ \x17\x02\x26\x15\x28\x81\xbc\x83\xf2\x21\x4d\xe3\xb9\x61\x89\x5a\ \xe8\xbe\x52\x05\x9b\x13\x42\x0d\x70\xc7\x08\x8d\xea\x56\xc1\xe9\ \x1a\x81\xd9\x0e\x4e\x3f\x07\x6c\x98\x11\x52\x63\x24\x99\x71\x92\ \xb3\xb5\x82\xb2\x66\x41\x66\x9c\x24\x29\x4a\x12\x1f\x0e\x81\x09\ \x37\x6e\x34\x34\xb5\x10\x17\x13\x15\x28\x99\x7a\xa4\x72\xe7\xa4\ \x54\xf1\x75\x20\x44\x02\x62\xc0\x4b\x07\xe4\xd3\x8d\x56\xf9\x8b\ \xb9\x59\x52\xd9\x7f\x51\x50\x73\x65\x94\x2b\x02\x26\xa7\x4a\x0a\ \x2a\x04\x76\x97\x5b\x92\x19\x9d\x24\x31\xaa\x70\xb2\x4a\x60\xf3\ \x20\xd8\x0d\x8c\x94\x64\x27\xba\x3b\xf9\x6a\xa3\x2e\xd4\x0b\x92\ \xa2\x24\x61\x41\x3a\x35\x38\x5d\x2e\x36\x7d\xba\x97\x8d\x5b\xbf\ \x64\xe3\xf3\xbf\x08\x06\xc9\x36\x29\xb4\xe5\x93\x93\x8d\x9f\xf6\ \x96\x80\xa1\xb7\x05\x5f\x38\xe0\x7a\xfe\x52\x33\x8f\x4e\x4e\x81\ \x26\x2b\x5c\x6e\xbb\xc6\x4b\x4d\x42\xe1\x65\xc1\xc0\x48\x49\x65\ \xb3\xc0\xa9\xc1\xf1\xca\xee\xbc\x76\xd8\xed\x34\xd4\xd5\x80\xb5\ \x11\x11\xd2\x44\xc9\x51\x33\xad\x66\x2b\x9a\x26\x69\x6d\xb3\x20\ \x71\x6f\x1e\x61\x21\x26\x42\x42\x8c\x44\x84\x85\x12\x16\x16\xca\ \xe0\x81\xf1\x0c\x49\x1e\x48\x72\xd2\x00\xbf\xdb\x7b\xf0\x58\x21\ \x6b\x5e\xd9\xc4\xc5\x8a\x1a\xa2\xa3\x22\x7a\xfb\xb3\xbb\x22\x42\ \x48\xe5\xc3\xfc\x72\xe7\xfd\x93\x52\x0c\x7f\xe9\x0d\x81\x5e\x31\ \x60\xed\x7e\xd7\xda\x8a\x66\xe5\x61\x29\xa1\xbc\x19\xea\xda\x94\ \xf6\x8d\x36\xdc\x04\xd3\xd2\x34\xea\xcc\x82\x0b\x0d\x02\xd7\x95\ \x74\x87\xc3\x4e\x65\xd9\x05\x2a\x2e\x95\x50\x59\x56\xca\xe5\x9a\ \x4a\x9a\x9b\x1a\x7a\xbd\x43\xe7\x8e\x1b\xc1\x9a\x7f\x7f\xb4\xc7\ \x7c\x55\x97\xeb\x79\xee\xb5\x0f\xf8\xea\xc0\x89\x5e\xd5\xe3\x07\ \x4c\x20\xde\xc8\x2f\x77\xca\x49\x29\x86\x0d\x7a\x0b\xeb\x66\xc0\ \x0b\x07\xe4\x6f\x2a\x9a\x79\xf8\x6a\xbf\x55\x36\x77\x1e\xd9\x66\ \x3b\xec\x3a\xe7\x5e\xa9\x2d\x6d\x6d\x14\x9e\x3a\xca\xf9\xa2\x53\ \x5c\x2c\x39\x8b\xd3\xe9\xe8\x4a\xae\xcf\x60\x77\x38\xf8\xcb\xe6\ \x5d\xac\xdf\xb4\x03\xab\x2d\x28\x02\x8b\x2f\x28\x08\xb1\xee\x48\ \x85\xa3\x61\x72\xb2\x71\x9b\x9e\x82\xba\x18\xf0\xda\x21\xf9\xf0\ \x85\x46\xfe\xb5\xa7\x41\x5b\x7a\xae\x90\xe3\x47\xf6\x51\x7c\xe6\ \x04\x2e\x97\xcb\x77\xe6\x3e\xc0\xbe\xfc\xd3\xfc\x29\xef\x7d\xca\ \xab\x6b\xfb\xaf\x52\x89\x51\xa0\x6c\xca\xbf\x24\x6f\xd5\xb3\x31\ \xfb\xcd\x80\x75\x05\x72\x5e\x69\xad\x7c\x51\x93\xde\xf7\xed\x4b\ \xa5\xe7\xd9\xbb\x73\x2b\x65\x17\x8a\xfd\x25\x1b\x54\x54\x54\xd7\ \xf1\xe2\x1b\x5b\xd8\xf9\x75\xc1\x75\xa9\x1f\x08\x6f\x75\x68\x1f\ \xad\x3f\x22\x97\x7e\x6f\xb2\xd8\xed\x4f\x01\xbf\x18\xb0\xf6\x73\ \x19\x53\xd9\xc0\x16\x87\x4b\x78\xec\xfd\xc6\xfa\x5a\x3e\xdb\xf2\ \x36\x17\xcf\x17\xe9\x69\x6c\xd0\x60\xb3\x3b\x78\xe3\xfd\x1d\xbc\ \xf9\xc1\x4e\xec\x8e\xfe\x5b\xe6\x3c\x21\xdc\x48\x7c\xbd\x55\x6e\ \xc9\xdb\x23\xd3\x56\xcf\x12\x2d\x3d\xe5\xf7\x8b\x01\xf6\x08\xf6\ \xb5\xb5\x12\xde\xed\x83\x94\x1c\xda\xb7\x93\xbd\xbb\x3e\xc1\x79\ \x1d\x7f\xf8\xd3\xcf\xae\x63\xf7\xa1\x93\xd7\xad\xfe\x8e\x50\x04\ \x84\xa8\xc4\x28\x06\x36\x03\x73\x7b\xca\xdf\x23\x03\x5e\x3a\x20\ \x7f\x5f\xd6\xc4\xe8\xae\xe9\x0e\xbb\x9d\x4f\xde\x7f\x83\xa2\xd3\ \xd7\x6d\xba\xb7\xc3\xd2\xf7\x9b\xac\x2e\x4c\x4d\x93\x80\x9c\x7d\ \xb4\xc2\xb9\x74\x62\xb2\xe1\x03\x5f\x79\x7d\x1e\x2c\x5f\xc9\x97\ \xa3\xab\x5b\xf9\x49\xd7\xf4\xd6\x96\x26\xfe\x92\xb7\xe6\x86\xe8\ \xfc\x1b\x18\xc2\xe1\x14\x79\xaf\xe4\xcb\x6e\x83\xb7\x23\x7c\x32\ \xa0\xd9\xc2\x56\xbb\xab\xf3\x69\xd9\x6a\x6e\x63\xe3\xba\xe7\xa9\ \xa9\x2a\x0f\x46\x23\xff\x57\xa3\xa4\x41\xc4\xb7\x9a\xe5\x66\x5f\ \x79\xbc\x32\xe0\xd5\xc3\xf2\x47\xf5\x6d\x64\x74\x4c\xb3\xdb\x6d\ \xbc\xf7\xe6\x5a\xea\x6a\xaa\x82\xd4\xc4\xff\x9d\x70\x6a\x50\x5c\ \x27\x38\x53\x23\xa8\x35\x8b\xac\xbc\xc3\xf2\x71\x6f\x79\x3d\x32\ \xe0\xd5\x12\x19\x5a\xd3\xca\xef\xba\xa6\x6f\xff\x68\x23\x95\x97\ \x4a\x83\xd2\x48\x21\x04\x43\x52\x93\xc8\x1d\x37\x82\x5b\xa6\x8d\ \xc5\x68\xd0\x6d\xad\xbc\x21\xd1\x62\x83\xed\x67\x15\x4e\x56\x5d\ \xd3\x02\xd4\xb6\xc8\x3f\xbc\x5a\x22\x3d\xea\x6f\x3d\x6e\xc2\xce\ \xcb\xda\x4b\x6d\x76\xa5\x53\x81\xa2\xd3\x05\x9c\x2e\x38\x14\x50\ \xe3\x84\x10\x4c\x9f\x30\x92\x3b\xe7\x4f\x67\xfc\xe8\xa1\xc4\x46\ \x47\xb6\x7f\x5b\xb4\xea\xdf\x70\xb4\xb4\x05\x44\xff\x7a\x43\x4a\ \x38\x5c\xae\x60\xed\xa2\x6c\x6c\x73\x88\x10\xad\x56\x7b\x1e\x78\ \xa0\x6b\x99\x6e\x0c\x78\xba\x44\x86\xd6\x96\x72\x4f\xc7\x34\x8b\ \xb9\x95\xcf\x3f\x7a\xbb\xd7\x0d\x13\x42\xb0\x70\xce\x14\xee\xbb\ \x6b\x3e\xe9\xc9\x03\x7b\x4d\xe7\x46\x47\x75\xab\xa0\xc9\xe2\xf9\ \x5b\x4d\x8b\xf8\xde\xda\xc3\xf2\xb1\x47\x72\x85\xb9\x63\x7a\x37\ \x06\xa4\xd5\xf2\xa7\x52\x3b\x9d\x14\xc0\x27\x0e\xec\xc4\xd2\xd6\ \xbb\xd1\x39\x70\x40\x2c\xff\xfe\xa3\xfb\x98\x30\x3a\xab\x57\xe5\ \xff\x9a\xd0\xe6\x43\x1a\xb6\x38\x85\x01\xa7\xf6\x67\x60\x75\xc7\ \xf4\x4e\x7b\x80\x94\x52\x64\xc4\x69\xf3\x26\xa5\x4a\x92\xa3\x25\ \xe1\x46\x48\x0b\x6f\xe1\xd0\x7e\xbf\x4e\xd5\xdd\x30\x32\x2b\x9d\ \xbc\xdf\x3f\xf9\x37\xd1\xf9\x0e\xed\x9a\x3d\xda\x1b\x9a\xac\xca\ \xbd\xef\x48\xd9\x69\xb3\xeb\xc4\x80\x63\xe5\xae\x45\x71\x61\x0c\ \x4b\x8f\x91\x4c\x4d\x93\xdc\x3e\x42\xe3\xd0\xbe\x5d\x58\xac\xfa\ \x0f\x3a\xa3\x86\xa5\xf3\xdc\x2f\x1f\x0d\x86\xe5\xe9\x86\x47\x9d\ \x19\xbe\x3a\xaf\xd0\xda\x43\x37\xb5\xda\x09\xb1\xe6\x6b\x3f\xeb\ \x98\xd6\x79\x06\x28\xca\xca\x8e\xff\x6b\x9a\x64\xdb\x97\x87\x75\ \x37\x68\xe0\x80\x58\x7e\xf7\xb3\xd5\x84\x87\x5f\x4f\xc3\x6d\xdf\ \xa3\xcd\x0e\xfb\x4a\x05\x7b\x4a\x14\x5a\x6c\xfe\x95\xa9\xb7\x28\ \x9d\x8c\x18\xed\x7b\xc0\xe1\x73\x32\x06\xa9\xdd\xd9\xf1\xe3\xa9\ \xb3\x17\xb8\x5c\xaf\xcf\xab\x5b\x08\xc1\xcf\xfe\xe1\x3b\x0c\x88\ \x8d\xd6\x55\xae\xbf\x30\x34\x3d\x99\x27\x1f\x5c\xe6\xf5\x7b\x45\ \x75\x1d\x45\x25\xe5\x94\x96\x57\x51\x53\xdb\x44\x4b\x9b\x19\xa7\ \xcb\x45\x88\xc9\x48\x54\x44\x38\x99\x69\x83\x18\x96\x91\xcc\xf0\ \xa1\xe9\x1c\xb9\x64\xa4\xde\xcb\xa6\xeb\x0d\x8d\x66\x52\x5e\x3b\ \x2c\xb3\xee\xcf\x15\xe7\xa0\x03\x03\x44\x88\xf6\x1d\xa0\x93\x53\ \xf7\x17\xbd\x50\xeb\xde\x3a\x73\x22\x53\xc7\x67\xeb\x2e\xd7\xd7\ \x88\x08\x0f\x63\xf5\x3d\x0b\xb8\x7b\xc1\x2c\x54\xf5\xda\xc4\x77\ \xba\x5c\x1c\x3a\x5e\xc4\xae\x7d\xc7\x38\x7c\xbc\x88\x9a\x3a\xff\ \x1c\xa5\xc3\x23\x22\xc8\xce\x99\xc4\xf8\x29\x33\x49\x18\x38\xd8\ \xef\x76\x68\x80\x4d\xd3\x9e\x06\xbe\x07\x1d\x19\x20\xe4\xbd\x5d\ \x6d\xf4\xfb\x8f\x9d\xf1\x9b\x30\x80\xa2\x28\xac\x5a\x71\x87\xae\ \x32\x7d\x0d\x21\x04\x0b\x66\xe7\xf2\xe8\x7d\x4b\x88\x8f\xbd\xb6\ \x1f\x99\xad\x36\xb6\x6c\xdf\xcf\xdb\x5b\xbe\xf0\xbb\xd3\x3b\xc2\ \xdc\xd6\xc6\xd1\x83\xbb\x29\x38\xbc\x97\xf1\xb9\x33\x99\x39\x6f\ \x11\xa1\x61\xdd\x15\xc6\x9e\xd0\x6a\x53\xda\x57\x1a\x03\x5c\x59\ \x7e\xd0\x6e\xea\x98\xc9\x6a\xb3\x53\x56\x71\x59\x57\xa3\xa6\x4f\ \x18\x79\x43\xc9\xf9\x23\x32\x53\x79\xe2\xc1\x65\x8c\xcd\xce\x6c\ \x4f\x73\xb9\x34\xde\xdf\xb6\x97\x57\x36\x6e\xa3\x39\x08\x07\x3f\ \x4d\xd3\x38\x7a\x70\x37\xe7\x8a\x4e\xb1\xfc\xbe\x1f\x10\x9f\x98\ \xd4\x63\x99\x66\x2b\xb1\x6b\x0f\xcb\xf4\x47\x72\xc5\x45\x03\x80\ \x12\xea\x9a\x0d\xa2\x93\x78\x74\xbe\xac\x12\x4d\xd3\xe7\x7d\xb6\ \x68\xde\x34\x5d\xf9\xfb\x0a\x51\x11\xe1\x3c\xf4\xdd\x45\x2c\xbd\ \x6d\x06\x8a\x72\x6d\xb9\x29\xf8\xe6\x1c\x7f\x7c\x79\x13\xe7\x4a\ \x2b\x82\x5e\x67\x73\x63\x3d\x6f\xe5\xad\x61\xd9\x7d\x8f\x90\x9c\ \x9a\xe1\x33\xaf\x94\x60\x70\x69\x8f\x00\x3f\x37\x00\x08\x29\xe6\ \x76\xb5\x34\x9e\x3d\xaf\x4f\xdb\xa9\xaa\x0a\x53\xc7\x8d\xd0\x55\ \x26\xd8\x10\x42\x70\xfb\x2d\x93\x79\xec\xfb\x7f\xd7\x49\xfc\xad\ \x6b\x6c\xe6\xf9\xf5\x5b\xf8\xec\xab\x23\xc8\x60\x45\x84\x78\x80\ \xd5\x62\xe6\x83\xb7\x5e\xe6\xfb\x8f\x3e\x45\x64\x54\x8c\xcf\xbc\ \x16\xa7\x58\xcc\x55\x06\x48\xc1\xbc\xae\x19\x4a\xab\xea\x74\x55\ \x3e\x72\x68\xfa\x75\x15\x3b\x47\x66\xa5\xf3\xe4\x43\xcb\x18\x3d\ \x6c\x48\x7b\x9a\xd3\xe5\x62\xe3\xd6\xaf\x78\x65\xe3\x36\x2c\x16\ \x3f\xe5\xc4\x00\x61\x6e\x6b\x61\xeb\x7b\xeb\x58\xb1\xf2\x87\x08\ \xe1\x5d\xdb\xdf\x6a\x17\xd9\x00\x86\x2b\xeb\xff\xd8\x8e\x1f\xcf\ \xd7\x0b\x0a\x2b\xf5\x35\x38\x2b\xc3\x7f\x49\x20\xd8\x78\xe0\xdb\ \x77\x30\x26\x3b\x13\x45\xb9\x36\x8d\x8f\x9c\x38\xcb\x1f\xf3\xde\ \xa3\xf4\x52\x75\xbf\xb7\xa7\xac\xa4\x98\xfc\x03\xbb\x99\x3c\x7d\ \xb6\xd7\x3c\x16\x27\xa1\x9b\x8a\xe5\x40\x83\xc1\x44\xb6\xd6\x41\ \xfc\xb1\x3a\xe1\x74\x8d\xc0\x66\xd1\x27\xe0\x5e\xcf\xcd\x77\xdc\ \xa8\xa1\xed\x7f\x57\xd7\x36\xf0\x5f\xaf\x7d\xc8\xae\xfd\xd7\xd7\ \x5a\xf7\xf5\x97\xdb\x18\x37\x69\x3a\x46\x93\xe7\xd8\x14\x29\xa1\ \xb1\x91\x85\x06\x4d\x75\x65\x23\x05\x1a\xd0\x6a\x85\x53\x35\x02\ \xa7\x0b\x6c\x36\xab\xae\x0a\x13\xe2\x7d\xaf\x79\xc1\x46\x4c\x54\ \x67\x91\xcf\xee\x74\xb2\xe1\xc3\x2f\x58\xb7\xe9\xf3\xfe\x70\xc4\ \xea\x11\x56\x73\x1b\xc7\xf3\xf7\xfb\x9c\x05\x4e\xa7\x76\x8b\xe1\ \x64\xa5\x72\x4f\x9d\x59\xd0\x68\xed\xec\x25\xe8\xb0\xeb\x63\x40\ \x78\x68\xaf\xa3\x90\x74\x21\x32\x3c\x8c\xd5\xdf\x59\xc0\xb2\x3b\ \x66\xb5\xa7\x7d\x7d\xf4\x1b\xfe\x94\xb7\x89\x4b\x55\xfd\xe8\x88\ \xe5\x07\x4e\xf4\xc4\x00\x29\x46\x1a\x1a\xad\x62\x7c\xd7\x10\x1f\ \x00\x45\xd5\xe7\xb5\x18\x62\x0a\x5a\xe0\xa3\x47\x28\x8a\x60\xd1\ \xdc\xa9\x3c\xf2\xdd\xc5\xed\x12\x4e\x45\x75\x1d\x7f\x7a\xf5\x7d\ \xf6\x1e\x3e\x15\x30\xfd\xb8\x98\x28\x66\xe6\x8e\x26\x33\x6d\x30\ \xc9\x49\x03\x08\x0b\x31\x51\xdb\xd0\xcc\xb1\xd3\xc5\xec\xd8\x77\ \xac\x57\x9b\x78\x6d\x75\x05\x8d\x0d\x75\xc4\xc6\x79\x76\x22\x76\ \x6a\xa4\x1a\x9c\x12\x8f\xea\xca\xb0\x10\x7d\x23\xda\xe1\xe8\xbb\ \x60\xc2\xd1\xc3\x86\xf0\xc4\x83\xcb\x18\x35\x2c\x1d\x08\xae\x23\ \xd6\x8c\x89\xa3\xf8\xfb\x6f\xcd\x65\xc2\xe8\xac\x4e\x67\x86\xab\ \x58\x30\x3b\x97\x1f\xdc\x7b\x27\xbf\x7f\xf1\x6d\xbe\x3a\xa8\xdf\ \xf7\xe8\x5c\xe1\x49\xaf\xb3\xc0\xe1\x12\xb1\x86\x99\xe9\x5a\xc5\ \x91\x72\x11\x53\xd5\xd2\xf9\x20\x20\x8c\x26\x5d\x15\x59\xec\xc1\ \x5f\x77\xe3\x62\xa2\xf8\xc1\xbd\x8b\x59\x38\x67\x6a\xbb\x84\xf3\ \xd5\x81\x13\xfc\xf9\xf5\x0f\xa8\xac\xa9\x0f\x88\xf6\xd8\xec\x4c\ \x1e\xbf\xff\x5b\x8c\x1e\x3e\xa4\xc7\xbc\xb1\xd1\x91\x3c\xfd\xe4\ \x2a\x1e\xff\xcd\x7a\x4e\x9d\x38\xa6\xab\x1e\x5f\x0c\xb0\x3b\x65\ \xa8\xc1\xa8\x12\x3e\x25\x4d\xb2\xaf\x54\x50\xd7\xe1\x64\x6e\xd2\ \xc9\x80\xfa\x86\x1e\xbd\xf0\xfc\x86\xaa\x2a\xdc\xbd\x60\x16\x0f\ \xdc\xb3\x80\xc8\x70\xb7\x7e\xf0\x62\x45\x0d\x6b\xf2\x36\x71\xb0\ \xa0\x30\x20\xda\xf1\xb1\x51\x3c\x7a\xdf\x12\x16\xcc\xce\x45\x78\ \xf6\xb4\xf4\x88\x46\xab\xca\xbc\x3b\xbf\x43\xe9\x85\x12\x5a\x5b\ \xfc\xd7\x10\x57\x55\x94\x79\xfd\x26\x11\xaa\x01\x88\x52\x05\xcc\ \x1c\xa2\x51\xd9\x22\xb8\xdc\xea\xb6\x6d\x9a\xbc\x88\x4f\xde\x50\ \x56\x19\x9c\x08\xce\x49\x63\x86\xf3\xc4\xea\x65\x64\xa6\x0d\x02\ \xdc\x4a\xb3\xd7\xdf\xfd\x9c\x77\x3e\xfa\x02\x87\xb3\xf7\x9e\xd6\ \xaa\xaa\x70\xf7\xc2\x9b\x59\xbd\xe2\x0e\x22\xc2\xc3\x7a\x2e\xd0\ \x05\x03\x22\x24\x61\xa1\x61\xe4\xde\x34\x97\x2f\xb6\xf9\x74\x76\ \xeb\x04\xbb\xd5\x42\x6b\x4b\x93\xc7\x93\xb1\x4b\x43\x31\x00\x91\ \xe0\xf6\x69\x4c\x89\x96\xa4\x44\x03\x48\xea\xb3\xe3\x38\xac\x23\ \xfa\xe9\x4c\xb1\x77\x4e\xfb\x83\xa4\x01\xb1\xfc\xe4\xe1\x6f\x33\ \x6f\xc6\xf8\xf6\xb4\xed\x7b\x8f\xf2\xdf\xaf\x7f\xa8\xdb\x26\xd1\ \x15\x13\x73\xb2\x78\x62\xf5\xdd\x0c\x4d\x77\x1f\x16\xed\x4e\x27\ \x26\x83\x3e\x21\x43\x00\x52\xc0\x98\x09\x53\xf9\xf2\xb3\xcd\x48\ \xe9\xbf\x9e\xac\xfe\x72\xb5\x37\x06\x08\xaf\xad\x18\x76\x65\x04\ \xfa\x8b\xd3\xc5\x17\x69\x69\x33\x13\x15\x71\x4d\x3e\x77\x68\x70\ \xa2\x52\x30\x3c\x41\xa2\x0a\x77\x94\xa3\xc9\x8b\xfb\xcf\xda\xdf\ \xfd\xb8\xbd\x53\xce\x5f\xac\xe4\xd9\xbc\xf7\x38\x7a\xea\x9c\xae\ \x36\x74\x45\x62\x7c\x0c\x3f\x5c\xf9\x2d\x6e\x9d\x39\xb1\x3d\x6d\ \xe7\xd7\x05\xac\x7f\xef\x73\x5e\xfd\xcf\x6e\x1e\x97\x3e\xa1\x08\ \x18\x91\x20\x29\x94\x11\x0c\x4e\x49\xa7\xe2\xd2\x05\xbf\xcb\xd6\ \x5d\xae\x26\x7d\xa8\x67\x3d\x99\x01\x68\x05\xe2\xbb\x7e\x18\x92\ \xa2\xef\x64\xab\x69\x1a\x1f\xef\x3a\xc4\xf2\xc5\xb3\xb9\xd4\x28\ \xa8\x35\x43\x4d\xab\xc0\xe1\x82\xc8\x10\x28\xaa\x15\x20\x61\x4c\ \x52\xf7\x88\x77\x4d\x82\xaa\x1a\x68\x33\x5b\xc8\x7b\x67\x1b\xef\ \x7d\xb2\x1b\x97\xab\xf7\x71\xc0\x46\x83\xca\x8a\x3b\xe7\xb0\x72\ \xf9\x6d\xed\xe7\x93\x92\xb2\x2a\x9e\xcd\xdb\x44\xfe\xc9\xb3\xbd\ \x8e\x11\x1b\x35\x50\x62\x52\x21\x29\x39\x4d\x17\x03\x9a\x9a\x3c\ \x0b\x0c\xaa\x82\x34\x00\x2d\x78\x60\x40\xe2\x80\x18\xc2\xc2\x42\ \x74\xc9\xbf\x1b\x36\xef\x62\x68\xce\x34\xca\xcd\xd7\x66\x41\xb8\ \x09\x8a\x6b\xdd\xa7\x6b\x80\x63\x95\x82\xc2\x5a\x81\xc0\x1d\x8e\ \x6a\x34\x40\x61\x35\x9c\x3c\x76\x88\xdd\xdb\x37\xd3\xd4\x1c\xd8\ \x66\x3e\x75\x7c\x36\x3f\x5e\xbd\xac\x5d\x35\xd2\x6a\xb6\xf0\xca\ \xdb\x9f\xf2\xde\xa7\x7b\x02\x62\xea\x55\x64\x0d\x90\x4c\x1c\x96\ \xc8\xd1\x83\xfe\x97\x71\xd8\x3c\xf7\xa1\xaa\xa0\x5d\x9d\x01\xdd\ \x20\x84\x60\xe4\xd0\x54\x5d\xcb\xc0\xe5\xfa\x26\xd6\xbe\xf6\x36\ \x8b\xbf\xbd\xb2\x5d\x13\x18\x6a\x00\x2b\x74\xba\x4c\xe3\x6a\xf0\ \x75\x51\xad\xa0\xe2\xd2\x05\xbe\xfc\xf4\x03\xca\xcb\x4a\xfc\xae\ \xc7\x13\x06\x0f\x8c\xe7\xf1\x95\x4b\xb9\x65\x9a\x5b\xaf\xa8\x69\ \x92\x4f\xbe\x38\xc8\x8b\x6f\x6e\xa5\xa1\x29\x78\x12\x1a\x40\xc6\ \x60\x7d\xf6\x6e\xbb\xdd\x33\x03\x84\x22\x5d\x06\x90\xcd\xde\xc2\ \x85\x67\xe6\x8e\xd1\xbd\x0e\x9f\x39\x75\x0c\x97\xcb\xc5\x6d\x7f\ \x77\x0f\xe1\x11\x51\xd4\x9b\xbb\xe7\xd1\x34\x8d\x8b\x25\x45\x1c\ \xde\xb7\x8b\x0b\xc5\xfa\xcc\x9e\x5d\x61\x32\x1a\xb9\x77\xe9\x3c\ \xee\xbb\x6b\x7e\xfb\x69\xfc\x9b\xe2\x8b\x3c\xfb\xf2\x26\x4e\x17\ \x07\xc7\x8f\xb5\x2b\xc2\x42\xf5\x89\xe8\x4e\x2f\x0c\x08\x51\xb1\ \x1a\x10\x4a\xb9\xb7\x50\xd1\x59\x53\xc6\xf0\x5f\xaf\xeb\xbf\x9b\ \xe2\xec\x99\x13\x94\x9e\x2f\x62\x44\xce\x44\x52\x87\x64\x12\x19\ \x15\x8b\xdd\x6e\xa3\xa9\xa1\x96\xda\xcb\xd5\x9c\x2f\x3a\xd9\x6b\ \x4f\xbb\x8e\x98\x99\x9b\xc3\x8f\x56\xdd\xd5\x1e\x2f\xdc\xd0\xd4\ \xc2\xda\xb7\xb6\xf2\xf1\xae\x83\x68\x5a\xdf\x19\x5e\xf4\x4a\x50\ \x76\x87\xe7\x43\xaa\x41\xa1\xd1\x20\x90\x85\xde\x9a\x9a\x3a\x28\ \x81\x8c\xb4\x41\x5c\x28\xd3\xef\x8e\x6e\xb7\xdb\x38\x79\x74\x3f\ \x27\x8f\xee\xd7\x5d\xb6\x27\xa4\x0e\x4a\xe0\x47\xab\x97\x31\x63\ \xe2\x28\xc0\x6d\xe7\xdd\xb4\x6d\x0f\x79\x1b\x3e\xa5\xd5\xac\xd3\ \x4f\xa4\x17\xd0\xab\x6d\x75\x79\x09\xcf\x35\x28\xe2\x92\x41\x22\ \x0b\x7d\xdd\x58\xb0\x64\xfe\x74\xfe\xfc\x9a\xff\x07\x8f\xbe\x84\ \xc9\x64\xe2\xae\xc5\xb7\xf1\xe0\x8a\x39\x84\x19\xaf\x8d\xc2\x5f\ \x3f\xb7\x9e\x1d\x7b\xf5\xa9\x08\x02\x81\x59\xa7\x62\xce\x68\xea\ \x6c\x29\x0c\x37\x42\x5a\xac\x64\x40\x98\x7c\xc7\xa0\xb8\xd4\x42\ \x4d\xf1\x2e\x1d\x2c\x99\x37\x8d\xd7\xdf\xfd\x8c\xe6\x56\x0f\x8b\ \x79\x3f\x22\x7b\xf4\x04\x66\x2f\x58\x4a\x74\x4c\x1c\x5f\x97\x42\ \x6e\x9a\x46\xf4\x95\xc3\x7a\x53\x4b\xff\xb6\xed\x72\xbd\x3e\x37\ \x96\xe4\xf8\x30\x46\x24\xb8\xaf\x5c\x4b\x8a\x82\xe4\x28\x89\x10\ \x20\x85\xb2\x47\xb1\x86\x73\x06\xf0\xba\x60\x86\x87\x87\x72\xff\ \xf2\xdb\x03\x6d\x73\xaf\x11\x9f\x98\xc4\x8a\x95\x3f\x64\xc9\x3d\ \xab\x88\x8e\x89\x03\xa0\xc9\x2a\x79\x73\xe7\x85\xf6\xcb\x41\xfa\ \x1b\x65\x95\xfa\xdc\x75\x06\xc6\x86\x32\x3a\x49\x32\x29\x45\x92\ \x12\xed\xee\x7c\x40\xb3\x18\x29\x32\x4c\x1f\x20\x9a\xf3\xcb\x5d\ \x27\x80\x71\xde\x08\x2c\x5f\x74\x33\xbb\x0f\x9d\x08\xf8\x64\xaa\ \x07\x46\x53\x08\x33\xe7\x2c\x60\xd2\xf4\xd9\x28\xea\xb5\xe3\x73\ \x65\x79\x29\x3b\x3e\x7a\x97\x90\xb0\x30\x26\xe4\x3c\xca\xc0\x48\ \x1f\x44\xfa\x08\xc5\x17\xf4\xb9\xb5\x44\x46\x74\xd7\x3d\x49\x28\ \x98\x95\x28\x5a\x14\x00\x21\xd9\xe9\x8b\x80\xa2\x28\xfc\xea\x89\ \x95\xfd\xe6\xef\x99\x35\x22\x87\x07\x1e\xfb\x57\x72\x67\xce\x6b\ \xef\x7c\xab\xc5\xcc\xce\x8f\x37\xf1\xd6\x4b\x6b\xa8\xaa\xb8\x08\ \x40\x5d\x9b\xa0\xad\x9f\xc3\x93\xdb\xcc\x16\x0a\xcf\x5f\xd2\x55\ \x26\x75\x50\x82\xa7\xe4\x1d\x70\xc5\x33\x4e\x0a\xb9\x0b\xc4\x8f\ \x7d\x11\x89\x8f\x8d\xe2\xb7\x3f\x5d\xc5\x53\xbf\x7d\x39\x28\x1e\ \x65\x9e\x90\x38\x28\x99\xf9\x8b\x97\x93\x9a\x7e\x2d\x9e\x40\x4a\ \x8d\x82\xc3\xfb\xd8\xbd\x7d\x2b\x36\x6b\xe7\xb5\xde\xa9\x41\x41\ \x45\xff\x2e\x43\xfb\x8f\x16\xea\x76\x58\x1b\x36\x24\xb9\x5b\x9a\ \xa2\xc9\x5d\x70\x85\x01\x9a\x55\xfd\x52\x09\xd5\x5c\xe0\xfb\x5e\ \xcf\x9c\xe1\x19\xbc\xf8\x7f\x1e\xe7\x9f\x7e\xf3\xff\x03\x36\x88\ \x74\x44\x68\x78\x04\x37\xcd\x5e\xc0\x84\xa9\xb3\x3a\x59\xa5\xca\ \xcb\x4a\xd8\xb1\xf5\x5d\x6a\x2a\xbd\x8f\xb8\x56\x9b\xf0\xbe\x81\ \xf5\x01\x3e\xfe\xe2\x80\xae\xfc\xaa\xaa\x90\x99\xde\x4d\xb1\xe9\ \xb4\x85\xab\x7b\xe0\x0a\x03\x72\xb3\x44\x53\x7e\xb9\x73\x2f\x88\ \x5b\x7a\x22\x98\x9e\x92\xc4\x2b\x7f\xf8\x27\x9e\x5d\xb7\x8d\xed\ \xbb\xf6\xe8\x1e\x0d\x1d\x11\x1d\x13\xc7\xe4\x9b\xe6\x32\x76\xd2\ \xf4\x4e\xf6\x07\x73\x6b\x0b\x5f\x7e\xbe\x99\x53\x05\x87\x7a\xbc\ \x4f\xc8\xec\x80\x06\x73\xff\xcc\x82\xd2\x4b\xd5\x1c\x2a\xd0\x77\ \x1f\xc6\x90\x94\x41\x98\x8c\x9d\xed\xe5\x52\x88\xdd\xd3\x07\x88\ \x66\xe8\xe0\x1d\x2d\x85\x78\x53\x48\x7a\x64\x00\xb8\x7d\x2f\x67\ \xdd\x7e\x37\x29\xa3\x66\x52\x70\x78\x2f\xdf\x1c\xcf\xc7\x62\xf6\ \xa8\x52\xea\x86\x98\xd8\x78\x86\x64\x65\x33\x22\x67\x02\xe9\x99\ \x23\x3a\x8d\xf8\xab\x8e\xae\x7b\x77\x7d\x82\xdd\xea\xff\x81\xca\ \xd5\x4f\x53\xe0\xa5\x0d\x1f\xeb\x1e\x70\xd3\x27\x8e\xec\x96\x26\ \xa4\xf6\xc6\xd5\xbf\xaf\x31\xc0\xa2\xbc\x2d\x42\xb5\x35\x74\x89\ \x11\xf0\x06\x45\xc0\x80\xc4\x41\xcc\x5b\x78\x37\x73\x6e\x5f\x4a\ \x55\x65\x19\x97\xab\xca\xa9\xbb\x5c\x85\xcd\x6a\xc5\xe9\xb0\xa3\ \xa8\x06\x42\xc3\xc2\x88\x8a\x8a\x25\x3e\x31\x89\xc4\x41\x29\xc4\ \xc4\x76\x53\xbc\x02\x50\x56\x7a\x8e\x1d\x5b\xdf\xa5\xb6\x3a\xf8\ \x8e\xb3\xc1\xc0\xbe\x23\xa7\xf9\x62\xff\x71\xdd\xe5\xe6\xcc\xe8\ \x26\x5c\x5a\x85\x5d\xdd\x74\xf5\x9f\x76\x06\xe4\x66\x89\xa6\xa3\ \xe5\xae\x2d\x12\x56\xf8\x43\x78\x6c\x92\xc6\xee\x0b\xee\xd1\xab\ \xa8\x2a\xc9\xa9\x19\x3d\x7a\x05\xfb\xc2\x87\x1b\xf2\xb0\x9a\x6f\ \xcc\x38\xe1\xcb\xf5\x4d\x3c\xf3\x82\xfe\x30\xdd\x41\x89\xf1\x8c\ \xca\x4a\xef\x9a\xfc\x41\xc7\xeb\xf2\x3b\xfb\x61\x68\xf2\x75\x7f\ \x08\xbb\x24\x9c\xad\xbd\x61\xde\x7e\xd0\x0d\x97\xe6\xff\x55\x75\ \xad\x66\x0b\x3f\xfd\xbf\x2f\x53\xd7\xd8\xac\xbb\x1e\x8f\x86\x7f\ \x45\xae\xeb\xf4\x6f\xc7\x7f\x26\xa4\xaa\x9f\x48\x49\x8f\xce\x2f\ \xf5\x66\xfd\x17\xb1\xde\x48\x70\x49\x77\x70\x5d\xb3\xcd\x37\x23\ \xea\x1b\x5b\xf8\xc7\xa7\x9f\xa7\xa8\x44\x9f\xdc\x0f\x10\x16\x16\ \xc2\x8a\xc5\xdd\xb6\xd4\xd3\x13\x07\xa9\x9d\xee\x94\xeb\xc4\x00\ \x21\x84\x54\x14\xd9\xed\x8e\x88\xae\x88\x09\x95\x28\x7f\xbd\xfd\ \x0f\xb8\xaf\xd9\xdc\x59\xac\xb0\xf9\x1b\x85\xae\x3e\x51\xe0\xf6\ \xae\x5e\xf5\x93\xff\xa4\x48\xe7\xa1\xeb\x2a\x96\x2f\xbc\xd9\x83\ \xe9\x53\xfe\x47\xd7\x07\x22\xba\x29\xb6\xcf\x0e\x56\x37\x0c\xab\ \xd0\x7e\x09\x0c\xf7\x46\xdc\xa4\x42\x4e\x92\xe4\x54\x95\xdb\xa9\ \xf7\xaf\x19\x52\xc2\xf9\x7a\x18\x74\xc5\x3f\xb0\xa4\xac\x8a\x75\ \x9b\x3e\x67\xfb\x9e\xa3\xbd\x0e\xe6\x88\x8b\x89\xe2\xef\x97\xcc\ \xe9\x9a\x5c\x5c\x9c\xac\x6e\xec\x9a\xd8\x8d\x01\x2b\x84\x70\xe5\ \x97\x3b\x9f\x01\xf1\xb2\xaf\x4a\xb2\x06\xb8\x1f\x49\x38\xda\xcf\ \x27\xd1\xbe\xc0\xa5\xea\x16\x76\x96\x17\xf3\xd1\x8e\xfd\x1c\x3a\ \x5e\x14\x70\x14\xcd\x3f\xff\x60\x45\xb7\xd1\x2f\xa4\xfc\xcd\x0a\ \x21\xba\x39\x36\x79\x34\xed\x14\x27\xab\xaf\x0d\xab\xd0\x1e\x03\ \x26\xf8\xaa\x28\x3d\x4e\x52\xde\xcc\x75\xd3\x4a\xf6\x16\x0e\x9b\ \x95\x0f\xff\xf2\x32\x76\x87\x9d\xe6\x86\x3a\x1a\xea\x83\xe7\x55\ \xbd\x78\xde\x34\x6e\x9e\x32\xa6\x6b\x72\xfe\xd9\x14\x75\xbd\xa7\ \xfc\x1e\x63\x68\x56\x08\xe1\x52\x34\xe5\x11\xf0\xbd\xc2\x08\x60\ \xdc\x60\x19\x94\x0b\xb5\xfb\x13\x2e\x97\xcb\x6d\x36\x3d\x57\x18\ \xd4\xce\xcf\x19\x9e\xc1\x8f\x1f\xe8\x16\x04\xae\x21\x95\xc7\x3c\ \x8d\x7e\xf0\x71\x63\xd6\x84\x34\x71\x10\x78\xad\xa7\x4a\x23\x4d\ \x90\x39\xa0\x3f\xb5\x31\x37\x26\x86\xa6\x27\xf3\x87\x9f\x3f\xd4\ \xcd\x60\x2f\x21\xcf\xd7\x45\xae\x3e\x07\xaf\x26\x94\x9f\x02\x3d\ \x46\xeb\x65\x27\x4a\xaf\x1e\x6f\x7f\x0b\xc8\xce\x4a\x63\xcd\x2f\ \x1e\x21\x3a\xb2\x5b\xa0\x76\xad\x53\x2a\xff\xe2\xab\xac\x4f\x06\ \xe4\x26\x8b\x5a\x21\xe5\x4a\x7c\x58\xcc\xc0\x2d\x15\x8d\x1c\xf8\ \xb7\x39\x0b\xe6\xcd\x18\xcf\x7f\xff\xfa\x31\xe2\xe3\xba\xd9\x4a\ \xa4\x40\x3e\x38\x2d\x55\xf8\x1c\xc0\x3d\x2e\xdf\x13\x53\x0d\x5b\ \x85\x60\x4d\x4f\xf9\x32\xe3\x25\x03\x23\xfb\x8f\x09\x46\x9d\xee\ \xf3\xc1\x46\x58\xa8\x89\x7f\x5c\x75\x17\xbf\x7a\x72\x25\xa1\x21\ \xdd\xdb\x22\xe1\xff\xf9\xf3\xde\x8c\x5f\x0e\x2e\xae\x0a\xe5\xa7\ \xca\x60\xd7\x74\x10\x33\xbc\xe5\x11\xb8\x5f\x3a\xfa\xec\x6c\xdf\ \x4a\x44\x06\x83\x91\x29\xb3\xe6\x33\x6d\xd6\xad\x7d\x5a\x8f\x2f\ \x4c\x19\x3f\x82\xa7\x1e\x5e\xe1\xfd\xfd\x02\x21\x0f\x86\x36\xa8\ \x3f\xf7\x87\x96\x5f\x0c\xc8\xcd\x15\x8e\xa3\x95\xf2\xbb\x52\xd3\ \x0e\x02\x89\xde\xf2\x85\x9b\x20\x3e\x1c\x8f\xde\x70\xc1\xc0\x90\ \xac\x6c\xe6\x2f\xba\x9b\xf8\x84\x9e\xef\x63\xe8\x0b\x8c\xcd\xce\ \xe4\xbe\xbb\xe6\x33\x33\x37\xc7\x57\xb6\x1a\x0d\xf5\xdb\xfe\x3e\ \xf2\xe3\xb7\x8b\xd7\xc4\xc1\xe2\x42\x7e\x85\x5c\x24\xa5\xb6\x53\ \xe0\x39\xae\x0c\xdc\x8f\x37\x1c\x28\x53\x82\xca\x84\xd8\xf8\x04\ \xe6\x2d\x5c\xc6\xd0\x11\x3e\x7f\x78\x9f\xc0\x64\x34\x32\x67\xc6\ \x38\x96\xcc\x9f\xce\xc4\x9c\x61\x3e\xf3\x4a\x68\x91\x42\x59\x98\ \x9b\x2c\x2e\xfa\x4b\x5f\x97\x8f\xdd\xa4\x64\x71\xf8\xc8\x25\xc7\ \x52\x84\xf2\x31\x78\x7e\x1d\x2f\xc4\x00\x37\x67\x68\x94\x35\x09\ \xbe\xa9\x16\x58\x02\x88\xdd\x33\x1a\x4d\x4c\xbb\xe5\x56\x72\x6f\ \x9a\x87\xc1\xd0\xb7\x51\x98\x1d\x11\x16\x6a\x62\xca\xb8\x11\xcc\ \x98\x94\xc3\x9c\x19\xe3\x3a\xc5\x3c\xf8\x80\x5d\x15\xda\xf2\x09\ \xc9\x6a\xbe\x9e\xba\x74\xbf\xa0\x31\x39\xd5\xb8\x33\xbf\xdc\x79\ \x3f\x88\x37\xf1\xb2\x89\x0b\x01\xe9\xb1\x92\xd4\x18\x49\x55\x8b\ \xfb\x25\xa5\x56\x9d\x51\x9e\xc3\x47\x8d\x63\xee\x82\xbb\x88\xf6\ \x62\xc0\x09\x16\x14\x45\x30\x28\x31\x9e\xac\xf4\xc1\xe4\x8c\xc8\ \x60\x4c\x76\x06\xa3\x87\xa7\x77\x33\x23\xf6\x00\x4d\x22\xbf\x3f\ \x21\xd9\xf8\x99\xde\xfa\x7b\xf5\x86\xcc\xa4\x14\xc3\x86\x23\x15\ \x4e\xbb\x90\xe2\x4d\xc0\xeb\x0d\x1d\x8a\x80\xe4\x68\xf7\x13\x54\ \xf9\x97\x04\xe5\xcd\x3d\x6f\xd0\xf1\x09\x03\x99\xb7\xf0\x6e\x32\ \x86\x75\x37\xe5\x79\xc3\xed\x4b\xee\xa1\xae\xb6\x9a\x96\xc6\x06\ \x6c\x36\x0b\x36\x9b\x15\x97\xc3\x81\xfd\x4a\xe4\xa6\x50\x04\x21\ \x21\x61\x28\x8a\x8a\x29\xc4\x44\x7a\x62\x04\xe3\x33\xa3\x49\x1a\ \x10\x4b\xea\xe0\x04\xbd\x9d\xdd\x15\x76\x90\x2b\x27\xa7\x18\x7a\ \x75\xb1\x6a\x40\x22\xcb\x91\x4b\x8e\x79\x42\x28\xef\x03\x3d\x3a\ \x0c\x79\x7b\xf7\xf1\x2a\xd6\xfe\xf1\x97\x8c\x9d\x34\x9d\x69\x37\ \xdf\x86\xaa\x33\x48\x5c\x2f\x6e\xc9\xd4\x88\xf7\xef\x72\xab\x9e\ \xd0\x7a\xe5\x19\x2b\x5d\xef\xc6\x74\x44\xc0\x32\xe3\x91\x0a\x39\ \x59\x48\xed\x63\xa0\xc7\x98\xa6\xe2\x3a\xc1\xc9\x2a\xcf\x55\x9a\ \xdb\x5a\x08\x8f\xe8\x9f\x2b\x2e\xe7\x0f\xd3\x88\x0a\xfc\x66\x85\ \x6a\x29\x94\xc5\x93\x93\xc5\x91\x40\x88\x04\xac\x47\x9b\x9c\x2c\ \x8e\x38\x51\x72\x41\xee\xeb\x29\x6f\x5c\xa8\xf7\x83\x5a\x7f\x75\ \x7e\x52\x94\x0c\xbc\xf3\xa5\x3c\xa4\xaa\xca\x8c\x40\x3b\x1f\x82\ \xf3\x32\x20\x53\x53\x44\x59\x53\xb2\x3a\x5b\xc2\xaf\xf0\xa1\x41\ \x0d\xed\x3f\x41\xc6\x2b\x46\x26\x06\x74\x5a\x97\xc0\x73\x21\x4d\ \xea\xac\xf1\x83\x44\x60\x31\x55\x57\x10\xb4\xc5\xf6\xca\xcb\xd3\ \x4f\xe7\x97\x3b\xf3\x41\xbc\x02\x74\x3b\x26\x06\xf8\xca\x75\xc0\ \x18\x18\x29\x89\xd3\x1f\xa3\x7d\x15\xb5\x12\xf9\xc0\xe4\x14\xc3\ \x96\x20\x36\x29\xf8\xaa\xfc\x49\x29\x86\xcd\x06\xa7\x32\x1c\x78\ \x8e\x2e\xb3\xe1\x7a\xa8\xeb\xe2\xc2\xdc\x0f\x3c\x8f\x1d\x24\xaf\ \xbc\xed\xa2\x1b\x12\xc9\x7a\x14\x65\x74\xb0\x3b\x1f\xfa\xf8\x49\ \xf3\x23\x15\x72\xb2\xd0\x5c\x2f\x20\xc4\x14\x70\xbb\x83\x7c\xf4\ \x8d\xd2\x2f\x8c\x88\x0f\x87\xd1\x49\x1a\x09\x81\x49\x3b\xc7\xae\ \x3c\x69\xde\xe3\xfe\xd6\x5b\xf4\xe9\xa2\x30\x39\x59\x1c\x29\x4e\ \x51\x67\x80\x5c\x0d\x9c\x55\x15\x30\xf5\xad\x84\x89\x2a\x20\x37\ \x55\x72\x4b\x66\x40\x9d\x5f\x24\xa4\x5c\x55\x9c\xac\xe4\xf6\x65\ \xe7\x43\x1f\xcf\x80\x8e\x90\x52\x2a\xf9\x15\xae\xc5\x3b\xcf\x29\ \x1b\x5a\xac\x22\x38\x52\xb8\x07\x0c\x4f\x90\xe4\x24\xf5\x6e\x8e\ \x49\xc9\x49\x81\xfc\x43\x53\x8a\xfa\xd6\x95\x3d\xad\xcf\xd1\xc7\ \xe3\xf1\x1a\xae\xf8\xc3\x6c\x91\x52\x46\xbe\x59\xa0\x3d\xd9\x64\ \x16\x8f\xd5\xb6\x89\x8c\x60\x3b\xd6\xf6\xe2\xed\x61\x0b\xf0\x21\ \x8a\x5c\x37\x69\x90\xfa\xa9\x10\xa2\x5f\xb7\xaa\xeb\xea\xce\xb0\ \xee\xb0\x4c\xb7\xba\xb4\xdf\xb6\x39\x94\x85\x4d\x16\xe2\x83\x11\ \xda\x3b\x34\x5e\x32\x6e\x70\x8f\x84\x9c\x08\xb9\x07\xc9\x7a\xcd\ \xaa\xbe\x97\x9b\x25\x02\xbb\x8e\x25\x00\xdc\x30\xfe\x24\x6f\x15\ \xca\x04\x6b\x8b\xf6\x90\xc5\xae\x2c\x6b\xb1\xc9\x31\x16\xa7\x08\ \xed\x8d\x7b\x8e\xb7\x25\x48\x48\xce\x03\xdb\x41\x6e\xc7\xa1\x7e\ \xde\xd1\x41\xf6\x7a\xe2\x86\x61\x40\x57\xbc\x55\x28\x13\x34\x33\ \xb7\x3b\x9c\xda\x4d\x36\x97\x32\xde\xe6\x22\xc3\xa5\xc9\x68\x97\ \x26\x8c\x2e\x0d\x83\x53\xc3\xe0\x74\x21\xa4\x00\xa3\x82\x54\x90\ \x5a\x88\x51\xd8\x66\x66\xb8\x4e\x98\x54\x71\x01\x41\x91\x40\x9e\ \xd1\x50\x0b\x2d\x46\x8a\x66\x25\xf6\xfc\xb0\xe6\xf5\xc0\xff\x00\ \x7f\xe1\xcb\x24\x2c\x98\x3d\x94\x00\x00\x00\x00\x49\x45\x4e\x44\ \xae\x42\x60\x82\ \x00\x00\x49\xbb\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x01\x00\x00\x00\x01\x00\x08\x06\x00\x00\x00\x5c\x72\xa8\x66\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ \x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x20\x00\x49\x44\ \x41\x54\x78\x9c\xec\xbd\x67\x7c\x1c\xd5\xbd\xff\xff\x3e\xb3\xab\ \xde\x25\xab\x17\xf7\x82\xbb\x64\x19\x1b\x63\x5c\x31\x06\x4c\xb3\ \x29\x09\x84\x16\x48\xc8\x4d\x0f\xe1\xde\x4b\x92\x9b\xe4\x47\x92\ \xff\x4d\x42\x72\x13\xd2\x81\x04\xd3\x0c\x04\x0c\x36\x1d\x4c\x71\ \xc3\x05\x5c\x24\xb9\xf7\xaa\x66\x49\xb6\xba\xb4\x7d\xe7\xfc\x1f\ \xac\x7b\x91\x76\x56\xb3\x3b\xbb\xd2\xbc\x1f\xf0\xc2\xab\x99\x73\ \xbe\x5a\xcd\xf9\xcc\x29\xdf\x22\x30\x89\x68\x9e\x5c\x27\xb3\xe2\ \xe2\x98\x2c\x24\xc5\x4e\x8f\x3a\x58\x95\x4a\xb6\x2a\xc9\x50\x55\ \x52\x3c\x52\x26\x79\x54\xe2\x3d\x2a\x31\x48\x21\x54\x55\x2a\x2a\ \x42\x01\xf0\xa8\x08\x55\xfa\xda\x88\xb6\x20\x4f\xb5\x67\x55\xf0\ \x08\xc0\x6a\xc1\x63\x55\x70\x58\x14\x3a\x15\x21\xdb\x2d\x8a\x68\ \xe9\x17\xaf\x6e\x1b\x98\x2e\x37\x0a\x95\x3a\x69\xb5\x1c\x71\x78\ \x38\x3c\xa5\x50\xd8\x0d\xf9\xc5\x4d\x74\x41\x18\x6d\x80\x49\xf7\ \xfc\x7d\xa5\x4c\x4c\x48\x61\xae\xea\x55\x67\x38\x3d\xa2\xc4\xa5\ \x8a\x22\x87\x9b\x74\x97\x97\x58\xb7\x17\x25\x54\x76\xc4\x5a\xe1\ \xea\xa1\x2a\xd6\x73\x7b\xac\x07\x79\x18\xc4\x11\x24\x87\x11\x72\ \xbb\xc5\x62\xd9\xee\xae\x66\x6f\x69\xa9\x70\x87\xca\x36\x93\xc0\ \x30\x05\x20\xcc\x78\x66\xa7\x4c\x8f\xb2\xf3\x65\xb7\x2a\xe7\xda\ \xdc\x8c\xb5\xb9\x44\xae\xdd\x43\x8c\x94\xdd\xdf\x1b\x0a\x46\x64\ \x49\x46\x64\xfa\x65\x8c\x0b\xd8\x85\x64\xbb\x10\x6c\x53\x85\xb2\ \xc1\xe9\x65\xb3\x39\x63\x08\x2f\x4c\x01\x30\x98\x97\xca\xe5\x48\ \x9b\x57\xbd\xcf\xe1\x14\xb3\x1c\x1e\x31\xbc\xd3\x45\x52\x98\x8c\ \xf5\x8b\x22\x04\x8c\xcc\x92\x0c\xed\x17\x90\x95\x1e\x60\x2b\xb0\ \x0e\x29\xcb\x54\xc5\xb2\xaa\x34\x4f\x54\xea\x6b\xa1\x89\x16\x4c\ \x01\x08\x31\x4f\x54\xc8\xd4\x44\x0f\x5f\x77\x7b\xb8\xbd\xc3\x25\ \x47\x77\x3a\x45\x5c\x38\x0f\xf8\x4b\x31\xa9\x50\x92\x9b\xdc\x73\ \xcb\x85\xe4\x10\xf0\xa9\x2a\xe4\x7b\xc9\x36\xcb\xc7\x43\x87\x0a\ \x67\xcf\xad\x33\xf1\x17\x53\x00\x42\xc0\xe2\x32\x39\xa1\xdd\xa3\ \x3e\xd2\xe6\x56\x66\xb7\xda\xc9\x52\x23\x71\xc4\x9f\xc7\xa8\xec\ \x80\x67\x01\x5d\xd1\x01\x62\x39\x52\x2e\x53\xa4\xf2\xee\xf8\x42\ \x51\xa3\x77\x07\x26\xe7\x62\x0a\x40\x90\x78\xaa\x5c\x96\x7a\xec\ \xf2\x47\x76\x8f\x98\xd3\xe1\x44\x87\x77\x65\x78\x31\x6d\xa0\x4a\ \x7a\x7c\x50\xbb\x50\x41\x7e\x2e\x84\x78\x5d\x7a\x94\xd7\x4b\x8a\ \x44\x6d\x50\x7b\xeb\xa3\x98\x02\xa0\x23\x8b\xb6\xca\xe1\x0e\x87\ \xfc\x55\x9b\x53\xcc\x6d\x75\x90\x6c\xb4\x3d\xc1\xa2\x28\x45\x52\ \x52\x10\x52\x49\x53\x11\xf2\x33\x54\xb1\xd8\x8d\xb2\x78\x52\x81\ \x68\x0c\x65\xe7\xbd\x19\x53\x00\x74\x60\x51\xb9\xbc\xf3\x78\x07\ \xbf\x6d\x73\x50\xa4\x1a\x6d\x4c\x90\x89\xb5\xc2\xec\xa1\x2a\x51\ \x21\x3b\x7c\xbc\x00\x27\x42\xbc\x83\xf4\xfe\xb3\x38\xcf\xba\x5c\ \x08\xd1\xdb\x26\x57\x21\xc5\x14\x80\x40\x90\x52\xac\xac\x21\x5f\ \xb8\xd4\x87\xaa\x5b\xc5\xb7\x0f\x9e\x10\xe9\xde\x3e\xf2\x18\x8e\ \xcb\x93\x0c\x4c\x0b\x9b\x5f\xf6\x80\x84\x97\xbd\x28\x0b\x2f\xcf\ \x17\x55\x46\x1b\x13\x89\x98\x02\xa0\x81\xef\x7e\x20\x63\x06\x27\ \xf3\x13\x87\x9b\xef\x39\xbd\xa4\x7a\x7b\xe9\xeb\x3e\x35\x0e\x06\ \x65\x48\x92\xa2\x25\x55\x2d\x82\x43\x4d\x67\x1e\x93\xe9\x83\x54\ \xd2\xe2\x0c\x34\xee\x62\x08\xdc\x42\xe5\x35\x50\xfe\x50\x5c\x20\ \xb6\x18\x6d\x4e\x24\x61\x0a\x80\x1f\xfc\xf8\x03\x99\xd9\x2f\x89\ \xdf\xda\x5d\x7c\xc5\xe5\x25\xc6\x68\x7b\x82\x45\xac\x15\x46\x66\ \x4b\x0a\x53\xe5\xe9\x07\xe3\x68\x8b\xa0\xa2\xc6\xf7\xaf\xc4\x18\ \x98\x3e\x50\x25\xca\x62\x9c\x8d\xdd\x23\xd7\x49\x78\xbc\x24\xcf\ \xf2\x9e\xb9\x3c\xe8\x1e\x53\x00\xba\xe0\x37\x2b\xe5\x80\xd8\x28\ \x16\xb6\xda\x99\xa9\xca\xde\xfb\x5d\x59\x2d\x30\x24\x5d\x32\xa4\ \x9f\x3c\xc7\xcd\xf7\x48\xb3\x60\x47\x9d\xc0\xa3\x42\x5a\x1c\x5c\ \xd1\x5f\x25\x3a\xac\x07\xff\x19\xa4\x64\x87\x82\xfc\xbf\x96\x7c\ \xcb\xcb\x33\x85\xf0\x18\x6d\x4f\xb8\xd2\x6b\x1f\xea\x9e\xf0\xf8\ \x4a\x59\x10\x65\xe1\x5f\xed\x4e\xe6\x7a\xc3\x74\xe0\x0b\x01\xa9\ \xb1\x90\x16\x27\xb1\x9c\x1c\xb4\x76\x37\xb4\x38\x04\x1d\x27\x5d\ \x69\x14\x01\xfd\x12\x24\xf1\xd1\xf8\x36\xed\x24\xb8\x25\xb8\x3d\ \xa0\x28\x60\x15\xbe\xe9\x7e\x7e\xb2\xc4\x7a\xd6\xc0\x96\x12\xb6\ \xd4\x0a\x8e\xb6\x9c\xf9\xd5\xaf\x1c\x20\xc9\x4c\x88\xc8\x17\xea\ \x11\x29\xf8\xcd\xc1\x5c\x65\xe1\x1d\x42\x78\x8d\x36\x26\xdc\x08\ \xcb\x87\xdb\x28\xfe\xf7\x33\x99\x1b\x27\x78\xba\xcd\x21\x6f\x50\ \xa5\x08\xdb\xef\x26\x2d\x0e\x4a\xf2\x55\x92\x2e\xb1\x18\xd9\xdf\ \x28\xd8\x59\x27\xb8\xa2\x48\x92\xad\xd1\xb1\x58\x95\xb0\xb9\x5a\ \x50\xdb\x76\xee\xaf\x3f\x26\x47\x32\x38\x23\x22\x05\xe0\x14\xbb\ \x91\xf2\xb7\xc5\xf9\x96\x97\x84\x10\xbd\x74\xf7\x46\x3b\x61\xfb\ \x90\x87\x92\xc7\xde\x95\xf1\xa9\xa9\xea\x93\xad\x0e\xe5\x6e\xaf\ \x0c\x5d\x74\x5d\x20\x44\x5b\x60\xce\xb0\xae\x8f\xe1\x3a\x5c\xf0\ \xe9\x7e\x05\xab\x02\x43\xfb\x49\x86\xf5\x93\xf8\x23\x67\x5e\x09\ \x9b\x2a\x05\x75\x1d\x17\x5e\x2c\x04\x64\x25\x48\x92\x62\x20\x3e\ \x1a\x72\x92\x24\xf1\x51\x3d\xf8\x45\x8c\x63\x8b\x22\xd4\x47\xc7\ \xe7\x45\x7d\x6c\xb4\x21\xe1\x40\x9f\x16\x80\xc7\x1e\x93\x4a\xf2\ \x2c\x1e\xb1\xbb\x79\xcc\xe9\x25\xb8\x7e\x6d\x3a\x91\x18\x0d\xc3\ \x32\x7d\xd3\x7e\x8f\xd7\x37\x5d\x8f\xb2\x42\x6e\x92\x44\x39\xeb\ \xaf\xb9\xff\x84\x60\xdf\x71\x81\x14\x70\x45\xa1\x4a\x46\x42\xd7\ \xed\x7a\x54\xf8\xe2\xa8\xe0\x84\xcd\xbf\x47\x22\x4a\xf1\xf9\x03\ \xc4\x5a\x7b\xf0\xcb\x18\x8a\xf8\x14\x45\x7c\xbf\x24\x57\xec\x32\ \xda\x12\x23\xe9\xb3\x02\xf0\xc7\xd5\xf2\x1a\x97\xca\x0b\x36\x37\ \x39\x46\xdb\xa2\x07\x85\x29\x92\x09\x01\x7a\xe7\xa9\x12\xbe\xa8\ \x14\x34\x5c\xe4\xcd\xdf\x15\x7a\x05\x04\x19\x86\xc0\x8d\xe4\x49\ \xd5\xa1\xfc\xbc\x74\xb0\x68\x35\xda\x1c\x23\xe8\x73\x02\xf0\xeb\ \x4f\x65\x46\x6c\x0c\x6f\xb6\xda\xb9\x2a\x82\x1f\xdd\x0b\x28\xce\ \x93\xf4\x0f\xc0\x41\x47\x02\x65\xd5\x82\xea\x56\xed\x8f\xc2\x35\ \x43\x55\xe2\xa3\x35\xdf\x16\x8e\x34\x48\xe4\x8f\x4a\xf2\x2c\xcf\ \xf7\xb5\xa3\xc3\xb0\x5e\xef\xea\x8a\x94\xe2\x0f\x6b\xe5\x7f\x7a\ \x05\xb5\x2d\xbd\x6c\xf0\x0f\x4a\x0f\x6c\xf0\x83\x6f\xa9\x10\xc8\ \xe0\x4f\x3e\xb9\x17\xd0\x4b\xc8\x12\x88\x67\x2b\x6a\xbd\xab\xb7\ \xd6\xc8\xe1\x46\x1b\x13\x4a\xfa\xc4\x0c\x60\xa5\x94\xd6\xf2\xb5\ \xf2\x8b\x56\xbb\x98\x60\xb4\x2d\x7a\x53\x98\x22\x29\xc9\xf7\x6f\ \x93\xef\x7c\x9c\x1e\x58\x7e\x40\xc1\x15\xc0\xe1\x58\xdc\xc9\xf4\ \x60\x96\xde\xf7\x0a\xb1\x4b\xf8\x5d\x6c\x8b\xf2\xeb\x51\xa3\x84\ \xcb\x68\x63\x82\x4d\xaf\x17\x80\xb2\x5a\x39\x41\x48\x75\xe1\xfe\ \x13\x62\xdc\xce\xfa\xde\xf5\xeb\xc6\x47\xf9\x36\xe2\x2c\x3d\xf8\ \xb5\x54\xe9\x3b\x35\x70\x7a\x7c\x8d\xb8\xbc\xe0\xf6\x42\xab\x03\ \xda\x9c\x02\x9b\xd3\x27\x14\x17\x3b\x37\x1b\x9f\x27\x19\x10\x3e\ \x71\x01\xba\x22\xa1\x42\x0a\xe5\x6b\xa5\x79\xa2\xdc\x68\x5b\x82\ \x49\xc4\xee\xe1\x76\x87\x94\x52\xd9\x72\x4c\xfd\xae\x94\xea\xef\ \x80\xe8\xb8\xc8\x3c\xb2\xea\x92\xa2\x54\xd9\xa3\xc1\x0f\x3e\x67\ \xa1\xe4\x18\x20\xe6\x62\x03\xd9\xf7\x99\x94\xd0\xee\x84\xbd\xc7\ \x05\x35\x67\xf9\x07\xf4\xd6\x58\x08\x00\x01\xc5\x42\xaa\x1b\xca\ \x6a\xbc\xff\x7b\x30\x4f\xf9\x55\x6f\x75\x22\xea\x5d\xaf\xc4\x93\ \x6c\xae\x95\x45\x8a\xf4\x2e\x02\x31\x0d\x7c\x6f\xb5\x0d\x55\x0a\ \x8d\x9d\x46\x5b\xa6\x2f\xe3\x72\x25\x03\xd3\xbb\x7f\x03\xcb\x93\ \xff\xe9\xa9\x6b\x93\xc4\xb7\x67\x70\xb8\x49\x90\x18\x2d\x29\x2d\ \x90\xc4\xf4\xda\x57\xc8\xd9\xc8\xcf\xbd\x56\xcb\xdd\x13\xb3\xc5\ \x21\xa3\x2d\xd1\x9b\x5e\x27\x00\x2f\x6c\xf2\xfe\xd6\x6a\x15\xdf\ \x73\x7b\x89\x73\x7a\xa0\xc3\x25\x68\x77\xfa\xa6\xba\xbd\x8d\xdc\ \x24\xc9\xa4\xa2\xae\x7f\xb1\x83\x8d\x82\xdd\x0d\x82\x91\xd9\x92\ \x41\x7e\x88\x85\xc9\x25\x69\x93\x42\x7e\x7b\x42\x9e\xf5\x25\xa3\ \x0d\xd1\x93\x5e\x23\x00\xef\x6d\x93\x69\xd5\x36\xbe\xa8\x6d\x63\ \x58\x57\xd7\xc5\x58\xc1\x22\xc0\xd6\x4b\x32\xd6\x9f\xbd\x0e\x6f\ \x77\xfa\x3c\x05\x63\xac\xbe\xb7\xf5\x8e\x3a\xc1\xc1\x46\xdf\x9f\ \xb8\x30\x55\x32\x21\xdf\x14\x80\x1e\x23\x78\x41\x45\xf9\x56\x69\ \x9e\xb0\x19\x6d\x8a\x1e\xf4\x0a\x01\xf8\x77\x99\xbc\xea\x58\xa7\ \x5c\xa6\x28\x22\xbe\x5f\x82\x24\x21\xca\xb7\x6e\xdd\x5e\x27\x38\ \x95\xa8\x23\x31\x06\x8a\x73\xcf\x78\xc4\x35\xdb\xe1\x70\x93\xa0\ \xd3\x2d\x48\x39\xb9\xfe\x3d\xdc\x2c\x08\x97\xfc\xfb\xfe\x22\x80\ \xac\x44\x89\x57\xc2\x89\x4e\x41\x9c\x15\xc6\xe6\x4a\x8e\x36\x73\ \xda\xa5\xd7\x6a\x81\x2b\xfb\x87\x61\x1c\x7f\xe4\xb2\x4d\xf5\x2a\ \x0b\x4a\x8b\xc4\x41\xa3\x0d\xe9\x29\x11\x2f\x00\x2f\x6e\xf2\xfe\ \x8f\x43\x15\x3f\x1f\x99\x25\xa3\xcf\x3e\x97\x6e\xb1\xc3\xaa\x43\ \x67\xce\xa8\x86\x64\x48\x46\xe7\x74\x3d\xba\xb7\x9f\xf5\xc6\xec\ \x0d\x08\x01\xfd\x53\x25\xc3\x33\x25\xbd\x71\x13\xd4\x60\xda\x84\ \x90\xf7\x15\xe7\x59\xdf\x32\xda\x90\x9e\x10\xb1\x4f\xfb\x63\x52\ \x2a\x39\x1b\xf8\xb0\xdd\x29\xaf\x99\x31\xf8\xcc\x6e\xb8\xcb\xeb\ \x7b\xb3\x1f\x38\x21\x70\x9f\xb5\x4b\x6d\xb5\xc0\x75\xc3\xbb\x3e\ \x32\xab\x6a\x15\x94\x55\x87\xdf\x57\x62\x51\xb4\xed\xb8\xa7\xc4\ \x42\x41\x8a\x2f\xb1\x47\xe4\xfa\xea\x47\x04\x12\xf8\x5d\x71\x9e\ \xf2\x93\x48\x8d\x30\x0c\xbf\xa7\xdd\x0f\x9e\xde\x2c\x73\xed\x0e\ \x2a\x9a\xed\x64\x4f\x1f\xa8\x92\x7a\x72\x6a\x5b\xd5\x2a\xd8\x7a\ \x4c\xe0\xb9\xc8\x81\x4d\x46\x02\x5c\x39\x40\x3d\xc7\xf5\x51\x4a\ \xa8\xef\x14\x20\x7d\x71\xf5\xdb\xeb\x02\xf3\x8a\xd3\x1b\x21\x7c\ \xd3\xfa\x82\x64\xc8\x4c\xf4\x0d\xe2\xf2\x1a\x41\x65\xcb\xc5\x6d\ \x8b\xb1\x42\x46\xbc\xa4\x5f\x02\xe4\x24\xca\xde\xe4\xa1\x17\x21\ \x88\xb7\x2d\x16\xf1\x95\x71\x39\x22\xe2\xce\x99\x8c\x7f\xda\x35\ \xf2\xc2\x16\x59\x5c\xdf\x22\xd7\x75\xba\x44\xdc\xd8\xb3\x12\x54\ \xee\x6e\x10\xec\x3d\x7e\xe9\x5f\xa7\xb4\x40\x52\x90\x72\x66\x09\ \x20\x25\x6c\xac\x12\x1c\x6b\x0f\x9f\xaf\x20\x4a\x81\xa2\x34\xc9\ \xa0\x0c\xdf\x3e\x06\x80\xdd\x03\x15\x35\xe7\x06\xea\x08\x7c\x39\ \x01\xf2\x52\x24\x59\x89\xbe\x10\xdd\xf0\xf9\x2d\xfa\x2c\x5b\x2d\ \x52\xb9\x61\x5c\x81\xa8\x36\xda\x10\x2d\x44\xd4\x04\xf1\x85\x4d\ \xf2\xe6\xda\x66\xde\xb0\xbb\x85\x75\x54\xf6\x99\xc1\x5f\xd5\xd2\ \xf5\xe0\x07\x2e\x48\x65\x75\xa0\x31\x3c\x06\x7f\xb4\x05\xb2\x93\ \x24\x79\x49\x90\x95\x74\xa1\x63\xcf\xa6\x4a\x85\xa6\xb3\xca\x69\ \x26\xc7\x40\x71\x7e\xef\xdc\xd0\x73\x79\x3c\xbc\xfa\xf6\x4a\xae\ \x9d\x31\x91\xac\x8c\x54\xa3\xcd\xd1\xca\x38\xaf\x50\xbf\x28\xaf\ \x96\xf3\x4a\x0a\xc4\x56\xa3\x8d\xf1\x97\x88\x11\x80\x17\xcb\xe4\ \xc3\x47\x5b\xf8\x83\xdb\x8b\x18\xda\xef\x4c\x59\x2a\xb7\xea\xdb\ \xbc\xeb\x8e\x56\x07\x64\x25\xfa\xfe\xbf\xc5\xe1\x9b\x31\x5c\x8a\ \x7e\x09\x92\x7e\xf1\x10\x1f\x03\xd1\x8a\x6f\x0d\xee\xf2\xc2\x81\ \x13\x82\x66\x1d\x6a\xdb\x5a\x14\x5f\x42\x8d\xc2\x14\xdf\x54\x5f\ \xe9\xc2\x7c\xab\x45\x02\x02\x45\xf8\x2a\xf3\x0e\xe9\x17\xe6\x19\ \x4b\x02\xa4\x6c\xfb\x7e\xfe\xb8\x70\x09\x47\xab\xeb\xb9\x7a\x6a\ \x89\xd1\xe6\x04\x4a\xbe\x14\xea\x9a\xb2\x5a\xf7\x1d\x13\xf2\xa2\ \x96\x19\x6d\x8c\x3f\x44\x84\x00\x2c\xdc\xec\xfd\xcb\xe1\x26\xbe\ \xeb\x95\xbe\xa9\xef\xc8\xac\x33\x53\xf9\x83\x8d\xc2\xaf\x60\x96\ \x43\x4d\xe2\x74\xc4\x5c\x79\x8d\x72\x49\xc7\xa0\xf8\x68\x5f\xdd\ \xbb\xd4\xd8\x73\x3d\xe7\xda\x9c\xe0\xf4\xf6\x6c\xc6\xa0\x08\x18\ \xd2\x4f\x32\x24\x43\xfa\x9d\x5c\xb3\x38\x4f\x72\xa2\xf3\xcc\x5e\ \x40\x6f\xa3\xa1\xb1\x85\xa7\x5f\x79\x9f\x8f\x56\x6f\x36\xda\x14\ \x5d\x10\x90\x84\x54\xde\x2e\xab\xf1\xdc\x3b\x21\xdf\xfa\x9a\xd1\ \xf6\x74\x47\xd8\x3f\x52\x4f\x7d\xa1\x2e\xae\x6c\x16\xb7\x9f\x3a\ \x9f\x1f\x93\xa3\x9e\x33\x30\xab\x2f\xb1\x31\x76\x3e\x76\xb7\x2f\ \x4d\x16\xd0\xa5\x60\xd8\x5c\xb0\xfa\x90\x2f\x9d\x56\xac\xd5\x77\ \x7a\xe0\xf6\x42\x67\x0f\xe3\xc2\xa2\x2d\x30\xa5\xff\x99\x0d\x4b\ \x7f\x89\x8b\xf2\x39\xf1\xf4\x36\x9c\x2e\x37\x6f\x7c\xf0\x19\xcf\ \xbf\xf1\x31\x76\x47\xaf\x0b\xba\x8b\x16\x88\x97\xcb\x6b\x3c\x89\ \x25\xf9\xd6\x85\x46\x1b\xd3\x15\x61\x2d\x00\x7f\x5f\xaf\x7e\x58\ \xdb\x26\xae\x3d\xf5\xef\x9c\x24\x79\x4e\x41\xca\x56\x87\x2f\x92\ \xcd\x5f\xb4\x84\xbd\x7a\x54\x6d\x6d\x77\x47\x69\x81\xbc\xe8\xe0\ \xef\xb4\xd9\xe9\xb4\x39\xe9\xb4\x3b\xb0\x39\x1c\xd8\xed\xbe\x4e\ \x5d\x1e\x0f\x0e\xe7\x85\xee\x8a\xb1\x31\x51\x44\x5b\x7d\x7f\xb6\ \xa4\x04\x5f\x83\x71\x71\x31\xa4\x24\x26\x90\x94\x18\x87\xa2\x84\ \xff\x02\x61\xed\xa6\x9d\xfc\xe5\xf9\x37\xa9\xad\xef\xd5\x25\xfe\ \x2c\x20\xfe\x55\x56\xeb\x4d\x9a\x90\x67\xf9\x93\xd1\xc6\x5c\x8a\ \xb0\x15\x80\x27\x3f\x57\x3f\x3d\xd6\x2e\x66\x9f\xfd\x59\xd1\x59\ \xfb\x42\x52\xfa\x5c\x5d\xb5\xa0\x08\xdf\x71\x59\x66\x02\x24\xc7\ \xfa\xda\xd8\xa7\xd3\xba\xfe\x6c\xa4\x54\x69\x69\x6e\xa2\xa3\xad\ \x99\x8e\xb6\x56\xdc\xb6\x56\x8e\x6e\x68\xe1\x44\x53\x2b\x27\x9a\ \xdb\x38\xde\xd4\x4a\x47\xa7\x9d\x0e\x9b\xce\x1d\x03\xf1\xf1\xb1\ \xa4\x24\xc6\x93\x92\x94\x48\x46\x5a\x12\xd9\x19\xa9\xf4\x4b\x4f\ \x21\x27\x33\x9d\xa2\xfc\x4c\x8a\xf2\xb3\x89\x8f\x35\xa6\xb6\x49\ \x75\xdd\x09\xfe\xbc\x70\x29\x9f\x57\xec\x36\xa4\x7f\x03\x10\x42\ \xf2\x44\x45\x8d\x37\xa5\x38\xdf\xf2\x0b\xa3\x8d\xb9\x18\x61\x29\ \x00\x4f\x6f\x50\x57\xd7\xb4\xfa\x22\xf9\xce\x26\x2e\xca\x37\x15\ \x56\xa5\xef\x68\xec\x78\x67\xd7\x02\x90\x18\xe3\xf3\x84\x8b\xb1\ \xfa\x62\xe7\xd3\xe2\xcf\xec\xb2\xdb\xdd\xb0\xa3\xbe\x67\x83\xdf\ \xeb\xf5\xd2\xd2\x74\x9c\x13\x0d\x75\xb4\x36\x9f\xe0\x44\x43\x1d\ \x8d\xc7\xeb\x68\x3a\x5e\x8f\xdb\x6d\xcc\xb4\xd6\x66\x73\x60\xb3\ \x39\x38\xd6\xd0\x74\xd1\x9f\x5f\x3f\xf3\x72\x7e\xf2\xed\x3b\x43\ \x6a\x93\xd3\xe5\xe6\xe5\xb7\x96\xf3\xd2\x9b\x2b\x70\xb9\x7b\x49\ \x10\x86\x06\x24\x3c\x56\x5e\xe3\x55\x4a\xf2\x2d\xff\xcf\x68\x5b\ \xce\x27\xec\x04\xe0\xa9\xcf\xd5\x4f\x2f\x36\xf8\x01\x76\xd4\x2b\ \x14\x24\x4b\x0e\x37\x0b\xda\x1c\xdd\xb7\x35\x22\xf3\xc2\xb3\xff\ \xfa\x76\x41\x65\x0b\xd4\xb5\x9f\x89\x13\xf0\x97\x96\xe6\x46\x6a\ \x8f\x1e\xa2\xae\xb6\x8a\x9a\xaa\x43\x34\x1c\xab\x46\x46\x5a\xf0\ \x40\x88\x59\xb7\x79\x27\x7f\x7a\x76\xe9\x25\x05\xa9\x0f\xf1\xf3\ \x8a\x5a\xaf\xa3\x38\xcf\xf2\x1b\xa3\x0d\x39\x9b\xb0\x12\x80\xa7\ \x36\xc8\x77\x6b\x5b\x99\x7d\xa9\x9f\x37\x76\x42\x63\x37\x6f\xfd\ \xb3\x39\xdc\x24\x90\xf8\x66\x0c\xad\x76\xa8\x6d\x13\x38\xfc\x2c\ \x12\xe5\xf5\x7a\xa9\xad\x3a\x44\xd5\xe1\x83\x1c\xab\x39\x42\x6d\ \xd5\x11\x9c\x0e\xfd\xa7\xec\xbd\x95\xca\xda\x06\x9e\x58\xb8\x84\ \x4d\x5b\xf7\x19\x6d\x4a\xd8\x20\x25\xbf\x2e\xaf\xf6\xba\x4a\x0a\ \x2c\x7f\x30\xda\x96\x53\x84\x8d\x00\x3c\xb9\x5e\xbe\x5e\xdb\xca\ \x0d\x7a\xb6\xd9\x68\x83\x46\x3f\xf3\xdc\x03\xb4\xb5\x34\x71\x70\ \xef\x4e\x8e\x1c\xd8\x4d\xd5\x91\x03\xb8\x5c\x4e\x3d\xcd\xe9\x13\ \xd8\x1c\x4e\x5e\x78\xe3\x13\x16\xbf\xb7\x0a\xf7\xc5\x7c\xb2\xfb\ \x3a\x82\xdf\x57\xd4\x7a\xda\x8a\xf3\xac\xff\x32\xda\x14\x08\x13\ \x01\xf8\xe7\x06\xef\x33\xd5\xad\xdc\x66\x44\xdf\x4d\xc7\xeb\xd9\ \xb7\x6b\x2b\xfb\xf7\x6c\xa3\xbe\xd6\x2c\x31\xdf\x13\x96\xaf\xdb\ \xc2\xdf\x5f\x7c\x9b\x86\xc6\x16\xa3\x4d\x09\x67\x84\x94\xe2\xc9\ \xb2\x1a\x4f\x5b\x38\xf8\x09\x18\x2e\x00\xcf\x6e\xf2\xfe\xf7\xd1\ \x16\xe5\xc1\x50\xf6\xe9\xb0\x75\xb2\x6f\xf7\x56\x76\x6e\xd9\x44\ \x4d\x65\xaf\xcb\xf2\x14\x72\x2a\x6b\x1b\xf8\xd3\xb3\x4b\xd9\xb8\ \x65\xaf\xd1\xa6\x44\x0a\x16\x21\xc4\xa2\xf2\x1a\x77\x63\x49\x7e\ \xd4\xa7\x46\x1a\x62\xa8\x00\xbc\x50\x26\xe7\x57\xb6\xf0\xdb\x50\ \xec\xa3\x49\x29\x39\x72\x60\x37\x5b\x36\xae\xe5\xd0\xfe\xdd\x48\ \x19\x91\xd1\x9b\x61\x85\xcd\xe6\xe0\xd9\xd7\x3f\xe2\x8d\x0f\xd6\ \xe0\xf1\x9a\xd3\x7d\x4d\x48\xa2\x40\x59\xb2\xb9\x4a\x4e\x2d\x2d\ \x14\xdb\x8d\x32\xc3\x30\x01\x78\xb9\x42\x8e\xaf\x6c\x61\xb1\xdb\ \x1b\xdc\x40\x36\xa7\xc3\xce\xb6\xb2\xf5\x6c\xdd\xb4\x8e\x96\xe6\ \x5e\xed\x78\x12\x32\xa4\x94\x7c\xb2\xa6\x9c\xbf\xbf\xf8\x0e\x8d\ \x2d\x6d\x46\x9b\x13\xc9\x24\x2b\x8a\xfa\xce\xf6\x7a\x39\x79\x4c\ \xb6\xa8\x37\xc2\x00\x43\x04\xe0\xc5\xad\x32\xab\xa6\x45\xae\xb3\ \xbb\x45\xd0\xfa\xb7\x75\xb6\xb3\x65\xd3\x5a\xca\x3e\x5f\x6d\xee\ \xde\xeb\x48\x65\x4d\x3d\x4f\x3c\xbb\xd4\xdc\xdd\xd7\x8f\x01\x6e\ \x8f\xfa\xfe\xd6\x3a\x39\xdd\x88\x7c\x02\x21\x17\x80\xc5\x3b\x65\ \x74\x4d\x13\xdb\x3b\x9c\x22\x28\xd5\x78\x3b\xda\x5a\xf9\xfc\xb3\ \x8f\xd8\x51\xbe\x01\xaf\xd7\xcf\x33\x3f\x93\x6e\xe9\xb0\xd9\x59\ \xf8\xea\x32\x96\x7e\xb4\x16\x6f\x6f\x2e\x08\x60\x0c\x13\x3e\x3f\ \xc2\xf6\x95\x2b\xe5\xb0\x99\x33\x45\x48\x1f\xda\x90\x0b\x40\x43\ \xab\x5c\xd3\x6c\x17\x59\x7a\xb7\xeb\x74\xd8\xd8\xb0\x66\x39\xe5\ \x1b\x56\xe3\xe9\x83\xde\x66\xc1\x42\x4a\xc9\x47\x9f\x95\xf1\x8f\ \x45\xef\xd0\xd4\xd2\x6e\xb4\x39\xbd\x96\x13\x9d\x0c\x94\xf1\x7c\ \x0a\xcc\x08\x65\xbf\x21\x15\x80\x85\x1b\xbd\xbf\x39\xda\x22\x2e\ \xd7\xb3\x4d\x55\x55\xd9\xb2\x69\x0d\xeb\x56\x2c\xc3\xe9\xe8\x15\ \x99\x9a\xc3\x86\x7d\x87\xaa\xf9\xe3\x33\x4b\xd8\xb1\xef\x88\xd1\ \xa6\xf4\x7a\x06\x67\x48\xf6\x34\x88\xe9\x0b\x37\x7b\x7f\xf9\x60\ \xa9\xe5\xe7\xa1\xea\x37\x64\x02\xb0\x68\x93\x9c\x73\xa4\x8d\x47\ \xf5\x6c\xb3\xae\xa6\x92\x4f\xdf\x7f\x9d\xba\x9a\x4a\x3d\x9b\xed\ \xf3\x9c\x9a\xee\x2f\x59\xb6\x16\x55\x35\xa7\xfb\xa1\x60\x70\x86\ \x64\xff\x09\x41\x6d\xab\xf2\xd3\x17\xcb\xe4\xaa\x7b\x27\x88\x15\ \xa1\xe8\x37\x24\x02\xf0\xca\x5e\xd9\xaf\xba\x8e\x77\xf4\xda\xf1\ \x77\x39\x1d\xac\x5c\xf6\x26\xdb\x2b\x36\x10\x71\x89\xfc\xc3\x18\ \x55\x95\xbc\xbf\x62\x03\x4f\xbd\xf2\x1e\xad\x6d\x11\x97\xdf\x32\ \xa2\xb1\x2a\xbe\xe4\xae\x9d\x2e\x44\x7d\x87\x7c\xef\xcd\x0a\x99\ \x37\xbf\x58\x04\xdd\xa3\x2a\x24\x02\xd0\xd2\xc4\xe7\x1d\x4e\x62\ \xf5\x68\xab\xf2\xf0\x3e\x96\xbd\xf9\x0a\x6d\xad\xcd\x7a\x34\x67\ \x72\x16\x7f\x7b\xf1\x6d\x16\xbf\xb7\xda\x68\x33\xfa\x2c\xee\x93\ \xae\x14\xed\x4e\x11\xd7\xe0\x94\x6b\x81\xd1\xc1\xee\x33\xe8\xd9\ \x23\x9e\xd9\xe4\x7d\xaa\xae\x9d\x21\x3d\x6d\xc7\xeb\xf5\xb0\xe2\ \x83\xa5\x2c\x7e\xe1\x1f\xe6\xe0\x0f\x12\x1d\x9d\xe6\x71\xa9\x91\ \xe4\x26\xfb\x66\xb3\x42\x40\x8b\x43\x8c\x5a\xba\x4b\xfe\x57\xb0\ \xfb\x0c\xea\x0c\xe0\xa9\x8d\x72\x76\x6d\x2b\x0f\xf5\xb4\x9d\xf6\ \xd6\x16\xde\x5d\xfc\x1c\xb5\xd5\x47\x74\xb0\xca\xc4\x24\x3c\x29\ \xce\x93\x8c\xcb\xf5\x89\xc0\xc9\x44\xb1\x3f\x29\xaf\x95\x8b\x4b\ \xf2\xc4\xd1\x60\xf5\x19\xb4\x19\xc0\xe2\x9d\x32\xba\xcd\xce\x9b\ \x1e\xb5\x67\xeb\xfe\xaa\x23\xfb\x59\xf4\xf4\xff\x99\x83\xdf\xa4\ \x4f\xa0\x08\xce\xce\x12\x9d\x8a\xf4\x2e\x5a\x2c\xa5\x9f\x29\x64\ \x03\xe8\x2f\x58\x0d\x37\xb7\xcb\xf7\x3b\x9c\x24\xf5\xa4\x8d\xb2\ \xcf\x57\xb1\xf8\xf9\xbf\x63\xeb\x34\xcf\x9f\x4d\xfa\x2a\xe2\xaa\ \x21\xc7\xd4\xa0\x2d\x05\x82\x22\x00\x2f\x6d\x95\xb7\x1d\x6b\x17\ \x57\x07\xdc\x80\x94\xac\x5f\xf5\x21\x2b\x97\xbd\x69\x66\xdc\x31\ \x31\x81\x5f\x6e\xa9\x91\xc5\xc1\x68\x58\x77\x01\xf8\xcb\x7e\x19\ \x53\xd7\xca\xf3\x97\xca\xbb\xdf\x1d\x5e\xaf\x87\x77\xdf\x78\x81\ \xf5\x2b\x23\xa2\xae\x82\x89\x49\xf0\x91\x44\xd5\xb6\xab\xaf\xff\ \x7d\xa5\x4c\xd4\xbb\x69\xdd\x05\x20\xae\x99\xb7\xdb\x1c\x24\x04\ \x72\xaf\xd7\xeb\xe5\xdd\xc5\xcf\xb1\x77\x47\x85\xde\x66\x99\x98\ \x44\x34\x07\x1a\x95\xc1\x4a\x2c\x4b\xf5\x6e\x57\x57\x01\x58\x54\ \x26\xaf\xad\x69\x65\x6e\x20\xf7\x9e\x1a\xfc\x07\xf6\xec\xd0\xd3\ \x24\x13\x93\x88\xa7\xc5\xee\xcb\x87\x59\xdf\xc1\x9c\x85\x9b\xe4\ \x75\x7a\xb6\xad\x9b\x00\x2c\x5e\x2c\x2d\xf5\x1d\xbc\x1a\xc8\xd4\ \x5f\x3d\x3d\xf8\x0d\xcb\x8b\x60\x62\x12\x96\x48\x60\x7b\xbd\x72\ \x3a\xb9\x6d\xb3\x9d\x7f\x2f\x5e\xac\xdf\xa9\x80\x6e\x02\xd0\x31\ \x90\x7f\xb5\x39\x49\x09\xe4\xde\x15\xcb\x96\x9a\x83\xdf\xc4\xe4\ \x22\xec\xa8\x13\x34\x9e\xe5\x95\xdd\xe6\x20\xa5\xb5\x3f\xba\x95\ \x1b\xd3\x45\x00\x16\x6f\x91\xf9\xc7\xda\xb9\x2f\x90\x7b\x37\xac\ \xf9\x84\x2d\x1b\xd7\xea\x61\x86\x89\x49\xaf\xc1\xe1\x81\x4d\xd5\ \x82\x83\x8d\x17\xba\xd1\xd4\xb7\x73\xcf\x33\xeb\x64\x7f\x3d\xfa\ \xd1\xc5\x13\xb0\xc5\x21\x97\x3a\x3d\x42\xb3\x98\xec\xd9\x51\xc1\ \x9a\xe5\xef\xeb\x61\x82\x89\x49\xaf\xa0\xcd\xc9\xc9\xa8\xc0\x4b\ \x17\xae\x71\xa9\x28\x4e\x85\xa5\xc0\x84\x9e\xf6\xd7\xe3\x19\xc0\ \xbf\xcb\xe5\xec\xda\x76\xed\x31\xfe\x8d\xc7\xeb\xf8\xe8\xad\x57\ \xcc\x68\x3e\x13\x13\xc0\xe3\x85\xb2\x1a\xc1\xca\x03\x0a\x55\x2d\ \xdd\x57\xad\x6a\xe8\xa0\xe4\xb9\x32\x79\x63\x4f\xfb\xed\xf1\x0c\ \xa0\xc9\xce\x22\xad\x1b\x7f\x6e\x97\x8b\xb7\x5f\x7b\xd6\xb0\xfa\ \x79\xc1\xc6\x6a\xb1\x90\x96\x92\x48\xbf\xf4\x14\x12\xe2\x7d\x41\ \x90\x2e\x97\x87\x6d\x7b\xcc\x14\xe4\x26\x17\xe2\x51\x61\xed\x51\ \x85\x16\x0d\xb1\x58\x52\x42\x73\x27\x0b\x81\x1e\x65\xd7\xea\x91\ \x00\xbc\xb8\xc9\xfb\xf0\xc1\x66\x72\xb5\xde\xf7\xc9\xfb\x8b\x69\ \x3a\x6e\x48\x12\x54\xdd\x89\x8b\x8b\xa1\x64\xd4\x10\x86\x0f\x2a\ \x64\xd8\xc0\x02\x86\x0c\xc8\x25\xbb\x5f\x1a\x42\x9c\xbb\x76\xab\ \xad\x6f\xe4\x8e\x6f\xff\x7f\x06\x59\x69\x12\xce\xec\xa8\x13\x9a\ \x06\xff\x29\x5a\x1c\x64\x3e\xb7\xc9\xfb\x93\xaf\x4e\xb4\xfc\x3a\ \xd0\xbe\x03\x16\x80\xc5\x8b\xa5\xe5\xa8\x5d\xfe\xaf\xd6\x09\xfc\ \xee\x6d\x9b\xd9\xb5\x65\x53\xa0\xdd\x86\x05\x39\x99\xe9\xcc\xbc\ \x62\x1c\x57\x94\x5c\xc6\x98\x11\x83\x88\xb2\x06\x2d\x56\xc3\xa4\ \x97\xd3\x6c\x87\xa3\x2d\x81\xc7\xcb\x1d\xb7\x89\x9f\x2e\x5e\x2c\ \x1f\xbf\xe3\x0e\x11\x50\x61\x86\x80\x05\xa0\x63\xa0\xfa\xfb\xf6\ \x66\x25\x4e\xcb\x3d\x0e\x5b\x27\x2b\x97\xbd\x15\x68\x97\x86\x12\ \x6d\xb5\x32\x75\xe2\x28\xe6\x4e\x9f\xc8\x15\x25\x97\xa1\x28\x41\ \x4f\xa5\x60\xd2\xcb\x91\x12\xb6\x1e\x53\x7a\xb4\x0d\xd6\xe1\x14\ \x71\x2d\x03\xd4\x3f\x00\x3f\x08\xe4\xfe\x80\x04\xe0\x31\x29\xad\ \x8d\x9f\xf1\x4d\xad\xf7\x2d\xff\x70\x69\xc4\x45\xf6\xc5\xc5\x46\ \x73\xc3\xac\xc9\xdc\xbd\x60\x36\x19\xa9\xc9\x46\x9b\x63\xd2\x8b\ \xa8\x6c\x09\x6c\xea\x7f\x3e\x8d\x1d\xca\x37\x17\xef\x94\xff\x7d\ \xc7\x28\xa1\x79\x53\x2d\x20\x01\x18\x54\xae\xfe\xfe\x80\x53\xd1\ \x94\xe2\xeb\xf0\xfe\xdd\xec\xde\xb6\x39\x90\xee\x0c\x21\x36\x26\ \x9a\x3b\x6e\x98\xc6\x97\xe6\xcd\x20\x25\x39\xa0\xd0\x06\x13\x93\ \x4b\x22\x81\x03\x17\x39\xe3\x0f\x04\xbb\x87\xe8\xd6\x4e\xfe\x04\ \x7c\x4b\xeb\xbd\x9a\xe7\xb1\x8b\xa5\xb4\x34\x74\x88\xff\xd0\x72\ \x8f\xea\xf5\xb2\xe2\xc3\x25\x5a\xbb\x32\x8c\x29\x13\x46\xb2\xe8\ \x89\x47\x79\xe8\xce\x79\xe6\xe0\x37\x09\x0a\x76\x17\xb4\xeb\x58\ \x7d\xbe\xd1\x26\xbf\xf6\xdc\x61\xa9\x39\xef\xa6\xe6\x19\x80\x7b\ \x8b\xfa\xfb\x36\x8d\x6f\xff\xad\x65\xeb\x69\x6e\x3c\xae\xb5\xab\ \x90\x93\x95\x91\xca\x0f\x1e\x58\xc0\xb4\x49\x63\x8c\x36\xc5\xa4\ \x97\x63\xd7\xb9\x76\x8d\xcd\x25\xa2\x3c\xc7\xd5\x3f\x03\xdf\xd0\ \x72\x9f\xa6\x19\x80\x94\x52\x34\x74\x28\x9a\x3a\x70\xb9\x9c\x6c\ \xf8\xec\x23\x2d\xb7\x18\x42\xc9\xe8\xa1\x2c\xfc\xdd\x0f\xcd\xc1\ \x6f\x12\x12\xa2\x82\x90\x8d\xb3\xc9\xa6\xdc\xa7\x35\x50\x48\x93\ \x19\x2f\x95\xab\x3f\x6e\xb6\x2b\x7e\xd7\xf4\x53\x04\x1c\xa9\x58\ \x4e\x47\x7b\xf8\x6e\xfc\x29\x8a\xc2\x83\x5f\x9a\xcb\x3d\xf3\xe7\ \xa0\x28\x41\x2d\x54\x6c\x62\x72\x9a\xa6\x4e\xfd\x9f\xb5\x4e\x17\ \x31\x9d\x03\xd5\x9f\x02\xbf\xf0\xf7\x1e\x4d\x02\x90\x9d\x28\xe6\ \x67\x24\x48\x6c\x6e\x68\x73\xc0\x09\x9b\xa0\xe3\x12\xeb\x18\xab\ \x05\xc6\x64\xd8\xf8\xeb\xca\x55\x5a\xba\x08\x29\xf1\xf1\xb1\xfc\ \xef\x23\x5f\x65\xe2\xb8\x61\x46\x9b\x62\xd2\x87\x70\x7a\x60\x67\ \x7d\x70\x5e\x36\x8d\x76\xe5\xfb\x04\x43\x00\xca\xab\xe5\x38\x84\ \x7a\x5e\xf0\x81\xc4\xe6\x82\x23\xcd\x82\xa3\x2d\x02\xa7\xc7\x97\ \xd3\x3c\x33\x41\x32\x26\x47\xf2\xce\x87\xeb\xb0\xdb\x75\xdc\xe9\ \xd0\x91\xa4\x84\x78\xfe\xef\xa7\x5f\x67\xd4\xd0\x01\x46\x9b\x62\ \xd2\x87\xf0\xaa\xb0\xb1\x5a\xc1\x1d\xa4\x8a\x6b\x6d\x0e\xd2\x9e\ \x2f\x93\x5f\xb9\x7f\x82\x78\xd9\x9f\xeb\xfd\x9f\x01\x08\xf5\xbf\ \xe1\xc2\x14\xdf\xf1\xd1\x30\x32\x5b\x72\x59\x96\xc4\xee\xf1\x95\ \x38\x8a\xb6\x80\xc7\xeb\x65\xc9\xb2\xf0\x0c\xf3\x4d\x4f\x4d\xe2\ \x89\x9f\xfd\x07\x83\xfb\xe7\x19\x6d\x8a\x49\x1f\xc2\xa3\xc2\xc6\ \xaa\x73\xe3\xfb\x83\x41\x9b\x93\x5f\x01\x7e\x09\x80\x5f\x9b\x80\ \x1b\x1b\x64\x0e\x82\xdb\xbb\xba\x46\x08\x88\x8f\xf2\x0d\x7e\x80\ \xe5\x6b\x2b\x68\x38\x11\xf4\xd2\x66\x9a\x49\x4f\x4d\xe2\xef\xbf\ \xfa\xae\x39\xf8\x4d\x42\x4a\xab\x03\x56\x1d\x52\x68\xe8\x08\xfe\ \x3e\x53\x53\x27\x03\x9f\xdf\x22\x47\xf9\x73\xad\x5f\x33\x80\x28\ \x8f\xfa\x55\x29\x89\xd2\x62\xc4\x1b\x1f\xae\xd1\x72\x79\x48\x88\ \x89\x8e\xe2\x37\x8f\x3e\x40\x61\x6e\xa6\xd1\xa6\x98\xf4\x11\x1c\ \x1e\xd8\x77\x5c\x70\xa4\x59\x10\x68\xa6\x6c\xad\xa8\x12\x9c\x4e\ \xf9\x7f\x40\xb7\xf9\x03\xbb\x9d\x01\x48\x29\x85\x94\x3c\xa0\xc5\ \x80\xa3\xd5\xf5\xec\x3e\x10\x5e\x25\xbb\x85\x10\xfc\xe8\x9b\x5f\ \x36\xd7\xfc\x26\x41\x47\x05\xea\xda\x05\x65\x35\x82\x4f\xf7\x2b\ \x1c\x6a\x0a\xdd\xe0\x3f\x45\x93\x4d\xcc\x7e\x6c\xa5\xec\xf6\x05\ \xdf\xed\x05\x15\x35\x9e\x59\x08\x45\x53\x71\xcf\x0f\x56\x85\x5f\ \xb4\xdf\x03\x77\xcc\x65\xce\x55\x25\x46\x9b\x61\xd2\xcb\xb1\xbb\ \x61\xfd\x51\x45\x57\x2f\xbf\x00\xed\x88\x2a\x48\xe7\x11\xe0\xf1\ \xae\xae\xeb\x7e\x0f\x40\x28\x5f\xd7\xd2\xb1\xaa\xaa\x7c\xfc\x59\ \x78\xf9\xfc\x97\x8c\x1e\xca\xfd\xb7\x5d\x63\xb4\x19\x26\x7e\x90\ \x94\x10\x4f\x6c\x4c\xb4\xd1\x66\x04\x84\x04\x2a\x6a\x85\xe1\x83\ \xff\x14\x36\x87\xec\x36\x36\xa0\xcb\x19\xc0\x86\x6a\x99\x01\xea\ \x2d\x5a\x3a\x2d\xdb\x7e\x80\xe3\x4d\xad\x5a\x6e\x09\x2a\x71\x71\ \x31\xfc\xf8\x5b\x5f\xbe\x20\x41\x87\x49\x78\xa1\x28\x82\xeb\x66\ \x5c\xce\x37\xef\xbe\x81\xd4\x64\xdd\x0b\xe0\x9c\xc6\xe6\x70\xd2\ \x70\xbc\x99\xa6\xd6\x76\x4e\x34\xb5\xd1\x61\x77\x60\xb7\x3b\x70\ \x38\xdd\xb8\xdc\x1e\x84\x80\xa4\x84\x38\x14\x45\x21\x25\x29\x81\ \x94\xa4\x04\xd2\x52\x12\x29\xc8\xcd\x24\x39\xb1\x6b\x1f\xb8\xa3\ \xcd\x22\x24\x9b\x7c\xfe\xd2\x62\x17\x45\x2f\x6e\x90\x03\xef\x9d\ \x24\x0e\x5f\xea\x9a\x2e\x05\xc0\x2a\xd4\x3b\x81\x18\x2d\x9d\xae\ \xdd\x1c\x5e\xe9\xbd\xbf\x7d\xcf\x4d\xe4\x66\xa5\x1b\x6d\x86\x49\ \x17\x0c\x1f\x5c\xc8\x23\x5f\xbb\x95\x91\x43\x75\x49\x74\x0b\x80\ \xcb\xe3\xe1\xd0\xd1\x5a\x0e\x57\xd5\x73\xb8\xba\x8e\x23\x95\x75\ \x1c\xa9\xa9\xe7\x58\x43\x53\xc0\xf5\x26\x93\x93\x12\xe8\x9f\x97\ \xc5\x65\x43\x0a\x19\x3d\x7c\x20\xa3\x87\x0f\x20\x2b\x23\x15\x80\ \xfa\x0e\xc1\x8e\xba\xf0\x19\xfc\x70\x72\x33\x50\xa8\xbf\x02\xee\ \xbe\xd4\x35\x5d\x0a\x80\x40\xde\x7e\x91\xa3\xff\x2e\xf9\xbc\x7c\ \xb7\xa6\xeb\x83\xc9\x84\x31\x43\xb9\x79\xce\x15\x46\x9b\x61\x72\ \x09\x92\x93\x12\xf8\xc6\x9d\xd7\x73\xe3\xd5\x57\xf4\xd8\x0d\xdb\ \xe3\xf5\xb2\x6b\xff\x51\x2a\x76\x1e\xa0\x7c\xc7\x01\x76\xec\x3d\ \x82\xd3\xa5\x6f\xc4\x4d\x5b\x7b\x27\xdb\xf7\x1e\x66\xfb\xde\xc3\ \x2c\x7e\xff\x33\x00\x06\x15\xe5\x32\x66\xcc\x18\x92\x0b\xc6\x90\ \x9d\x57\xa4\x6b\x7f\x7a\xd0\x6a\x57\x6e\xe8\xea\xe7\x97\x14\x80\ \xcd\x47\x64\x2e\xa8\x57\x6a\xe9\xec\x68\x75\x3d\xb5\xf5\x8d\x5a\ \x6e\x09\x1a\x8a\x22\xf8\xfe\x57\xe7\x9b\x53\xff\x30\x44\x51\x04\ \x37\xce\xbe\x82\x6f\xdc\x75\x3d\xc9\x49\x81\x87\x5b\x77\xd8\xec\ \x7c\xb6\x61\x3b\xab\x3e\xdf\x4a\xc5\xae\x03\xd8\x1d\xa1\x4f\x32\ \x7b\xa8\xf2\x18\x87\x2a\x8f\x01\x1f\x93\x9e\x99\xcd\xf8\x89\x57\ \x32\x6a\xdc\xe5\xc4\xc4\x6a\x4a\x96\x15\x34\xda\x9d\xa4\x3c\x5f\ \x26\x27\xdc\x3f\x41\x94\x5d\xec\xe7\x97\x14\x00\x4b\xb4\x7a\x87\ \x94\x68\x8a\x2c\x5a\x5f\xbe\x4b\xab\x7d\x41\x63\xf6\x94\x62\x06\ \x15\x69\xce\x57\x6a\x12\x64\x46\x0e\xed\xcf\x23\x5f\xbb\x95\xe1\ \x83\x0b\x03\xba\xdf\x66\x77\xb2\x66\xe3\x76\x96\xaf\xaf\x60\xd3\ \xd6\xbd\xb8\x3d\x01\xa5\xc2\x0b\x0a\x4d\xc7\xeb\x59\xf1\xc1\x52\ \xd6\x7c\xf2\x1e\xe3\x4a\xa7\x30\x69\xda\x1c\xe2\xe2\x83\xb7\x9f\ \xe1\x0f\x12\x70\xb9\xe5\x8f\x81\xdb\x2e\xf6\xf3\x4b\x0a\x80\x94\ \xf2\x0e\xad\xd3\xff\x0d\x5b\xf6\x68\xba\x3e\x58\x58\x2c\x0a\x5f\ \xbd\x23\xa0\x1a\xa5\x26\x41\x22\x35\x39\x91\x6f\xde\x7d\x03\xd7\ \xcd\xb8\x3c\xa0\xe9\xfe\xd1\xea\x7a\x96\x2c\x5b\xcb\x87\xab\x37\ \x85\x6d\x7c\xc9\x29\xdc\x6e\x17\x9b\x3f\x5f\xc5\xb6\xb2\xcf\x29\ \x99\x3c\x83\xcb\xaf\x9a\x4d\x74\xb4\xa6\xad\x34\x5d\x69\x77\x89\ \xab\x2f\xf5\xb3\x8b\x0a\xc0\xd6\x6a\x59\xe0\x45\x9d\xac\xa5\x13\ \x55\x55\xd9\xb5\xef\xa8\x56\xdb\x82\xc2\xdc\x69\xa5\x14\xe5\xf5\ \x28\x5d\xba\x89\x4e\x28\x8a\xc2\xfc\xb9\x53\xf8\xda\x97\xaf\x23\ \x29\xc1\xef\x48\xf2\xd3\x6c\xa8\xd8\xc3\x2b\xef\xac\xa0\x7c\xc7\ \x81\x80\x37\xef\x8c\xc2\xe5\x72\xf2\xc5\x67\x1f\xb1\x7b\xdb\x26\ \xe6\xdc\xf8\x25\x06\x0c\x19\x61\x88\x1d\x6d\x0e\x52\x5e\xdc\x20\ \x4b\xef\x9d\x24\x2e\x38\x9f\xbf\xa8\x00\xa8\x42\xbd\x09\x8d\xc9\ \x42\x0e\x55\x1d\xc3\xe6\x30\x5e\x99\x15\x45\x70\xdf\x6d\x73\x8c\ \x36\xc3\x04\x18\x3b\x62\x10\x0f\x7f\x6d\x01\x43\x07\xe4\x6b\xbe\ \x77\xdb\x9e\x43\x3c\xfd\xf2\x07\x6c\xdd\x7d\x30\x08\x96\x85\x96\ \xd6\x96\x26\xde\x58\xf4\x24\xa3\x8a\x27\x31\xfb\xfa\x5b\x43\x3e\ \x1b\x90\x80\x03\xf5\x87\xc0\x5d\xe7\xff\xec\xa2\x02\x20\x11\xd7\ \xfa\x6e\xf3\x9f\x1d\x7b\xc3\xe3\xed\x3f\x69\xdc\x08\xf2\xb3\xfb\ \x19\x6d\x46\x9f\x26\x3d\x2d\x99\x6f\xdd\x7d\x23\x73\xa7\x4d\xd0\ \xbc\x09\x5b\x5b\xdf\xc8\x5f\x9f\x7f\x8b\x35\x9b\x76\x04\xc9\x3a\ \xe3\xd8\x59\xb1\x81\xfa\x9a\x4a\x6e\xbe\xf3\x6b\xa4\xa5\x87\xf6\ \x19\xb5\xb9\x94\x99\x17\xfb\xfc\x02\x01\xd8\xb9\x53\x46\x3b\x51\ \x2f\x7a\x71\x57\xec\xdc\x77\x24\x00\xb3\xf4\x67\xfe\x75\x53\x8d\ \x36\xa1\xcf\x62\xb1\x28\xdc\x7a\xdd\x55\x3c\x78\xc7\x5c\x12\xe2\ \xb5\xed\x82\x3b\x5d\x6e\x5e\x7e\x6b\x39\x2f\xbf\xb5\x42\xf7\xe3\ \xbb\x70\xe2\x44\xc3\x31\x5e\x7a\xfa\x0f\xdc\x78\xfb\x7d\x21\x5d\ \x12\xb4\xb9\xc8\x7e\xe9\x0b\x99\x7c\xf7\x64\xd1\x76\xf6\xe7\x17\ \x08\x80\x33\xd5\x33\x0d\x14\xcd\x5b\x97\x3b\xf7\x1b\x1f\xfc\x93\ \x96\x92\xc4\xa4\x71\xc6\xac\xb3\xfa\x3a\xc5\xa3\x86\xf0\xf0\x83\ \x0b\x02\x3a\x79\x59\xb3\x69\x07\x7f\x7d\xfe\xad\xb0\x39\x42\x0e\ \x36\x4e\x87\x8d\xa5\xaf\xfc\x93\x1b\x6e\xbb\x8f\x61\x23\xc7\x85\ \xa4\x4f\x8f\x17\xe1\xb6\xa8\xdf\x00\x7e\x7f\xf6\xe7\x17\x2e\x01\ \x84\x72\x9d\xc6\xd9\x3f\x6e\x8f\x97\x9a\x3a\xe3\xb3\xfe\xce\x9a\ \x32\x0e\x8b\xc5\xac\xd8\x13\x4a\x32\x52\x93\xf9\xe6\x3d\x81\x4d\ \xf7\xab\xeb\x4e\xf0\x97\x67\xdf\x0c\xab\xe3\xe3\x50\xa1\x7a\xbd\ \xbc\xbb\xf8\x79\xae\x5f\xf0\x15\x2e\x1b\x5b\x1a\x92\x3e\x6d\x1e\ \xf1\x25\xba\x15\x00\xc9\xb5\x5a\x1b\xae\xad\x3b\x81\xd7\x1b\xa4\ \x1c\x47\x1a\x98\x31\x39\x34\x6a\x6a\xe2\xab\x80\x7c\xc7\x0d\xd3\ \xb9\xff\xb6\x6b\x88\x8f\xd3\xb6\xa9\x65\x77\xb8\x78\x71\xe9\x27\ \xbc\xf6\xce\x2a\x5c\x1e\x4f\x90\x2c\x0c\x7f\xa4\x54\xf9\x60\xe9\ \xcb\x58\xac\x51\x21\x99\x09\x74\xb8\xc4\x05\x49\x42\xce\x11\x80\ \xed\xf5\x32\xdb\xed\x51\x47\x6a\x6d\xf8\x68\xad\xf1\x6f\xff\x84\ \xf8\x38\xc6\x8c\x18\x68\xb4\x19\x7d\x82\xe2\x51\x83\xf9\xe1\xd7\ \x6e\x63\x60\x61\x8e\xe6\x7b\xd7\x6d\xde\xc9\x13\x0b\x97\x52\x77\ \xbc\x29\x08\x96\x45\x1e\x3e\x11\x58\x44\x72\x4a\x1a\x39\xf9\xc1\ \x75\x25\xb6\x39\x89\x7d\x63\x9b\x1c\x71\xdb\x58\x71\xda\x61\xe7\ \x1c\x01\x70\x7b\xbd\x53\xb5\x3a\xff\x00\x54\xd5\x1a\x5f\xea\x7b\ \xd2\xb8\x61\x58\x2d\x66\x95\xde\x60\x92\x99\x9e\xc2\x37\xbe\x72\ \x03\xd7\x4e\xd7\x3e\x65\xad\xac\x6d\xe0\xcf\xcf\xbd\xc9\x86\x8a\ \xf0\x70\x16\x0b\x27\x3c\x6e\x37\x6f\xfd\xfb\x19\xee\x7e\xe8\x11\ \x12\x93\x53\x82\xd6\x8f\x04\xda\x9c\xea\xfd\xc0\x8f\x4e\x7d\x76\ \x8e\x00\x08\xc4\x95\x81\xb8\x5a\x1c\xad\x69\xe8\x99\x65\x3a\x50\ \x32\xc6\x4c\xed\x1d\x2c\xa2\xad\x56\xbe\x74\xd3\x0c\xee\x5d\x30\ \x87\xb8\x58\x6d\xb1\xfa\x36\xbb\x93\xe7\x5e\xff\x88\xd7\xdf\xff\ \x0c\x8f\x37\x7c\xdc\x76\xc3\x8d\x8e\xf6\x56\xde\x5d\xfc\x1c\x5f\ \x7e\xf0\x7b\x08\x11\xbc\x7d\x2c\xbb\x5b\xcc\xe1\x52\x02\x20\x91\ \x57\x06\x32\x03\xa8\x6b\x30\x7e\x3a\x37\x76\xc4\x00\xa3\x4d\xe8\ \x95\x94\x8c\x1e\xca\x0f\x1f\x5c\xc0\x80\x00\xa7\xfb\x7f\x7c\x66\ \x09\xf5\x27\x9a\x83\x60\x59\xef\xa3\xa6\xea\x30\x1b\xd7\x2e\x67\ \xd2\x55\xc1\x73\x64\xb3\xb9\x18\x7e\xf6\xbf\x4f\x0b\xc0\xfa\x2a\ \x19\x87\x54\xc7\x07\xd2\x68\x63\x6b\x47\x4f\xed\xea\x11\x09\xf1\ \x71\x0c\x28\x30\x03\x7f\xf4\x24\x37\x2b\x9d\xef\xde\x3f\x9f\x69\ \x97\x8f\xd6\x7c\xef\xc1\xa3\xb5\x3c\xb1\x70\x29\x5b\x76\x45\xbe\ \x17\x5f\xa8\x59\xbf\x72\x19\x83\x86\x8e\x22\x33\x27\x38\x59\xab\ \x3b\x5c\x22\xe1\x1f\xdb\x64\xda\xb7\xc6\x8a\x66\x38\x4b\x00\x62\ \x2d\x4c\x44\xa2\x69\x7e\xd7\xe9\x82\x6d\x75\x82\x86\x66\x63\x4b\ \x7f\x0d\x1d\x90\x67\x96\xf5\xd2\x89\xe8\xa8\x28\xbe\x72\xcb\x2c\ \xee\x9e\x3f\x9b\x98\x68\x4d\x89\xa0\xe9\xb0\xd9\x59\xf8\xea\x32\ \x96\x7e\xb4\x36\x2c\x4e\x85\x22\x11\xaf\xd7\xc3\x87\x6f\xbe\xc4\ \xdd\xdf\xf8\x4f\x14\x45\xff\xa5\x80\x04\x12\x5d\x7c\x09\x78\x0a\ \xce\x12\x00\x21\xd5\xcb\xb5\xac\xff\xdb\x9d\xb0\xe6\xb0\x82\xc3\ \xe5\xc5\x61\xb3\xe9\x6c\xa6\x36\xcc\xb0\x5f\x7d\x98\x70\x32\x77\ \x62\x5e\x76\x86\xa6\xfb\xa4\x94\x7c\xb8\x6a\x13\x4f\xbe\xf4\x1e\ \xcd\xad\xe1\x5b\x07\x32\x52\x68\xa8\xab\x61\x7b\xd9\xe7\x8c\x9b\ \xa8\x29\x1d\x87\xdf\xb8\x24\x73\x39\x5f\x00\x90\x8c\xf3\x77\xf9\ \x2f\x25\x94\x55\x2b\xb8\xbc\x60\xb3\x75\x18\x1e\xa5\x65\x0a\x80\ \x3e\xcc\x0d\x60\x77\x7f\xdf\xa1\x6a\xfe\xf8\xcc\x12\x76\x84\x89\ \x2b\x78\x6f\x61\xdd\xaa\x0f\x19\x39\xae\x94\xa8\x20\x04\x0e\xd9\ \x5d\x8c\x3d\xf5\xff\xa7\x05\x40\x8a\x33\x1f\x76\x47\x75\xab\xa0\ \xc5\xe1\xfb\x7f\x5b\x87\xf1\x8a\x6f\x06\xff\x84\x9e\xb6\x0e\x1b\ \xff\x7c\xe5\x7d\xde\xf9\xf4\x0b\x54\xd5\x9c\xee\xeb\x8d\xad\xa3\ \x9d\x0d\x6b\x96\x33\x75\xf6\xf5\xba\xb7\xdd\xe9\x92\xa7\x37\x18\ \x14\x80\xcd\x9b\x65\x14\xe0\x97\x13\xbd\x94\xb0\xef\xc4\x99\xa9\ \x82\xcb\x6d\x7c\x08\x70\x76\x66\xaa\xd1\x26\xf4\x19\x54\x55\xf2\ \xce\x27\x9f\xf3\xe5\xef\xfe\x9a\xb7\x3e\x5e\x6f\x0e\xfe\x20\x52\ \xf6\xf9\x4a\x6c\x9d\xfa\xbf\x60\x1d\x6e\x11\xfb\xf4\x66\x19\x0f\ \xa7\x66\x00\xd9\x8c\x00\xff\x36\x00\xab\x5a\xcf\xcd\x7b\xee\x75\ \x1b\xeb\xca\x29\x84\x20\xbb\x5f\x9a\xa1\x36\xf4\x15\x76\xed\x3f\ \xca\x1f\x9f\x59\xca\x9e\x83\xc6\x07\x7e\xf5\x05\xdc\x6e\x17\x5b\ \x36\xad\x65\xca\x8c\x6e\x2b\x7c\x69\x42\x05\xa2\xe1\x1a\xe0\x2d\ \x05\xc0\x62\xf1\xfa\x35\xfd\x6f\x77\x72\x41\xea\x63\x8f\xc7\xd8\ \xd0\xcd\xc4\xf8\x38\xcd\xbb\xd5\x26\xda\x68\xeb\xb0\xf1\xe7\x67\ \xdf\xe4\x3f\xfe\xe7\xcf\xe6\xe0\x0f\x31\x15\x1b\xd6\xe2\x71\xeb\ \x3f\xc6\x54\xd5\x17\xf2\x6f\x05\x90\xaa\x18\xd5\xd5\x06\xa0\xc3\ \x03\xc7\xda\x04\x7b\x8e\x0b\x5c\xe7\x39\x73\x79\xbc\xc6\xce\x00\ \xba\x2b\xd6\x60\x72\x06\x21\x04\x03\x0a\xb2\xfd\xbe\x5e\x55\x55\ \xde\xfc\x68\x3d\xcf\xbc\xfa\x21\xed\x9d\xc6\x9e\xf4\xf4\x55\xec\ \xb6\x0e\x76\x6f\x2f\x63\x4c\x89\xa6\x0c\x7d\xdd\xe2\x51\x45\x09\ \x9c\x5a\x02\x08\x06\x81\x6f\x7d\xdf\xe6\x80\x86\x4e\x41\x9b\x13\ \x3a\x5d\x82\x0e\x27\x17\x0c\xfa\xb3\x51\x0d\x8e\xe6\x4a\x4e\x32\ \x05\xc0\x1f\x2e\x1b\x52\xc4\xc3\x0f\x2e\xf0\xbb\xf8\xc6\xb6\xdd\ \x87\xf8\xe3\x33\x4b\x39\x70\xb4\x26\xc8\x96\x99\x74\x47\xd9\xe7\ \xab\x74\x17\x00\x97\x57\x14\xc2\x49\x01\x58\x77\x44\x5c\xa3\x4a\ \x41\x9b\x03\xdc\x1a\xf7\x74\x82\x31\x3d\xd1\x82\xd6\x50\xd4\xbe\ \x46\x6a\x72\x22\xdf\xb8\x6b\x1e\xf3\x66\x4d\xf2\xcb\x59\xaa\xb1\ \xa5\x8d\x7f\x2c\x7a\x97\x8f\x3f\x2b\x33\xfc\x78\xd7\xc4\xc7\x89\ \x86\x63\x34\x1c\xab\x26\x2b\xb7\x40\xb7\x36\x9d\x1e\x99\x01\x27\ \x05\xa0\xd9\x2e\x52\x3d\x11\xba\x99\x1b\x6d\xed\xb6\xc0\x71\x9f\ \xc4\x62\x51\x98\x37\x6b\x12\xdf\xb8\x73\x1e\x29\xc9\xdd\x17\xdf\ \xf0\x7a\x55\x96\x2c\x5b\xcb\xc2\xd7\x96\xd1\x69\xb3\x87\xc0\x42\ \x13\x2d\xec\xde\x5e\xae\xab\x00\x38\x3c\xc2\x77\x0a\xf0\xd7\x0d\ \x72\xe0\xf1\xd6\x00\x22\x80\x4e\x62\xb1\x18\x3b\x00\x2d\x56\x33\ \x03\xd0\xf9\x14\x8f\x1a\x7c\x32\x3d\x97\x7f\xfe\xe4\xe5\x3b\x0f\ \xf0\xc4\x33\x4b\x38\x5c\x55\x17\x64\xcb\x42\x4b\x5a\x4a\x12\x69\ \x29\x89\xa4\x24\x25\x90\x9c\x14\x8f\xd7\xeb\xc5\xee\x70\xd1\x61\ \x73\x50\x73\xec\x04\x1d\x11\x24\x74\x7b\x77\x94\x33\x7d\xce\x8d\ \xa0\x53\xa5\x2b\x97\x17\xe5\x2f\x9f\xc9\x4c\x6b\x9c\x85\x1e\x15\ \xcf\x0b\x86\xbf\xb2\x16\xcc\x1c\x00\x67\xc8\xca\x48\xe5\xdb\xf7\ \xde\xcc\xec\x2b\xfd\x8b\xe9\x6a\x68\x6c\xe1\xef\x2f\xbe\xcd\xf2\ \x75\x5b\x82\x6c\x59\xf0\x89\xb6\x5a\x19\x37\x6a\x10\x25\xa3\x86\ \x32\x7c\x50\x21\x43\x06\xe4\x91\x9e\x9a\x74\xc9\xeb\xa5\x94\x54\ \x1d\x3b\xce\x9e\x03\x55\x6c\xd8\xba\x87\x35\x9b\x76\x60\xb3\x39\ \x42\x68\xb1\x36\xda\x5a\x9b\xa9\xae\x3c\x4c\x41\xff\x41\xba\xb5\ \x99\x94\x24\x63\xa2\xc1\x00\x00\x20\x00\x49\x44\x41\x54\xc0\x64\ \xab\xe2\x51\x2f\xd3\x58\x02\xe0\x1c\x84\xc1\x03\xd0\x74\x44\xf1\ \x3d\xfc\x5f\xbe\x79\x06\xf7\xcc\xf7\x2f\x5e\xdf\xed\xf1\xb2\xf8\ \xbd\x55\x3c\xff\xc6\xc7\x86\xd4\xd3\xd3\x8b\xb8\xd8\x68\xa6\x4f\ \x1a\xcb\xac\x2b\x8b\x29\x19\x35\x84\xd8\x18\xff\x63\xd9\x84\x10\ \x14\xe5\x65\x51\x94\x97\xc5\x35\xd3\x26\x60\x73\x38\x79\x7f\xc5\ \x06\x16\xbd\xb9\x9c\xa6\xe6\xb6\xee\x1b\x30\x80\xfd\xbb\xb6\xe8\ \x2a\x00\x0a\x8c\xb1\xaa\x82\x1e\xd5\x64\x56\x0c\x16\x00\x4f\xa4\ \x6e\x5e\xe8\xc4\x94\x09\x23\xf9\xde\x57\xe7\x53\x90\xe3\x9f\x3b\ \xf4\xc6\x2d\x7b\xf9\xd3\xb3\x4b\xa9\xac\x35\x3e\x89\x4b\xa0\x0c\ \x1f\x5c\xc8\xad\xd7\x4e\x65\xc6\xe4\x71\xba\x6d\x02\xc7\xc7\xc6\ \x70\xfb\xf5\xd3\x98\x37\x73\x12\xff\xfa\xf7\x07\xbc\xf1\xe1\x9a\ \xb0\xdb\x04\x3d\x72\x68\xaf\xae\xed\xb9\x25\x03\xad\x28\x4a\x7a\ \x4f\x1a\xb1\x18\xbc\x04\x70\x1b\xec\x87\x60\x14\x85\xb9\x99\x7c\ \xf7\xab\xb7\x30\xa5\xc4\xbf\x14\x8e\xb5\xf5\x8d\xfc\xed\x85\xb7\ \xf8\x6c\x63\xe4\x16\xdc\x98\x5c\x72\x19\x77\xdd\x3c\x8b\x92\x51\ \x43\x82\xd6\x47\x7c\x5c\x0c\xdf\x7f\x60\x3e\xa5\x63\x87\xf1\x8b\ \x3f\x2d\x0a\x8b\x6a\x57\xa7\x68\x6c\xa8\xa3\xa3\xbd\x95\xc4\x24\ \x7d\xd2\x86\xa9\x1e\x99\x67\x2d\xce\x53\x77\x0f\x4a\x63\xde\xd6\ \x63\x0a\xcd\x01\xec\x89\x18\x3d\x03\xb0\xdb\x23\x77\x0a\x1b\x08\ \x71\xb1\xd1\xdc\x77\xeb\x35\xdc\x71\xe3\x74\xbf\x4e\x40\x7c\x05\ \x37\x56\xf0\xf2\x5b\xcb\x23\xb6\xe0\xc6\xd8\x11\x83\xf8\xe6\x3d\ \x37\x30\x66\x78\xe8\x92\xbe\x5e\x59\x3a\x8a\xbf\xfe\xe2\xdb\x7c\ \xff\x97\x4f\xd1\x11\x46\x4e\x50\x95\x07\xf7\x31\x72\xfc\x44\x5d\ \xda\xf2\x48\x91\x69\x55\x20\x23\x35\x0e\xae\x1a\xa8\x52\x56\x2d\ \xa8\x69\xd3\xb6\xcb\x68\xb5\x6a\xcb\x11\xa7\x37\x6d\x9d\x91\xb3\ \x93\xdb\x13\x84\x10\xcc\x9e\x32\x9e\x6f\xdd\x7b\x13\x59\x19\xfe\ \x05\x3f\x7d\xb6\x71\x07\x7f\x7b\x21\x72\x0b\x6e\xe4\x66\xa5\xf3\ \x9d\x7b\x6f\x66\xfa\x64\xbf\x03\x55\x75\x65\xf8\xe0\x42\x1e\x7a\ \xe8\xeb\xfc\xe5\x6f\xff\x30\xdc\xdf\xe5\x14\x47\x0e\xed\xd5\x4d\ \x00\xbc\x2a\x69\x56\x89\xc8\x00\x89\x22\x60\x42\x81\xc4\x79\x14\ \x4e\x74\xfa\x2f\x02\xf1\xf1\xdd\x9f\x31\x07\x93\xf6\x8e\xf0\x51\ \xe7\x60\x31\xb8\x7f\x1e\x3f\x78\x60\x3e\xc5\x7e\x4e\x7d\x23\x3d\ \x03\x6f\x4c\xb4\x2f\x2b\xd1\x57\x6e\xd1\x9e\x95\x48\x4f\xaa\x5b\ \x05\x4a\xea\x20\x66\x5f\x7f\x1b\x1f\xbd\xfd\x6f\xc3\xec\x38\x9b\ \xaa\xc3\xfb\x75\x6b\x4b\x55\x65\xa2\x15\xd4\x7e\xa7\x12\x81\x2a\ \x02\x26\x15\x49\x3e\x3b\x74\x6e\xc4\x5f\x57\xc4\xc6\x19\xeb\x8a\ \xdb\xda\xde\x89\xd7\xab\xf6\xca\x8a\x40\x49\x09\xf1\x7c\xed\xcb\ \xd7\x71\xcb\x35\x53\xfc\xfa\xfd\x6c\x0e\x27\x2f\x2e\xf9\x84\xd7\ \xde\x5d\x85\xdb\x13\x99\x19\x78\xaf\x9a\x38\x9a\xef\xde\x7f\x8b\ \xe6\xac\x44\xc1\x60\xdf\x71\xdf\xb8\x18\x53\x32\x99\xc3\xfb\x77\ \xb1\x6f\xd7\x56\x83\x2d\x82\xf6\xb6\x16\x6c\x1d\xed\xc4\x27\x5e\ \xfa\x88\xd3\x5f\x3c\x52\xc4\x59\x41\x9c\x33\x9f\x8c\x52\x60\x72\ \x7f\x95\xcf\x0e\x29\x38\xfd\xd8\x5f\x8b\x8d\x8b\xf7\x39\x27\x18\ \xb4\x63\xaa\xaa\x2a\xc7\x9b\x5a\xc8\xc9\xec\xd1\x5e\x66\x58\xa1\ \x28\x82\x1b\x66\x4d\xe2\xa1\xbb\xe6\x91\x9a\xec\x5f\x99\xc6\x4f\ \xd7\x55\xf0\x8f\x17\xdf\xa1\xa1\xb1\x25\xc8\xd6\x05\x87\xa2\xbc\ \x2c\xbe\xff\xd5\xf9\x4c\x2a\x0e\x9f\xda\x8e\x67\x3f\xd1\xb3\xae\ \xbb\x95\xc3\x07\xf6\xe0\x76\x19\xbf\x29\x58\x7f\xac\x9a\x81\x43\ \x2f\xeb\x71\x3b\x5e\x29\x63\xac\x42\x12\x2b\xcf\x9b\xf1\x27\x44\ \xc1\xd4\x01\x2a\x1b\x2a\x15\x3a\xba\xd9\x63\x53\x2c\x16\xa2\xa2\ \xa2\x0d\xfd\x62\xea\x8e\x37\xf7\x1a\x01\x18\x35\x74\x00\x0f\x7f\ \x6d\x3e\x23\x06\xfb\x57\x25\xe6\x50\xe5\x31\x9e\x58\xb8\x84\x8a\ \x9d\x91\x99\x81\x37\x3e\x36\x86\x7b\x6f\x9d\xc3\x97\x6e\x9c\x41\ \x94\x35\xbc\x9c\xba\x06\xa4\x49\xb6\x9f\x0c\x7f\x4f\x4c\x4e\xa1\ \xf4\x8a\x19\x7c\xbe\xfa\x23\x83\xad\x82\x06\xbd\x04\x40\x15\x16\ \xab\x14\x17\x4f\x04\x92\x14\x03\xb3\x86\xa8\x54\xb6\x08\x1a\x3a\ \xa0\xd5\x21\xb0\xb9\x2f\xfe\xa2\x8f\x8f\x4f\xa0\xd5\x40\x01\xa8\ \xaa\x3d\xce\xf8\x91\x83\x0d\xeb\x5f\x0f\xd2\xd3\x92\xf9\xe6\xc9\ \xaa\x3b\xfe\x14\xd9\xec\xb4\xd9\x59\xf8\xda\x32\x96\x2c\x8b\xdc\ \x0c\xbc\x57\x5f\x59\xac\x69\x53\x33\xd4\xf4\x4f\x93\xec\xaa\x17\ \x78\x4f\x3e\xf3\xa5\x53\x66\x50\xf6\xc5\x6a\x5c\x4e\x63\x3d\x06\ \x1b\x8e\x55\xeb\xd2\x8e\xaa\xa2\x58\xe9\x22\x13\x90\x22\x7c\x2a\ \x38\x20\x0d\x40\xe2\xf0\x40\x4d\xab\xa0\xaa\x55\xd0\xea\x38\x23\ \x06\xb1\x71\xf1\xb4\xb6\x18\x57\x1c\xe4\x60\x65\xad\x61\x7d\xf7\ \x14\xab\xc5\xc2\x6d\xd7\x5f\xc5\x03\xb7\xcf\x25\x3e\x3e\xb6\xdb\ \xeb\x4f\x65\xe0\x7d\xea\xe5\xf7\x68\x6a\x31\x3e\x1f\x63\x20\x0c\ \x2a\xca\xe5\xe1\x07\x17\xf8\xbd\xa9\x69\x14\x56\x05\x62\xa3\x7c\ \xe9\xef\x01\x62\x62\xe3\x19\x39\xb6\x94\x2d\x9b\xd6\x1a\x6a\x57\ \x7d\x9d\x4e\x02\x00\xc2\x8a\x20\xc6\xdf\x72\xe0\xb1\x56\x18\x9c\ \x21\x19\x9c\x21\x71\xab\xd0\x66\x87\x76\xa7\xe0\x93\x7e\x69\xd4\ \xeb\xa4\x4a\x81\xb0\xff\x70\x64\x0a\x40\xe9\xd8\x61\xfc\xe0\xab\ \xf3\xfd\xae\xba\x13\xe9\x19\x78\x13\xe2\xe3\x78\xf0\x4b\xd7\x72\ \xeb\xb5\x53\x23\x62\xd3\x56\x05\x6c\xe7\x9d\xfe\x8d\x2b\xbd\xd2\ \x70\x01\x68\x69\x6e\xc4\xe3\x76\x63\x8d\xea\xf1\x09\x89\xb0\x6a\ \x2d\x06\x72\x8a\x28\x05\x32\x12\x20\x23\x41\x32\xbc\x28\x8d\xad\ \xdb\x7b\x6a\x4b\xe0\xec\x39\x58\x89\xcb\xe3\x89\x98\xd0\x60\xad\ \x45\x36\xdb\x3b\x6d\x3c\xfb\xda\x47\x2c\x59\xb6\x36\x22\x63\x1f\ \x84\x10\x5c\x33\x6d\x02\xdf\xbe\xe7\xa6\x2e\x03\x74\xc2\x0d\xbb\ \xeb\xc2\x25\x6f\x66\x4e\x1e\xe9\x99\xd9\x34\x1d\x37\xb0\x20\xae\ \x94\xb4\xb5\x36\x91\xde\xcf\xff\xec\x4e\x17\xc3\xab\x22\xba\x5c\ \x02\xf8\x4b\xae\xc1\x47\x36\x4e\x97\x9b\x3d\xfb\x2b\x19\x7b\x99\ \x7e\x81\x12\xc1\x20\x26\x3a\x8a\xbb\x6e\x9e\xc5\x57\x6e\x99\xe5\ \x57\xe0\x8a\xaa\xaa\xbc\xf3\xe9\x17\xfc\xf3\xdf\x1f\xd0\xd6\xde\ \x19\x02\x0b\xf5\x67\xf8\xe0\x42\x1e\x7e\x70\x01\xa3\x87\x0d\xf0\ \xeb\xfa\xda\xfa\x46\x52\x92\xe2\x49\x88\x8f\x0b\xae\x61\x7e\x10\ \x6d\x05\x8b\xe0\xf4\x1e\xc0\x29\x86\x5e\x36\x96\x0d\xc7\x3f\x31\ \xc6\xa8\x93\xb4\x34\x37\xf6\x58\x00\x24\xe7\x15\x07\x0d\x94\xbc\ \x2c\xe3\xcf\x6c\x37\x6e\xdd\x1b\xb0\x00\x34\xda\xa0\xaa\x45\x70\ \x59\x96\x24\x26\x48\x93\x88\x94\xa4\x78\x16\x3d\xf1\xa8\xdf\xe7\ \xdb\x3b\xf6\x1d\xe1\x8f\xcf\x2c\x61\xdf\x21\xe3\x96\x56\x3d\x21\ \x39\x29\x81\x6f\xdc\x79\x3d\x37\x5e\x7d\x85\x5f\x99\x88\x1c\x4e\ \x17\x2f\xbf\xb5\x82\x57\xde\x5e\xc1\xa2\x27\x1e\x0d\x0b\x01\x88\ \x52\x60\x58\xa6\x64\x77\xc3\xb9\xf6\x0f\x18\x72\x19\x1b\x3e\x33\ \x56\x00\x5a\x9b\xf5\xf1\xee\xb4\x02\x2e\xa0\x47\xdf\x76\x6e\x96\ \xf1\x69\xb9\x57\x6f\xd8\xc6\xd7\xbe\xac\x3d\x7d\x72\x65\xab\x60\ \x6b\x8d\x6f\xa7\x37\x3d\x1e\x8a\x52\xcf\xc8\xbd\xdd\x0d\x31\x56\ \xdf\x66\x68\x4f\x49\x88\x8f\xf3\xeb\xa1\x6e\x6b\xef\xe4\xb9\xd7\ \x3f\x66\xc9\xb2\x35\xa8\x6a\x78\x45\xa3\xf9\x83\xa2\x08\xe6\x5c\ \x35\x81\xef\xde\x77\xb3\xdf\x3e\x0c\xeb\x36\xef\xe4\xcf\xcf\xbd\ \x19\x96\x2e\xcb\xc3\x32\x25\x4e\x0f\x1c\x6a\x3a\xf3\x10\xe4\x15\ \xf4\xc7\x6a\x8d\x32\x34\x23\xb6\x1e\x9b\xee\x02\xb0\x22\x70\x21\ \x7b\x26\x00\x39\x61\x30\x03\x38\x5c\x55\xc7\xa1\xca\x63\x7e\x97\ \x09\xb3\xb9\x60\x7b\xbd\xe0\xd8\xc9\xd8\x87\xe4\x58\x28\x48\xf1\ \x0d\x38\x09\xec\xaa\x17\x1c\x38\x21\x88\xb2\xc0\xd0\x7e\xbe\x8d\ \xcf\x60\xd6\x1f\xf5\x7a\x55\x96\x7c\xb8\x86\x85\x8b\x3f\x8a\xd8\ \x94\x5c\x63\x86\x0f\xe4\xe1\x07\x17\x30\x6c\x90\x7f\xa9\xab\x8e\ \x54\xd5\xf1\xa7\xe7\xde\x64\xf3\xb6\x7d\x41\xb6\x2c\x70\x04\x30\ \x26\x57\x62\x55\xce\x14\xc4\xb1\x58\xac\xe4\xe4\x17\x52\x7d\xf4\ \x90\x61\x76\xb5\xe9\x30\x03\xb0\x28\x48\x2b\x92\x1e\x1f\xe0\xc7\ \xc7\xc6\x90\x93\x99\x4e\xdd\x71\xe3\x8e\x02\x01\xde\xfe\x64\x3d\ \x0f\x3f\x78\xeb\x45\x7f\xe6\x51\xa1\xd3\x09\x2d\x0e\x41\x7d\x07\ \xd4\xb5\x09\xce\xde\x4e\x2b\x4c\xf5\x0d\x70\x55\x42\x45\xad\xa0\ \xaa\xc5\xf7\xc7\x76\x79\x61\x67\xbd\xe0\x68\xb3\x60\x42\x81\x4a\ \x5a\x10\x66\xa6\xe5\x3b\x0f\xf0\xa7\x85\x4b\x39\x54\x79\x4c\xff\ \xc6\x43\x40\x7a\x5a\x32\xdf\xba\xfb\x46\xe6\x4e\x9b\xe0\x97\x0f\ \x83\xcd\xe6\xe0\xd9\xd7\x3f\xe2\x8d\x0f\xd6\xe0\xf1\x86\xbf\xcb\ \xb2\x00\x46\x66\x4b\x12\xa2\x61\x6b\xad\xef\xb9\xe9\x97\x95\x67\ \xac\x00\xb4\x36\xeb\xd1\x8c\x3c\xb5\x04\xe8\x31\xc3\x06\xe6\x19\ \x2e\x00\xcb\x56\x97\xf1\xf5\x3b\xaf\x27\xf1\xe4\x54\x5b\x95\x70\ \xe0\x84\xa0\xb2\x55\xd0\xe9\xa4\xcb\xd3\xce\x7d\x0d\x82\x13\x9d\ \xd0\xe6\x10\xd8\x2f\x32\xb3\xeb\x70\xf9\xaa\x21\x4f\x2a\x92\x64\ \x27\xea\x33\x35\x8f\xf4\x94\x5c\x56\x8b\x85\x5b\xaf\x9b\xca\x03\ \x77\xcc\xf5\x6b\x79\x23\xa5\x64\xd9\xea\xcd\x3c\xf9\xf2\x7b\x61\ \x9b\x75\xa7\x2b\xfa\xa7\x49\xe2\xa2\x60\x63\xb5\x20\x33\xdb\xbf\ \x7c\x8b\xc1\xc2\x61\xef\x79\x10\x9c\x02\xd2\x2a\x24\xae\xf3\x5d\ \x81\x03\x61\xd8\xa0\x42\xc3\x93\x4d\x74\xda\xec\x2c\x7e\x6f\x35\ \x0f\xdc\x71\x2d\x52\xc2\xfa\xa3\x0a\x27\xfc\xdc\x3c\x77\xab\x50\ \xdf\xde\xf5\x17\xa1\x4a\xf8\xa2\x52\x30\x24\x1d\x32\x13\x7d\xbb\ \xc3\x36\x97\x4f\x58\xfa\xc5\x4b\x52\x2f\x32\x06\xaa\x5a\x05\x95\ \xcd\xe0\xf0\x08\x14\x01\x59\x89\x92\xc1\xe9\x1e\xde\xf9\x78\x1d\ \xff\x7a\xf5\x03\xec\x76\xe3\x7d\xcb\x03\xa1\x78\xd4\x10\x1e\x7e\ \x70\xbe\xdf\x89\x47\xf7\x1d\xaa\xe6\x4f\xcf\xbe\xc9\xb6\x3d\xc6\ \xbd\x35\xf5\x20\x2b\x51\x72\xd5\x00\x49\xed\x91\x2c\x43\xed\xb0\ \xdb\x7a\x7e\x2a\xa4\x28\xa8\x56\x29\xd0\xc5\xaf\x71\xd8\x40\xfd\ \x52\x16\xf7\x84\x57\xdf\x5b\xcd\x4d\x73\xae\xa0\x43\xa6\xfa\x3d\ \xf8\xb5\x20\x25\xec\x6f\x14\xec\xbf\x60\x09\x26\xc8\x4a\x94\x8c\ \xc9\x91\x24\xc5\x80\xc7\x0b\xdb\xea\x04\x95\x2d\xe7\x8a\xca\x96\ \xed\x7b\x58\xfd\xd1\x52\x4e\x1c\x8f\xcc\x94\x5c\x59\x19\xa9\x7c\ \xe7\xbe\x9b\x99\x35\xc5\xbf\xc4\xa3\x2d\x6d\x1d\xfc\xf3\x95\xf7\ \x79\x6f\xc5\x86\x88\xdc\xd4\xbc\x18\x29\xb1\x70\xf5\x98\x54\x5e\ \x34\xd0\x06\xa7\xd3\x81\x94\x2a\x42\x04\xee\x50\x65\x15\xd2\x6b\ \x05\xd9\x4c\xe0\x59\xc1\x4f\x33\x6c\x50\x7e\x8f\xdb\xd0\x03\x9b\ \xcd\xc1\x9f\x9f\x7d\x93\x69\x37\x3d\xe0\xf7\x3d\x85\x29\x92\x31\ \xb9\x92\xf5\x47\x94\xd3\x65\xcf\x03\xa1\xa1\x43\xb0\xe2\xa0\x20\ \x21\xca\xe7\x41\x76\xf6\xf3\xde\xda\xdc\xc8\xca\x65\x6f\x72\x60\ \x8f\x81\x1e\x53\x3d\x20\xda\x6a\xe5\x4b\x37\xcd\xe0\xde\x05\xfe\ \x25\x1e\xf5\x7a\x55\xde\xfa\xb8\xf7\x96\x15\x2b\xca\x49\x45\x51\ \x14\xe3\x1c\xb3\xa4\xc4\x61\xb3\x13\x97\x10\x78\x3e\x0e\x8b\x82\ \xd3\x0a\xca\x89\xae\x57\xc7\xfe\xd1\x2f\x2d\x85\x8c\xd4\x64\x1a\ \x5b\x8c\x5f\xdb\xad\xfc\x7c\x2b\x31\xd9\x5f\x30\xba\xd8\xbf\x72\ \x4a\x49\xb1\x10\x6d\x81\xd4\x38\x49\x8b\xa3\x67\x62\x28\x25\xe7\ \x44\x50\x7a\xdc\x6e\x36\xae\xfb\x94\x8d\x6b\x97\x87\x4d\x56\x19\ \xad\x5c\x51\x7c\x19\xdf\x7b\x60\x3e\x85\xb9\x99\x7e\x5d\x5f\xb1\ \xf3\x00\x7f\x7a\xf6\x4d\x0e\x1e\x8d\x4c\x17\x6d\x7f\xb0\x5a\x2c\ \x24\x25\xc6\xd1\xda\x66\x9c\x83\x96\xc3\xd1\xd9\x23\x01\x50\x84\ \xb0\x59\x41\xea\x76\xf8\x5a\x32\x66\x08\x9f\xac\x29\xd7\xab\xb9\ \x1e\xf1\xe9\xfb\x6f\x90\xd6\x2f\x9b\xfc\xc2\xee\xf3\xc8\x1d\x6c\ \x14\x08\xe0\x58\x37\x7b\x00\x5a\xd9\xb7\x6b\x2b\xab\x3f\x7a\xcb\ \xd0\x40\xa9\x9e\x90\x97\x9d\xc1\xf7\xee\x9f\xcf\xd4\x89\xa3\xfc\ \xba\xbe\xa1\xb1\x85\x7f\xbc\xf8\x0e\xcb\xd7\x6f\x09\xbb\x8c\xba\ \xc1\x20\x25\x31\xc1\x50\x01\xb0\xdb\x6c\xa4\xf5\xe0\x04\x5e\x51\ \xe8\xb0\x0a\x38\xa1\xd7\x9f\x6a\xe2\xd8\xe1\x61\x23\x00\x1e\xb7\ \x9b\xa5\x2f\xfd\x93\xdb\xef\xfb\x26\x39\x79\x5d\xc7\xd6\x3b\x3d\ \xbe\xa3\x3e\xbd\xa8\xae\x3c\xc8\x67\x1f\xbf\x43\x6d\xd5\x11\xdd\ \xda\x0c\x25\xb1\x31\xd1\xdc\x3d\x7f\x36\x77\xdd\x3c\x93\x68\x3f\ \x02\x4e\x5c\x1e\x0f\x8b\xdf\x5d\xcd\x0b\x4b\x22\xbb\xce\x80\x56\ \x92\x12\x8c\xcd\x86\xd5\xd3\x1c\x1c\x56\x85\x26\xab\x0a\x8d\x7a\ \x3d\xfa\xa5\x63\x86\xe9\xd4\x92\x3e\x38\x1d\x36\x16\x3f\xf7\x37\ \xae\xbf\xf5\x5e\x86\x8c\x18\x1d\xf4\xfe\x8e\x1e\xdc\xcb\xc6\x75\ \xcb\x39\x7a\x50\xdf\xfc\xed\xa1\x64\xfa\xe4\xb1\x7c\xf7\xbe\x9b\ \xfd\x4e\xb0\xb2\xbe\x7c\x17\x7f\x7d\xee\x2d\xaa\x8e\x1d\x0f\xb2\ \x65\xe1\x47\x5c\x9c\xb1\x09\x71\x55\xb5\x67\x3e\x14\x56\x45\x1e\ \xb7\x0a\x64\xa3\x1e\x9b\x80\x00\x59\xfd\x52\x29\xca\xcb\x0a\xab\ \xa2\x13\x2e\x97\x93\xb7\x5e\x7d\x86\xe2\x89\x53\xb9\x6a\xf6\x3c\ \xa2\x63\xf5\xf5\xe4\x71\x39\xec\xec\xd9\xb9\x85\x2d\x1b\xd7\xd0\ \x50\x17\xb9\xa5\xb4\x8b\xf2\xb3\x79\xf8\x81\x05\x4c\x1c\xe7\x9f\ \x88\x57\xd7\x9d\xe0\x2f\xcf\xbd\xc9\xfa\xb2\x5d\x41\xb6\x2c\x7c\ \x89\x32\x38\x25\x7e\x4f\x13\xc1\x58\x15\x51\x6b\x15\x2a\xc7\xa4\ \x8e\xa1\xd9\xa5\x63\x87\x85\x95\x00\x00\x20\x25\x15\x1b\xd7\xb0\ \x77\xe7\x16\x2e\xbf\xea\x6a\xc6\x4d\xb8\x82\xa8\xe8\xc0\x2b\xca\ \xb8\x5d\x4e\x8e\x1c\xdc\xcb\xde\x1d\x15\x1c\xd8\xb3\xdd\x50\x9f\ \xf0\x9e\x12\x1f\x1b\xc3\xfd\xb7\x5f\xc3\xed\xf3\xa6\xfb\x95\x92\ \xcb\xee\x70\xb1\xe8\xcd\x4f\x78\xf5\xed\x55\xb8\x3c\x7d\xb3\x28\ \xcb\x29\xac\x06\xa7\x30\xeb\xf1\x0c\xc0\xaa\x1e\xb1\x4a\xab\xe5\ \x08\x3a\x1e\x65\xcc\xbc\x62\x1c\x4b\x97\x19\x9b\x30\xe1\x52\xd8\ \x3a\xdb\x59\xb5\xec\x4d\xd6\xad\xf8\x80\x21\x23\xc6\x30\x70\xc8\ \x08\x72\xf2\x8a\x48\xeb\x97\xd5\xa5\x0b\x6b\x7b\x5b\x0b\x0d\x75\ \x35\x1c\xaf\xab\xe6\xe8\xc1\xfd\xd4\x54\x1d\x42\x8d\x00\x17\xd6\ \xae\x10\x42\x9c\x4e\xc9\x95\x99\xee\x5f\xa5\x99\xe5\xeb\xb6\xf0\ \xf7\x17\xdf\x8e\xd8\xc4\xa3\x7a\x63\x74\x52\x13\x6f\x0f\xab\x62\ \x09\xaf\xb2\xcd\xea\xf0\x70\x38\x56\x41\xa2\xd3\x3a\x60\xdc\x65\ \x83\xc9\x4c\x4f\xe1\x78\x53\xab\x1e\xcd\x05\x05\xb7\xcb\xc9\xee\ \x6d\x9b\xd9\xbd\x6d\x33\x00\x51\xd1\x31\xa4\x65\x64\x12\x1d\x15\ \x43\x54\x4c\x0c\x16\x8b\x05\xbb\xad\x13\xbb\xad\x93\xce\x8e\x36\ \x9c\x8e\xc8\x0c\xce\xb9\x14\x5a\x53\x72\x1d\xaa\xac\xe5\x89\x85\ \x4b\x23\x36\xf1\x68\xb0\x30\x3a\x8e\x41\xf6\x70\x09\xd0\xea\xe5\ \x0b\xeb\x94\x42\x61\x2f\xaf\xf1\x1e\x07\x74\xf1\x6d\x54\x14\xc1\ \xec\x2b\x8b\x79\xf5\xdd\x55\x7a\x34\x17\x12\xdc\x2e\xa7\x6e\x89\ \x16\xc3\x99\xb8\xd8\x68\xee\xbc\x69\x26\xf7\x2c\x98\xe3\xd7\x74\ \xbf\xc3\x66\x67\xd1\xd2\xe5\x2c\x7e\x2f\x72\xeb\x0c\x04\x13\x97\ \xdb\xd8\x25\x90\x2a\x03\xff\x9b\x44\x5b\x50\xbf\x33\x49\x34\x5a\ \x01\x84\x90\x87\xa5\x14\xba\x39\x37\x5f\x3d\x35\xb2\x04\xa0\xb7\ \x23\x84\x60\xee\xb4\x09\x7c\xeb\xee\x1b\x49\x4f\x4b\xee\xf6\x7a\ \x55\x95\xbc\xbf\x62\x03\x4f\xbf\xf2\x3e\x2d\x6d\x1d\x21\xb0\x30\ \x32\xf1\xb8\x0d\x9e\x01\xf4\xc0\xb5\x3a\x36\x8a\x4e\x38\x99\x11\ \x48\x4a\x71\x04\x98\xa4\x8b\x55\xc0\x88\xc1\x45\x0c\x28\xcc\xe1\ \x48\x55\x9d\x5e\x4d\x9a\x04\xc8\xb0\x81\x05\xfc\xe0\xc1\xf9\x8c\ \x1d\xe1\x5f\xb6\xa4\x5d\xfb\x8f\xf2\xc4\xc2\xa5\xec\x3e\x50\x19\ \x64\xcb\x22\x9f\x4e\x83\x2b\x07\xf7\x24\x29\x68\x8c\x45\x36\xc1\ \x69\x01\xe0\xa0\x1f\x61\xdc\x9a\xb8\xe3\xfa\x69\xfc\xee\xe9\xc5\ \xfa\x36\x6a\xe2\x37\x49\x09\xf1\x7c\xfd\xae\xeb\xb9\x65\xce\x15\ \x28\x7e\x96\x70\x7f\xe5\xed\x15\x3c\xf9\xd2\x7b\x7d\xc2\x8b\x4f\ \x0f\x8c\xce\xd3\x68\xb1\x6a\x13\x00\x01\xa4\xc4\x41\x6e\x92\x24\ \x23\x4e\xbe\x07\x27\x05\x40\x08\xb9\x43\x2f\x5f\x80\x53\x5c\x3b\ \x73\x22\xcf\xbc\xf6\x61\xc4\xe6\xae\x8f\x58\x84\x60\xe4\xd8\x52\ \xe6\xdf\x72\x33\x63\xfb\x27\x22\x84\xff\x83\xf9\x48\x75\xbd\x39\ \xf8\x35\xd0\x62\xb0\x00\xf8\x3b\x03\x88\xb1\xfa\x52\xdd\x0d\x48\ \x97\x24\x9c\xbc\x45\x48\xb9\x1a\x4e\x0a\x80\xc5\x62\xd9\xae\x77\ \x75\x99\x68\xab\x95\x1b\x67\x5f\xc1\x0b\x4b\x3e\xd6\xb5\x5d\x93\ \x4b\x93\x93\x5f\xc4\xec\x79\xb7\x91\x9b\xdf\x1f\x27\xb0\xa9\x0a\ \x92\x62\x04\xc5\xf9\x2a\xe9\xc6\xe7\xd8\xec\x55\x38\x9c\x2e\xc3\ \x73\x39\x58\x2d\x5d\x67\xb0\x4d\x8c\xf6\xa5\xb3\x3b\x95\xed\xea\ \x6c\xbc\x8a\x65\x3b\x9c\x14\x80\xa6\x6c\xf6\xa4\xd4\xe2\x04\x02\ \xf7\x8e\xb9\x08\xb7\x5e\x7b\x25\xaf\xbe\xbb\x12\xa7\x2b\x72\x1d\ \x65\x22\x81\xb8\x84\x04\xae\x9a\x7d\x23\x63\x4a\x26\x5f\xe0\xcf\ \xd0\xee\xf4\x65\x32\x1a\x92\x21\x19\x99\x25\xd1\x7b\xa9\xd7\x57\ \xa9\x3f\xae\x4b\x4a\xae\x1e\x31\xae\xd0\x4a\x56\xa6\xa4\xae\x43\ \xd0\x76\xb2\x52\x97\x10\x90\x11\x0f\x03\xd3\x25\x79\x49\x97\xfc\ \x7b\x3b\x0e\xe5\xb2\x1f\x4e\x0a\xc0\x4c\x21\x3c\xe5\x35\xde\xdd\ \x80\x7f\x59\x1e\xfc\x24\x3d\x2d\x99\x3b\x6e\x98\xce\xa2\xa5\x9f\ \xea\xd9\xac\xc9\x49\x84\x50\x18\x37\x71\x0a\x53\x67\xcd\xeb\xb2\ \x4c\xbb\x94\xb0\xff\x84\xa0\xc9\x2e\x98\x58\xa0\x12\x1b\x19\xf5\ \x53\xc2\x9a\xba\x30\x10\x80\x82\xb4\x68\x06\x67\x49\x2e\xcb\xf2\ \x55\xea\xb2\xbb\x20\x3e\x0a\xfc\x38\xe1\xdd\x79\x87\x10\x5e\x80\ \x33\xbb\x43\x82\x6d\xc1\x30\xf2\x9e\x5b\x66\xfb\x9d\x1e\xda\xc4\ \x7f\xf2\x0b\x07\x72\xcf\x37\x1e\xe1\xea\x79\xb7\x77\x39\xf8\xcf\ \xa6\xb1\x13\x56\x1e\x54\x38\xd1\x69\x4e\x03\x7a\x4a\x75\xdd\x09\ \xa3\x4d\x20\x2d\xe5\x4c\x95\xa5\x28\xc5\x97\xd9\xda\x1f\xef\x64\ \xc1\x99\xb1\x7e\x5a\x00\x84\x24\x28\xa9\x6a\xe2\xe3\x63\x79\xe0\ \x8e\xb9\xc1\x68\xba\x4f\x12\x9f\x98\xc4\x75\xf3\xbf\xc2\x9d\x0f\ \x7e\x9f\xac\x5c\x6d\x69\xd8\x5a\x9a\x1b\xa9\x28\xdb\xc4\xba\xa3\ \x67\xd2\xa1\x9b\x04\x86\xd1\x19\x9c\x15\x45\x21\x35\x39\xb0\x64\ \x20\x52\x72\x3a\x79\xe7\xe9\xc9\xa0\x2a\x94\x0d\x42\x06\x27\xbd\ \xd1\x4d\x73\xae\xe0\xed\x4f\x3e\xef\xd5\x19\x62\x82\x8d\xa2\x28\ \x14\x5f\x7e\x15\x57\xce\xbc\x4e\x73\x44\xa3\xc7\xed\x66\xc3\xda\ \x4f\xd9\xb4\x76\x39\xc3\xc7\x94\x30\x72\xdc\x44\xb6\x1e\x13\xe4\ \x5c\x7a\x8d\x68\xd2\x0d\x87\x0d\xf6\x71\x49\x4d\x4e\xf0\xfb\x78\ \xf7\x7c\x84\x50\xbe\x38\xf5\xff\xa7\x05\x20\xb9\x93\x8d\xed\xf1\ \xfa\x6f\x04\x82\x2f\x7d\xd2\x4f\xbf\x7b\x17\x0f\xfd\xe8\x09\xd3\ \xa5\x34\x00\x0a\x07\x0c\x61\xf6\xf5\xb7\xd2\x2f\x80\x54\xd4\xfb\ \xf7\x6c\x67\xd5\x87\x4b\x2f\xc8\x4a\xe4\xf0\xf8\xf2\x16\x26\x18\ \x1b\xd2\x1e\x91\xa8\xaa\xca\xfe\xa3\xc6\x86\x7e\x9f\x3d\xfd\xd7\ \x88\xb3\xc5\xc5\xe9\xac\x3d\xa7\x05\x60\xe8\x50\xe1\x2c\xaf\xf1\ \x54\x80\xf0\x2f\x91\x9e\x46\x86\x0e\xc8\xe7\xfe\xdb\xaf\xe1\x5f\ \xff\xfe\x30\x18\xcd\xf7\x4a\x12\x12\x93\x99\x3e\xe7\x26\x46\x8e\ \x2b\x45\xeb\xab\xba\xb9\xe9\x04\x2b\x3f\x58\xc2\xa1\xfd\x97\x8e\ \xd7\x6f\x77\x0a\x12\xa2\xcd\x73\x7f\xad\xec\x3f\x5c\x63\xf8\x11\ \x60\x46\x80\x55\x96\x05\x72\xd3\xcc\x81\xe2\x74\xea\xdb\x73\xf7\ \x83\x85\x58\x87\x24\x28\x02\x00\x70\xf7\x2d\x57\xb3\x7e\xf3\x6e\ \x76\xee\x3f\x12\xac\x2e\x7a\x05\x8a\xc5\x42\xc9\xe4\xe9\x5c\x39\ \xe3\x5a\xcd\x79\x0b\xdc\x2e\x17\x5f\x7c\xf6\x31\x9b\xd7\xaf\xec\ \x36\x5c\xb4\xd9\x0e\x39\x91\x53\xad\x3b\x6c\xd8\xbe\xf7\x88\xd1\ \x26\x90\x93\x19\x58\x3d\x4e\x89\x58\x77\xf6\xbf\xad\xe7\xfe\x50\ \xae\x13\x88\x47\x7a\x60\x57\x97\x58\x2c\x0a\xbf\x7e\xf4\xab\x7c\ \xfd\xd1\x27\xcc\x98\xf2\x4b\x50\x38\x70\x08\x57\xcf\xbb\x9d\x8c\ \xcc\x1c\xcd\xf7\x1e\xdc\xbb\x83\x15\x1f\x2c\xf1\x3b\x09\xe9\xe1\ \x26\xc1\x80\x93\xd5\x6e\x4c\xfc\xa7\x6c\xbb\xf1\xb5\x0c\x8b\xf2\ \x03\x2b\x0d\x2e\x91\x97\x16\x00\xab\x62\x59\xa7\xb7\x47\xe0\xf9\ \x64\xa4\x26\xf3\xdb\x1f\x3d\xc8\xb7\x7f\xf6\xd7\x3e\x95\x40\xb2\ \x3b\x12\x93\x53\x98\x36\xfb\x46\x46\x8e\x9f\xa8\xf9\xde\xe6\xc6\ \xe3\xac\xf8\x70\x09\x87\xf7\xef\xd6\x74\x9f\xcb\x0b\x1b\xaa\x14\ \xae\x1a\xa0\x62\x70\x6e\x8b\x88\xc1\xed\xf1\xb2\x79\xc7\x7e\xa3\ \xcd\xa0\x28\x3f\xa0\xe0\x5d\x29\x14\xcb\x17\x67\x7f\x70\x8e\x00\ \x8c\xcb\x11\x0d\xe5\x35\xde\x9d\x80\x7f\x79\xa0\x03\x64\xd8\xc0\ \x02\xfe\xe7\x3b\x5f\xe1\xe7\x7f\x7c\xc1\xb8\xc2\x0a\x61\x82\xc5\ \x62\xa5\xf4\xca\x99\x4c\xbe\xea\x1a\xa2\xa2\xb5\xed\xc8\xb9\x5d\ \x4e\xd6\xaf\xfa\x88\xb2\x2f\x56\x05\x9c\xa1\xa8\xc5\x0e\xdb\x8e\ \x09\x8a\xf3\xcd\xbd\x00\x7f\xd8\xba\xeb\xa0\xe1\xeb\x7f\x80\xa2\ \xbc\x80\x04\x60\x5b\x49\xae\x38\x27\x7b\xeb\x05\x3e\x61\x12\x3e\ \x14\x41\x16\x00\x80\x19\x93\xc7\xf2\xd8\x0f\xee\xe1\x97\x7f\x7e\ \xc9\xf0\xcc\x2a\x46\x51\x34\x68\x18\x57\x5f\x7f\x1b\xe9\x99\xda\ \xa7\x73\x07\xf7\xee\x60\xf9\xfb\x6f\xe8\x52\x25\xb6\xb2\x45\x30\ \x2c\xd3\x14\x00\x7f\x58\xbe\xbe\xc2\x68\x13\x88\xb2\x5a\xc8\xf5\ \x33\x6b\xf3\x79\x5c\xb0\x03\x7f\x81\x00\x08\xa9\x2e\x43\x28\xff\ \x19\x48\xeb\x5a\x99\x35\x65\x3c\x42\x11\xfc\xe2\x89\x45\x7d\x4a\ \x04\x52\xd2\x32\x98\x79\xed\x7c\x86\x8c\x18\xa3\xf9\xde\x13\x0d\ \xc7\x58\xfe\xfe\x1b\x54\x1d\x39\xa0\x9b\x3d\x12\x68\x73\x9a\x0e\ \x01\xdd\xe1\xf6\x78\x59\xfd\x45\x50\x1c\x66\x35\x51\x98\x97\x1d\ \x58\x3e\x42\x45\x59\x76\xfe\x47\x17\x08\x40\x4c\xab\x75\x8d\x23\ \x55\x6d\x17\x10\x92\xfd\xe1\x99\x93\xc7\x11\xff\xa3\x18\xfe\xdf\ \x13\x2f\xd2\x61\xeb\x5d\xb9\xf7\xce\xc7\x1a\x15\xc5\xe5\x53\x67\ \x73\xf9\x95\x57\x6b\x4e\xe6\xe0\x72\xd8\x59\xb7\x6a\x19\x15\x1b\ \x3e\x0b\xca\xb2\x29\xc6\x62\xce\x00\xba\xe3\xf3\xb2\x5d\xb4\x75\ \x18\x5f\xe7\x70\xd4\xb0\xae\x0b\xdd\x5c\x82\x36\xb5\x86\xf5\xe7\ \x7f\x78\x81\x00\x8c\x1a\x25\x5c\xe5\x35\xea\x4a\x90\x37\x05\xd2\ \x4b\x20\x4c\x2a\x1e\xc1\x33\xbf\xfb\x21\x3f\x7e\x7c\xa1\xe1\x1e\ \x56\xc1\x62\xe8\x88\x31\xcc\xb8\x76\x3e\x29\x5a\x6b\x39\x49\xc9\ \xae\xad\x9b\x59\xfd\xe9\x3b\x74\xb6\x07\xaf\xee\x62\x4f\x6b\x22\ \xf6\x05\xde\xfa\x78\x5d\xf7\x17\x85\x80\x51\x43\x07\x68\xbf\x49\ \x8a\x4f\x4b\x4b\xc5\x05\x61\xb9\x17\x8f\x0b\x93\x72\x19\x82\x90\ \x09\x00\x40\x41\x4e\x3f\x9e\xfe\xcd\x0f\x78\xe2\x99\x25\x7c\xb8\ \x6a\x53\x28\xbb\x0e\x2a\x85\x03\x86\x30\xf5\xea\x1b\xfc\xaa\x51\ \x78\x3e\x0d\x75\x35\x2c\x7f\xff\x0d\x6a\x2a\x0f\x05\xc1\xb2\x73\ \xd9\x7e\x4c\xd0\x68\x33\x45\xe0\x52\x54\xd6\x36\xb0\x69\x9b\xf1\ \xc7\x7f\x00\xa3\x86\xf5\xd7\x7c\x8f\x50\xd4\x0b\xa6\xff\x70\x09\ \x01\x50\xa4\xf2\x8e\x2a\xd4\xbf\x71\x76\xb4\x60\x08\x88\x8f\x8d\ \xe1\x7f\xbe\x73\x17\x73\xa6\x4e\xe0\xf1\xa7\x16\x53\x7f\x22\x32\ \x8b\x6a\x22\x04\x03\x06\x0d\x67\xe2\x95\xb3\xe8\x3f\x78\xb8\xe6\ \xdb\x9d\x0e\x1b\x6b\x57\x7c\xc0\x96\x8d\xeb\x90\x41\x8a\xcf\x38\ \x1f\xc9\xb9\x55\x8d\x4d\xce\xe5\xf9\x37\x3e\x0e\x8b\x6c\x49\x09\ \xf1\x71\xf4\xd7\xee\x03\xa0\x7a\x5d\x96\xf7\x2e\xf6\x83\x8b\x0a\ \xc0\xf8\x42\x51\x53\x51\xe3\x59\x2f\x11\x53\xb5\xf6\xa4\x07\x97\ \x8f\x1f\xce\xcf\x7f\xfa\x28\x8b\x96\xae\xa0\xfc\x8b\x55\xb8\x7a\ \x58\x04\x31\x54\x44\xc7\xc6\x31\x7a\xdc\xe5\x8c\xbf\x7c\x2a\xe9\ \xfd\xb4\x1f\xd3\x48\x29\xd9\xb9\x65\x03\xab\x3f\x7e\x17\xbb\xcd\ \xcc\xc6\x1b\x2e\x1c\xa9\xaa\xe3\xd3\xb5\xc6\xef\xfe\x03\x8c\x1e\ \xde\x1f\xe5\xfc\xf4\x3e\xdd\x20\x85\x58\x5d\x3a\x40\x5c\x34\x7c\ \xf1\x92\xa9\x21\x24\x62\x31\x60\x88\x00\x78\x55\xa8\xe9\x88\x65\ \xea\xec\xeb\x99\x70\xc5\x34\x36\x7c\xf6\x09\x5b\x37\xaf\xc7\xed\ \x0e\xbf\x57\x94\xd5\x1a\xc5\x80\x21\x23\x18\x31\xaa\x98\x41\x23\ \x46\x13\x1d\x60\xc9\xb1\xfa\xda\x2a\x3e\x7d\xff\x75\x8e\x55\x1f\ \xd5\xd9\x42\x93\x9e\xb2\xf0\xb5\x65\x61\xe3\xaf\x32\x69\xfc\x65\ \x9a\xef\x11\xaa\xbc\x64\x76\xde\x4b\x0a\x80\x27\x4a\x79\xdd\xea\ \x56\x9f\x00\x42\x5e\x00\xed\x78\xa7\xc0\x75\xf2\x54\x30\x2e\x3e\ \x91\x19\xd7\xce\xe7\x8a\x19\xd7\xb2\x73\xcb\x26\xb6\x96\xad\xa3\ \xb1\xc1\xd8\x8d\xc2\xa8\xe8\x18\xfa\x0f\x1a\xc6\xf0\x51\xe3\x19\ \x3c\x7c\x34\xd1\x31\xb1\x01\xb7\xe5\xb0\x75\xb2\x66\xf9\xfb\x6c\ \x2b\x5b\x1f\x16\x53\x4c\x93\x73\xd9\x77\xb8\x9a\x55\x1b\x8c\x3f\ \xfa\x3b\xc5\x94\x09\x23\xb5\xde\xe2\xb5\x58\x95\xa5\x97\xfa\xe1\ \x25\x05\xe0\xf2\x2c\x51\x57\x5e\xeb\x59\x83\x14\x33\xb4\xf6\xd8\ \x53\x5a\x1d\x17\x7e\x16\x13\x1b\x47\xc9\xe4\x69\x94\x4c\x9e\x46\ \x5d\x6d\x25\x07\x76\xef\xe0\xc0\xde\xed\x9c\xa8\x0f\x7e\x8e\x81\ \xa8\xe8\x68\xf2\x0b\x07\x51\x34\x70\x28\x85\x03\x87\x92\x9d\x57\ \x18\x70\x2c\xf6\x29\xa4\x94\x6c\x2b\x5b\xcf\x9a\xe5\xef\xe3\xb0\ \x19\x9b\x5d\xd6\xe4\xe2\xa8\xaa\xe4\x2f\xcf\xbd\x15\x36\xc2\x5c\ \x94\x97\x45\x41\x4e\x3f\x8d\x77\x89\x15\xe3\x72\xc4\x25\xab\xf5\ \x76\x9d\x1d\x4e\x15\x8b\x11\xcc\xd0\xd8\x63\x8f\x89\xea\x66\xce\ \x91\x93\x57\x44\x4e\x5e\x11\x53\x67\x5f\x4f\x7b\x5b\x0b\xb5\x55\ \x47\x38\x56\x75\x98\xba\x9a\x2a\x1a\x4f\xd4\x07\xbc\x7e\x16\x42\ \x21\x35\x2d\x9d\x8c\xac\x5c\xd2\x33\xb3\xc9\xcc\xca\x25\x3d\x33\ \x87\x7e\x59\xb9\x58\x74\x2c\x05\x7d\xac\xfa\x28\x9f\xbe\xff\x3a\ \xf5\xb5\x55\xba\xb5\x69\xa2\x3f\x6f\x7c\xb8\x86\x2d\xbb\xc2\xa7\ \x1e\x62\x00\x6f\x7f\x40\x7d\xad\xab\x9f\x76\x29\x00\x6e\x94\xc5\ \x51\xa8\x7f\x04\x02\x9f\xe3\x06\x40\x6e\x92\x64\x57\xbd\xc0\xe3\ \xc7\xb2\x2b\x29\x39\x95\xe1\xa3\xc6\x33\x7c\xd4\x99\x7c\xa6\x4e\ \x87\x9d\x96\xa6\x13\x74\x76\xb6\xe3\xe8\xec\xc4\x66\xef\x44\x3d\ \x19\x1a\xeb\xb0\xdb\x89\x8e\x8e\xc1\x6a\xb5\x12\x1d\x1b\x4b\x4c\ \x4c\x1c\x09\xc9\xc9\x24\x24\x24\x91\x94\x92\x86\x55\x63\xb1\x05\ \x2d\xd8\x6d\x1d\xac\xfe\xf8\x5d\x76\x6e\xd9\x10\x36\x6f\x15\x93\ \x8b\x53\x75\xec\x38\xff\x7c\xe5\x7d\xa3\xcd\x38\x87\xa9\x13\x47\ \x6b\xbd\xc5\x6e\xf5\x58\x2e\x39\xfd\x87\x6e\x04\x60\x52\x81\x68\ \x2c\xaf\xf1\xbe\x05\x7c\x59\x6b\xcf\x3d\x21\x2e\x0a\x2e\x2f\x94\ \x7c\x71\x54\x10\xc8\xd6\x4b\x4c\x6c\x1c\xd9\x79\x85\xba\xdb\xd5\ \x13\x6c\x1d\xed\x2c\xfc\xeb\xaf\x71\x3a\x8c\xf7\x24\x33\xe9\x1a\ \x55\x95\xfc\xe6\x1f\xaf\xe2\x70\x86\xcf\xa6\x73\x5e\x76\x06\xe3\ \x2e\xf3\xaf\xbc\xdb\x69\x04\xaf\x8f\xed\x2f\xba\x0c\x16\xe9\x76\ \x21\x2b\xa5\xfa\x2f\x6d\xbd\xea\x43\x56\xa2\x64\x68\x2f\x0a\x50\ \x71\xb9\x5d\xe6\xe0\x8f\x10\x9e\x79\xed\x03\xb6\xed\x0e\xbe\xf3\ \x95\x16\xe6\x4e\x2b\xbd\xa0\xe6\x43\x77\x48\x94\x67\xba\xbb\xa6\ \x5b\x01\x28\xc9\xb7\xae\x04\x0c\x09\x80\x8e\x35\x13\x55\x98\x84\ \x98\x65\xab\x37\xf3\xe2\x92\xf0\xab\x63\x31\xe7\xaa\x12\xad\xb7\ \xec\x2b\xc9\x65\x6d\x77\x17\x75\x2b\x00\x42\x08\x89\xe0\x59\xad\ \xbd\xf7\x94\x36\x27\xec\x69\x30\x5d\x53\x4d\x42\xc7\xd6\xdd\x07\ \xf9\xdd\x93\x5d\xee\x99\x19\xc2\xd8\xcb\x06\x69\x8e\xff\x17\xf0\ \x2f\xe1\x47\x61\x48\xbf\xce\xb2\xa2\x2c\xca\x73\x08\x42\x56\xdf\ \xab\xc5\x0e\x6b\x0f\x2b\x38\xbb\x4e\x69\x67\x62\xa2\x1b\x47\xab\ \xeb\xf9\xc9\xef\x9f\xc3\xe5\x09\xbf\x87\xee\xd6\x6b\xaf\xd2\x7a\ \x8b\x4b\xb1\x28\x2f\xfa\x73\xa1\x5f\x02\x30\x26\x5b\xd4\x0b\x95\ \x90\x48\x63\xbb\x13\xd6\x1d\x51\x4e\x3b\x02\x99\xf4\x3d\xf6\x1c\ \x17\x84\xf2\x90\x64\xdf\xe1\x6a\xbe\xfd\xf3\xbf\xd1\xda\x16\x7e\ \xfe\x18\x79\xd9\x19\xcc\x98\x3c\x56\xd3\x3d\x12\x5e\xe9\xea\xec\ \xff\x6c\xfc\xf6\x66\x51\xa5\xf2\x7b\x5f\xdb\xc1\xa5\xaa\x55\xe0\ \x0e\x0f\xaf\x4b\x13\x83\xa8\x6c\x11\x6c\xa8\x12\x21\x79\x09\xec\ \x3e\x50\xc9\x0f\x7e\xf9\x14\x2d\x6d\xe1\x19\x7b\x71\xc7\xbc\xe9\ \x9a\x93\x7f\x08\xa9\xfc\xc9\xdf\x6b\xfd\x6e\x79\x42\xa1\xd8\x06\ \x62\xb9\x26\x4b\x02\xa0\x3b\x27\x20\x93\xbe\x41\x5d\xbb\x60\xe5\ \x01\x85\x23\xcd\x81\x1d\x05\xfb\xc3\x86\x8a\x3d\x7c\xff\xb1\x7f\ \xd0\xd6\x1e\x7e\x6f\x7e\x80\xe4\xc4\x78\xae\x9f\x75\xb9\xc6\xbb\ \xc4\xb2\x92\x02\xb1\xd5\xdf\xab\x35\xd5\x89\x95\xc2\xfb\x07\x21\ \x95\xab\x35\x5a\xa4\x89\xec\x04\xc9\x4e\xcc\xcd\x3f\x13\xb0\x7b\ \x60\x4b\xad\x60\x47\x9d\x20\x21\x06\xa2\x15\x49\x94\x05\xfa\xa7\ \x41\x76\x62\xe0\x93\x51\x29\x25\xaf\xbc\xbd\x82\xa7\x5f\x79\x1f\ \x55\x0d\xdf\xa3\xe6\x2f\xdf\x38\x93\xf8\x58\x8d\xc1\x65\xd2\xfb\ \x7f\x5a\x2e\xd7\x24\x00\x13\xf2\xa2\x96\x95\xd7\x78\xb7\x02\xe3\ \x34\x19\xa5\x81\xe4\x58\x48\x8a\xf1\xed\x05\x98\x98\x00\x78\x54\ \x68\xb5\x03\x27\x5f\x0c\x75\xed\x30\x63\xb0\x24\x39\x80\xc0\xcb\ \xd6\xb6\x4e\x7e\xf1\x97\x45\x6c\xdc\xb2\x57\x57\x1b\xf5\xa6\x5f\ \x5a\x0a\xb7\xcf\x9b\xa6\xf5\xb6\x6d\xc5\xf9\xd6\x15\x5a\x6e\xd0\ \x1c\xd1\x22\xa4\x7c\x42\xeb\x3d\x5a\xc9\x4f\x09\x5f\x55\x36\x31\ \x1e\x55\xc2\xfe\xe3\xda\x67\x89\x9f\xae\xab\xe0\x9e\x47\x7e\x17\ \xf6\x83\x1f\xe0\x81\x2f\x5d\x4b\x5c\xac\xb6\x34\xf1\x42\xc8\xc7\ \xfd\x39\xfa\x3b\x1b\xcd\x02\xb0\x3f\xdf\xf2\x92\x84\xa0\x7e\x83\ \xfd\xd3\xa4\x5f\x75\xce\x4d\xfa\x2e\xd5\x6d\xc2\xef\x63\xe2\xda\ \xfa\x46\x1e\xf9\xdf\xa7\x79\xec\x89\x17\x69\x6a\x0e\x5e\x5e\x45\ \xbd\x28\xca\xcb\x62\xde\x4c\xad\x6b\x7f\xf6\xed\xcf\xb5\x68\x3e\ \xa9\xd3\x2c\x00\x77\x08\xe1\x55\x84\xfc\x95\xd6\xfb\xb4\x10\x67\ \x85\xab\x06\xa8\x24\x87\x34\x04\xc9\x24\x92\x90\x12\xea\x3b\xba\ \x9e\x05\x34\xb7\xb6\xf3\xf7\x17\xdf\xe1\x9e\x87\x1f\x67\x43\xc5\ \x9e\x10\x59\xd6\x33\x84\x10\xfc\xe7\x43\xb7\x05\xb0\xf3\x2f\x7f\ \x7a\x87\x10\x9a\xcf\x4d\x34\xed\x01\x9c\x62\x7c\xae\xe5\xdf\x15\ \xb5\xea\x7f\x03\xda\x0e\x28\x35\x90\x12\x0b\x33\x06\xab\xec\xae\ \x17\xec\x3f\x61\x6e\x0a\x9a\x5c\x48\x43\x07\x14\xa5\x5e\xf8\x79\ \x73\x6b\x3b\xaf\xbe\xbb\x9a\x25\x1f\xae\x09\xab\x80\x1e\x7f\x98\ \x37\xeb\x72\x4a\x46\x0f\xd5\x74\x8f\x94\xec\x28\xce\xb7\x2c\x09\ \xa4\xbf\x80\x04\x40\x08\xa1\x96\x57\x7b\x7e\x85\x10\xaf\x07\x72\ \xbf\xbf\x28\xc0\xa8\x6c\x89\xc3\xed\xf3\x0f\x30\x31\x39\x9b\x56\ \x87\xe0\x94\x6b\x8a\xaa\x4a\xca\x77\xec\xe7\xc3\xd5\x9b\x58\xf5\ \xf9\x56\x9c\xae\x90\x39\xae\xea\x46\x7a\x5a\x32\xdf\xbe\x57\x7b\ \x32\x6e\x45\xc8\x9f\x0a\x21\x02\x3a\x2d\x0d\x48\x00\x00\x8a\xf3\ \x2d\x4b\x2a\x6a\xd5\x2d\xc0\xf8\x6e\x2f\xee\x21\x63\xf3\x24\x8d\ \x36\x81\x2d\xf2\xfe\xa6\x26\x41\xa4\xcd\xe6\x66\xe3\xd6\x03\x6c\ \xa8\xd8\xcd\xca\xcf\xb7\x46\x7c\xc5\xe9\x47\x1e\xbc\x95\xa4\x84\ \x78\xad\xb7\x95\x8d\xcf\xb3\xbc\x13\x68\x9f\x01\x0b\x80\x10\x42\ \x96\x57\xbb\xff\x13\xa1\x04\x3d\x74\x2a\x4a\x81\xf1\x79\x92\xf5\ \x47\xcd\x59\x40\x5f\x45\x4a\x95\xb6\x96\x66\xea\x8f\x55\xd3\x70\ \xac\x86\xfa\x63\x95\x54\x1f\x39\x18\x96\x89\x62\x03\xe1\xe6\x39\ \x53\x98\xae\xd1\xe5\x17\x40\xa0\xfe\x58\x88\xc0\xcb\x3a\x05\x2c\ \x00\x00\x25\x05\x51\xcb\xcb\x6b\xd4\x77\x41\xde\xd8\x93\x76\xfc\ \x21\x2b\x51\x92\x9f\x02\x35\xe6\x52\xa0\xd7\xf3\xe1\xd2\x97\x4e\ \x97\x4e\xb3\x77\x76\xd0\xd9\xd1\x8e\xad\xb3\xbd\xd7\x66\x51\x1a\ \xd2\x3f\x9f\xef\x3f\x70\x8b\xf6\x1b\xa5\x58\x5a\x5c\x10\xf5\x49\ \x4f\xfa\xee\x91\x00\x00\xa8\x5e\xf1\xb0\x62\x91\xd7\x00\x81\xe5\ \xc3\xd6\xc0\x98\x1c\x49\x7d\x87\xc0\x63\x06\x0a\xf5\x6a\x42\x51\ \x09\x29\x5c\x88\x8f\x8d\xe1\x97\x3f\xbc\x87\x68\x8d\xb5\x22\x01\ \x97\x90\xe2\x47\x3d\xed\xbf\xc7\x95\x7f\x4a\x8b\xc4\x41\x09\x7f\ \xed\x69\x3b\xfe\x10\x6b\x85\x11\xbd\x28\x4b\x90\x49\xdf\x46\x51\ \x14\x7e\xf6\xfd\xbb\x29\xd2\x5e\xe9\x07\x24\x7f\x28\x2e\x14\x3d\ \x4e\xd4\xa3\x4b\xe9\x2f\x7b\xb4\xf2\x4b\x20\x24\xc9\xfa\x07\x65\ \x48\x92\x82\x3e\xd7\x30\x31\x09\x3e\xdf\xbd\xff\x66\xae\xd2\x9e\ \xe8\x13\xa0\xde\x15\xa7\xfc\x56\x0f\x1b\x74\x11\x80\xa9\x99\xa2\ \x5d\x22\x7f\xa2\x47\x5b\xdd\xa1\x00\xa3\xb3\xcd\x59\x80\x49\x64\ \x73\xf3\x9c\x29\xdc\x7e\xbd\x66\x5f\x7f\x1f\x42\xfe\xd7\xe4\x0c\ \xa1\x8b\x4b\xa3\x6e\xc5\x3f\x4b\xf2\x2c\xcf\x83\x08\x49\x32\xb5\ \xec\x24\x49\xbf\x78\x53\x04\x4c\x22\x93\xab\xaf\x2c\xe6\x91\xaf\ \xdf\x1a\xd0\xbd\x52\x88\x95\xc5\xb9\x96\x97\xf4\xb2\x45\x37\x01\ \x10\x42\x48\x8b\x45\x3c\x04\x84\x24\xb8\x7a\x54\x8e\x34\x83\x86\ \x4d\x22\x8e\x19\x93\xc7\xf2\xb3\xef\xdd\x1d\x68\x65\x29\x9b\xf4\ \x88\xaf\x6b\x0d\xf8\xe9\x0a\x5d\xcb\x7f\x8f\xcb\x11\x87\x81\xa0\ \xc6\x09\x9c\x22\x2d\x0e\x72\x92\xcd\x59\x80\x49\xe4\x30\x7d\xf2\ \x58\x1e\x7b\xf8\x5e\xcd\x7e\xfe\xa7\x91\xfc\xbc\xb4\x48\xe8\x5a\ \xaa\x48\x57\x01\x00\x68\xcd\x53\xfe\x00\x94\xe9\xdd\xee\xc5\x18\ \x99\x25\xd1\x98\x2a\xdd\xc4\xc4\x10\xe6\x4e\x2f\xe5\x17\x0f\xdf\ \x8b\x35\xf0\x12\x73\x5b\x5a\xf3\x95\x3f\xeb\x69\x13\x04\x41\x00\ \x66\x0a\xe1\x51\x85\xf2\x50\x28\xb2\x08\x27\xc5\xf8\x42\x87\x4d\ \x4c\xc2\x99\xdb\xaf\x9f\xc6\x4f\xbf\x73\x57\x4f\x06\xbf\x0b\xa9\ \xdc\x3f\x53\x08\xdd\x53\x16\xeb\x2e\x00\x00\xa5\x79\xa2\x5c\xc0\ \x63\xc1\x68\xfb\x7c\x46\x66\x49\xa2\xcd\xdc\x01\x26\x61\x88\xc5\ \xa2\xf0\x5f\x0f\xdd\xce\xf7\x1f\x98\xaf\xb9\xaa\xcf\xd9\x48\xf8\ \x99\x96\x3c\x7f\x5a\x08\x8a\x00\x00\x8c\xcf\x55\x7e\x2b\x85\x58\ \x19\xac\xf6\x4f\x11\x6d\xf1\x89\x80\x89\x49\x38\x91\x9a\x9c\xc8\ \x1f\xfe\xe7\x1b\xdc\x7c\xcd\x94\x1e\xb6\x24\x3f\x3b\xe8\x5b\x56\ \x07\x85\xa0\x09\x80\x10\x42\xb5\xaa\xe2\x5e\xa0\x29\x58\x7d\x9c\ \xa2\x7f\xba\x24\xd7\xdc\x10\x34\x09\x13\x2e\x1b\x52\xc4\x33\x8f\ \x3f\x4c\xe9\xd8\x61\x3d\x6d\xaa\x45\x15\x96\x7b\x02\x49\xf4\xe1\ \x2f\x41\x13\x00\x80\x71\x05\xa2\x1a\x29\xbf\x11\xcc\x3e\xc0\x97\ \x2a\xb2\x38\x4f\x92\xa0\x2d\x85\x9a\x89\x89\xae\x28\x8a\xe0\xae\ \x9b\x67\xf1\xe4\xff\x7e\x8f\x9c\xcc\xf4\x1e\xb7\x27\x91\xff\x51\ \x9a\x27\x2a\x75\x30\xed\x92\x04\x55\x00\x00\x4a\x0a\xac\x6f\x20\ \x78\x21\xd8\xfd\x44\x5b\xa0\xb4\x40\x0d\xfe\x2f\x14\xe1\xc4\xc6\ \xc6\x19\x6d\x42\xaf\x24\x2f\x3b\x83\xbf\xfd\xe2\x3b\x7c\xeb\x9e\ \x1b\x7b\xb2\xd9\x77\x1a\x09\xff\x9a\x90\x6f\x0d\x7a\x35\xae\x1e\ \x47\x03\xfa\x43\xab\x53\xf9\x8f\x94\x68\x75\x0c\xa0\xb9\xc4\xa9\ \x16\xd2\xe2\x60\x68\xa6\x64\x6f\x00\x19\x63\x7b\x3b\x71\x09\x09\ \x4c\x9b\x7d\x23\xa3\x4b\x26\x1b\x6d\x4a\xaf\x42\x51\x04\x37\xcf\ \x99\xc2\x37\xef\xb9\x51\x7b\x0e\xff\x4b\xb3\xc5\xa9\x2a\xdf\xd7\ \xab\xb1\xae\x08\x89\x00\xcc\x1c\x28\x1c\xe5\xb5\x72\x01\x52\x2d\ \x03\x32\x82\xd9\xd7\xd0\x0c\xc9\xc1\x46\x81\xc7\x2c\x2f\x06\xf8\ \x92\x4c\x5e\x36\xb6\x94\x99\x73\xe7\x13\x97\x90\x60\xb4\x39\xbd\ \x8a\xa1\x03\xf2\x79\xe4\xa1\xdb\x18\x3d\x6c\x80\x9e\xcd\x36\x59\ \x2c\xca\x82\x29\xf9\xc2\xae\x67\xa3\x97\x22\x24\x02\x00\x50\x92\ \x27\x8e\x56\xd4\xb8\xef\x94\x28\x1f\x02\x41\x3b\xb8\xb3\x5a\x20\ \x3d\x5e\xd2\xd0\x4d\xc6\xd8\xbe\x40\x5e\xe1\x00\x66\xcf\xbb\x9d\ \xec\xdc\x02\xa3\x4d\xe9\x55\x24\x27\x25\xf0\xb5\x2f\x5f\xc7\x2d\ \x73\xae\x08\xd4\xa5\xf7\x52\xa8\x52\xa8\x5f\x19\x97\x63\x39\xac\ \x67\xa3\x5d\x11\x32\x01\x00\x28\xce\x8f\xfa\xa4\xa2\xc6\xfb\x2b\ \x19\x64\x1f\x81\x7e\x09\xbe\x8c\xb1\x7d\x95\xd8\xf8\x04\xae\x98\ \x3e\x97\x92\x49\xd3\x7a\x74\xfe\x6c\x72\x2e\x71\xb1\xd1\x2c\xb8\ \xf6\x2a\xee\x59\x30\x9b\xc4\x78\xfd\xf7\x52\x84\xe0\xa7\x25\x79\ \x51\xcb\x74\x6f\xb8\x0b\x42\x2a\x00\x00\xe3\xf3\x94\x5f\x55\xd4\ \xca\x62\x90\x37\x07\xab\x8f\x82\x64\xdf\x3e\x80\xb7\x8f\x2d\x03\ \x14\x45\x61\xfc\xc4\xab\xb8\x72\xd6\x75\xc4\x98\x9b\x7d\xba\x11\ \x1b\x13\xcd\x8d\xb3\x27\x73\xcf\x82\xab\x49\x4f\x4d\x0a\x4e\x27\ \x52\x2c\x19\x9f\x27\x74\x89\xf1\xd7\x42\xc8\x05\x40\x08\xa1\xae\ \xaf\x92\x77\xc6\x2a\xea\x0a\x10\x41\xd9\x91\x8a\x8f\x86\xf1\xb9\ \x92\xf2\xda\xd0\xd6\x99\x37\x92\x82\xa2\xc1\xcc\x9e\x77\x1b\x99\ \x39\x79\x46\x9b\xd2\x6b\x88\x8f\x8f\x65\xde\x8c\xcb\xb9\x7b\xc1\ \x6c\x32\x52\x93\x83\xd8\x93\xdc\x6c\xb1\x2a\xf7\xe9\x19\xe5\xe7\ \x2f\x21\x17\x00\x80\x29\x85\xc2\x5e\x7e\x4c\xde\x84\x54\x3f\x47\ \x32\x38\x18\x7d\x14\xa6\x4a\x14\x05\x2a\x6a\x7a\xf7\x86\x60\x42\ \x62\x32\x33\xe6\xde\xcc\x65\x63\x26\x60\x46\x46\xe9\x43\x51\x7e\ \x36\xf3\xe7\x4e\x61\xde\xcc\x49\xc4\xc7\x05\x3d\xfd\xd4\xe1\x28\ \xab\xe5\x86\x31\xd9\xc2\x90\x1a\xe5\x86\x08\x00\x40\x49\xae\x38\ \x5e\x56\x2b\x6f\x14\xa8\xeb\x80\xb4\x60\xf4\x91\x9f\x2c\x49\x8e\ \x91\x6c\xac\x52\x7a\x5d\xb5\x61\xc5\x62\xa1\x64\xd2\x34\xa6\xcc\ \xb8\x96\xe8\x18\xb3\x86\x5a\x4f\x89\xb2\x5a\x98\x5a\x3a\x9a\xf9\ \xd7\x5e\x49\xf1\xa8\x21\xa1\xda\x3b\x69\x52\x85\x72\xfd\x98\x6c\ \x51\x1f\x8a\xce\x2e\x86\x61\x02\x00\x30\x21\x4f\xec\x2e\x3f\x26\ \xe7\xa3\xaa\x1f\x11\xa4\xac\xc2\x49\x31\xbe\x12\x63\xfb\x8e\x0b\ \x0e\x9c\x10\x78\x7b\xc1\x92\xa0\x70\xc0\x10\x66\xcf\xbb\x8d\x7e\ \x59\xb9\x46\x9b\x12\xf1\x0c\x1f\x5c\xc8\xdc\x69\xa5\xcc\x99\x5a\ \x4c\x5a\x4a\x90\xd6\xf7\x17\x43\xe0\x46\x55\xef\x28\xcd\xb7\x18\ \x5a\xb4\xd0\x50\x01\x00\x28\xc9\x15\xab\xcb\x6a\x3c\xf7\x09\xc4\ \xcb\x04\xe9\x78\xd0\x22\xe0\xb2\x2c\x49\x61\xaa\x64\x47\x9d\xa0\ \xae\x3d\x32\xa7\xca\x49\xc9\xa9\x4c\x9f\x7b\x0b\x23\x46\x17\x1b\ \x6d\x4a\xc4\xa2\x28\x82\xcb\x06\xf7\x67\xfa\xa4\x31\x4c\x9b\x3c\ \x96\x82\x9c\x7e\x46\x98\xe1\x45\x95\x77\x95\x14\x44\x2d\x37\xa2\ \xf3\xb3\x31\x5c\x00\x00\x26\xe4\x5b\x5f\xab\xa8\xf6\xc4\x4a\x21\ \x9e\x25\x88\xee\xc9\x89\xd1\x30\xb9\x48\xd2\x64\x97\xec\xae\x17\ \x1c\xef\x8c\x0c\x21\xb0\x58\x2c\x4c\xb8\x62\x26\x93\xa7\x5f\x43\ \x74\xb4\x99\x12\x59\x2b\x09\xf1\x71\x94\x8e\x1d\xca\xa4\x71\x23\ \x98\x52\x3a\x92\x7e\x69\x29\x46\x9a\x23\x85\x90\xff\x51\x9c\x6f\ \x7d\xc3\x48\x23\x4e\x11\x16\x02\x00\x50\x5c\x60\x7d\xa1\xdc\xec\ \x1a\x97\x00\x00\x09\x20\x49\x44\x41\x54\xac\xc6\x9b\x28\xe0\x6f\ \xc1\xee\x2b\x3d\x0e\xae\x1c\x20\xe9\x70\x49\x2a\x9b\x05\x95\x2d\ \x02\x87\xee\xa9\x16\xf4\xa1\xff\xe0\xe1\x5c\x3d\xef\x36\xd2\x32\ \xb2\x8c\x36\x25\x62\x48\x49\x4e\x60\xd4\xd0\xfe\x8c\x1e\x36\x80\ \xf1\xa3\x06\x33\x6a\xe8\x80\xc0\xd3\x70\xe9\x8c\x94\x3c\x52\x92\ \x6f\x7d\xc6\x68\x3b\x4e\x11\x36\x02\x00\x30\x21\xdf\xf2\xf7\xb2\ \x6a\x6f\xb4\x10\xfc\x31\x14\xfd\x25\x46\xc3\xc8\x6c\xc9\x65\x59\ \x92\xda\x36\xc1\x8e\x7a\x81\x3d\x4c\x0a\x90\x26\x26\xa7\x30\x6d\ \xf6\x8d\x8c\x1c\x3f\xd1\x68\x53\xc2\x1a\x45\x51\x28\xca\xcf\x62\ \xf8\xa0\x02\xc6\x8d\x18\xc4\x98\x11\x03\x19\x50\x90\x1d\x9e\x0e\ \x50\x82\x1f\x4f\xc8\xb7\x3c\x61\xb4\x19\x67\x13\x56\x02\x00\x30\ \xa1\xc0\xf2\x44\x79\x8d\x37\x15\xf8\x79\xa8\xfa\x14\x02\xf2\x53\ \x24\xd9\x89\x92\x6d\x75\xbe\x19\x81\x51\x28\x16\x0b\xe3\x4b\xa7\ \x32\xf5\xea\x79\xe6\x74\xff\x2c\xa2\xac\x16\x32\x33\x52\x19\x50\ \x90\xcd\xc0\xc2\x5c\x06\x16\xe6\x30\xb0\x20\x9b\xc2\xfc\x2c\x3d\ \x83\x70\x82\x86\x84\x5f\x4c\xc8\xb3\x84\xdc\xd1\xa7\x3b\xc2\x50\ \x26\x7d\x94\xd7\x78\x1f\x05\x0c\xf9\xc2\x2a\x5b\x04\x5b\x6a\x05\ \xaa\x8e\x27\x06\x2d\xcd\x8d\x3c\xf3\xa7\x5f\x76\x79\xcd\xa0\xa1\ \x23\x99\x79\xfd\xad\xa4\xa5\x1b\xb2\x31\x85\xcb\xe5\xa4\xb9\xf1\ \x38\x4d\x27\xea\x69\x6f\x6d\xa6\xa3\xbd\x95\xb6\x96\x66\x3a\x3b\ \xda\xb0\x77\x76\x60\xb7\xdb\x70\x3a\xf4\x8d\x51\x11\x42\x10\x1d\ \x13\x47\x5c\x7c\x3c\x89\x49\xc9\x14\x64\xa5\x50\x94\x9d\x4c\x56\ \x46\x2a\x19\xa9\xc9\x64\xf5\x4b\x21\x33\x3d\x95\x9c\xcc\x74\x14\ \x25\x6c\x1f\xd7\xee\xf8\x7f\x25\xf9\x96\xae\xff\xf8\x06\x11\x76\ \x33\x80\x53\x94\xe4\x5b\x1e\x2f\xaf\xf6\xb6\x22\xf8\x3b\x21\xc8\ \x5b\x70\x36\x45\xa9\x92\x84\x28\xc9\x86\x2a\x05\x57\x08\x0a\x91\ \xa6\xa4\x65\x30\xf3\xda\x05\x0c\x19\x11\x50\x99\x28\xdd\x88\x8e\ \x8e\x21\x3b\xb7\xa0\xeb\xe0\x21\x29\x71\x38\xec\xd8\xed\x36\x5c\ \x27\xc5\xc0\xe5\x74\xa2\xaa\x5e\xbc\xaa\x17\xb7\xeb\xdc\x72\xdd\ \x42\x08\x62\xce\xf2\x53\x88\x89\x8d\x23\x26\x36\x96\xa8\xe8\x18\ \xa2\x63\x62\xcf\x99\xe5\xc4\x58\x61\xce\x10\x15\x6b\xef\xc9\xf1\ \x28\x85\xe0\xe1\xe2\x3c\x8b\xee\xd9\x7c\xf5\x22\x6c\x05\x00\xa0\ \xa4\xc0\xf2\x54\x45\xad\xa7\x5d\x4a\xf1\x3c\x21\xb6\x35\x23\x01\ \xa6\xf4\x57\x59\x73\x58\x09\x9a\xef\x80\x35\x2a\x8a\x92\x49\xd3\ \xb9\x62\xfa\x5c\xa2\xa2\x23\x24\x9d\x91\x10\xc4\xc6\xc5\x13\x1b\ \x17\xaf\x7b\xd3\x43\xfb\xc9\xde\x34\xf8\xbd\x12\xf9\xf5\x92\x3c\ \xeb\x73\x46\x1b\xd2\x15\x61\x2d\x00\x00\xc5\x79\xd6\x97\xcb\x6a\ \x3c\x1e\x81\x78\x11\x08\xe9\x28\x49\x8d\x83\xd1\xb9\x92\xad\xb5\ \xfa\x4f\x3d\x07\x0f\x1b\xc5\xcc\xeb\x6f\x25\x35\x2d\xa8\xe9\x11\ \x22\x8a\xcc\x84\x5e\xe0\xa5\xe5\xc3\x89\x94\x77\x4f\x28\x08\x8f\ \xa3\xbe\xae\x08\x7b\x01\x80\x93\x7e\x02\x35\xb2\x5a\xa2\xbe\x05\ \x84\x74\x81\x5c\x94\x22\xd9\x55\x27\x70\xeb\x14\x4f\x90\x9a\xde\ \x8f\x59\xd7\x2d\x60\xd0\xb0\x51\xfa\x34\xd8\x8b\xe8\x0d\x5e\x9a\ \x40\xb3\x50\xd5\x05\xc5\x85\x51\xab\x8c\x36\xc4\x1f\x22\x6a\x57\ \xa5\xac\x4a\x0e\x11\x8a\xfa\x01\x30\x34\x94\xfd\x6e\xa8\x14\x1c\ \xeb\xa1\xf7\x60\x7b\x5b\x0b\x3b\x2b\x36\x32\x71\xea\x2c\x2c\x96\ \x88\xd0\xdd\x90\x73\x45\x91\x24\x3b\x29\xa2\x55\xe0\xb0\x2a\x94\ \xeb\x4b\xf3\x84\xa1\xee\xbd\x5a\x88\x28\x01\x00\xd8\x5c\x2b\xfb\ \x29\xd2\xfb\x16\x88\x2b\x43\xd5\xe7\x81\x46\xc1\x8e\xba\x88\xfb\ \xaa\x22\x8e\xab\x87\xa8\x24\x86\xff\x89\xde\xc5\x11\x72\x63\x94\ \xc5\x72\x93\x91\x81\x3d\x81\x10\x1e\xee\x51\x1a\x28\xcd\x13\x27\ \x54\x61\xb9\x46\x20\xde\x0c\x55\x9f\xa9\x66\xb0\x5d\xd0\xc9\x49\ \x92\x91\x3b\xf8\xa5\x58\xe2\xf0\x5a\x66\x44\xda\xe0\x87\x08\x14\ \x00\x80\xd2\x3c\x61\x1b\x9f\x27\x6e\x05\x7e\x04\x04\xfd\xa0\x2e\ \x39\x36\xa2\xa7\xa5\x61\x8f\x10\x11\x5b\xdd\x49\x02\x8f\x17\xe7\ \x8b\x3b\xa6\x14\x86\x26\x89\xa7\xde\x44\xa4\x00\x00\x08\x21\x64\ \x49\xbe\xe5\x71\x45\xaa\x73\x80\x86\x60\xf6\x15\x6d\x81\x58\x73\ \xd9\x1e\x34\x06\xa6\x49\x92\x23\x6f\x96\xd5\x28\x85\x7a\x5d\x49\ \xbe\xe5\x47\x42\x88\x88\x4d\x39\x13\xb1\x02\x70\x8a\xf1\x05\x51\ \x2b\x3d\x28\xa5\x48\xb9\x29\x98\xfd\x24\xc5\x44\xe4\x1b\x2a\xec\ \x51\xf0\x9d\xff\x47\x12\x12\x2a\x2c\x16\x65\xe2\x84\xbc\xa8\x8f\ \x8c\xb6\xa5\xa7\x44\xbc\x00\x00\x5c\x9e\x2f\xaa\x1c\xd2\x32\x5d\ \x40\xd0\x9c\x2e\xe2\xa2\x82\xd5\x72\xdf\x26\x3f\x55\x46\xd4\x77\ \x2b\xe1\x5f\x6d\x2e\x65\xca\xb8\x1c\x11\xb2\xd4\xdd\xc1\xa4\xd7\ \x6d\x6d\x97\xd7\x7a\x6e\x45\x8a\x7f\x02\x3d\x2f\xce\x76\x16\x3b\ \xeb\x05\xfb\x4f\xf4\xba\xaf\xcb\x70\xa6\x0f\x52\x49\x8b\x84\x04\ \xc6\x82\x56\xa4\xfc\x66\x49\xbe\xf5\xdf\x46\x9b\xa2\x27\xbd\x62\ \x06\x70\x36\x25\x79\xd6\x25\x1e\x94\xf1\x08\xb9\xca\x68\x5b\x4c\ \xba\x26\x3d\x9e\x88\x18\xfc\x52\x88\x95\x16\x55\x19\xdd\xdb\x06\ \x3f\xf4\x42\x01\x00\xdf\x92\xa0\x38\xd7\x32\x4b\x08\x7e\x00\xb8\ \xba\xbd\xc1\x24\xe4\x08\x60\x54\x56\xd8\xef\x9d\x79\x24\xfc\xa2\ \x24\x57\x5c\x3d\xae\x40\x54\x1b\x6d\x4c\x30\xe8\x95\x02\x00\xbe\ \x53\x82\xe2\x3c\xcb\x9f\x15\x94\xc9\x40\x79\x4f\xdb\xeb\x2b\xf5\ \x05\x42\xc5\x88\x2c\x49\x46\x58\x97\x2a\x94\x9b\x91\x4a\xe9\x84\ \x7c\xcb\x63\x91\xbc\xcb\xdf\x1d\xbd\x56\x00\x4e\x31\x3e\x5f\x54\ \xb4\xe6\x29\x93\x4e\xce\x06\x02\x2e\x18\x66\x8e\xff\x9e\x23\x84\ \x6f\xca\x3f\xa9\x48\x32\x3c\x33\x6c\xbf\x51\x3b\xf0\xa3\x03\x79\ \x96\xc9\x25\x05\x62\xab\xd1\xc6\x04\x9b\x3e\xb5\xab\xb5\xa9\x5e\ \x0e\xb2\x78\xe4\x53\x20\xe7\x68\xbd\x77\xdb\x31\xc1\xa1\xa6\x3e\ \xf5\x75\x05\x8c\x00\x32\x12\x24\xfd\x12\x20\x21\x0a\x62\xa3\x20\ \xc6\x22\x89\x8f\x06\x6b\x38\xbf\x72\xa4\x5c\xad\x60\x79\x68\x7c\ \x81\xd8\x67\xb4\x29\xa1\xa2\xcf\x3d\xd1\x52\x4a\x51\x5e\xeb\xbd\ \x5f\x20\x7e\x03\x64\xfb\x7b\xdf\xf6\x3a\xc1\xc1\xc6\x3e\xf7\x75\ \x69\x26\x3b\x49\x32\x32\x4b\x92\x12\x59\x8e\x3d\x75\x48\xf9\x68\ \x49\x81\xf5\x45\xa3\x0d\x09\x35\x7d\xf6\x89\xde\x5a\x27\x13\x3c\ \x5e\xf5\xbf\x04\x3c\x0a\x74\xfb\xb8\x9a\x01\x41\x5d\x93\x99\x20\ \x19\x96\x19\x61\x31\xfd\x02\x37\x92\x27\x5d\xb1\xca\xcf\x26\x67\ \x88\x36\xa3\xcd\x31\x82\x3e\xff\x44\x97\x55\xc9\x21\xc2\x22\x7f\ \x8d\x94\xb7\x77\x75\x5d\x5d\xbb\xe0\x8b\xca\x3e\xff\x75\x5d\x40\ \x46\x02\x8c\xce\x8e\x90\xb3\xfc\xb3\x10\x88\xf7\xbc\x5e\xf1\x83\ \xd2\x22\x71\xd0\x68\x5b\x8c\xc4\x7c\xa2\x4f\x52\x5e\xe3\xbe\x1a\ \x94\xc7\x81\x92\x8b\xfd\xbc\xcd\x01\x2b\x0e\x86\xf3\x02\x36\xb4\ \xc4\x45\xc1\x98\x1c\x49\x5e\x72\x04\xbd\xf1\x01\x90\x9b\xa5\x94\ \x8f\x4e\x28\x88\x5a\x61\xb4\x25\xe1\x80\x29\x00\xe7\x51\x5e\xe3\ \xbe\x5a\xa2\xfc\x4e\xc0\x39\xf5\xb7\xdc\x5e\x78\x7f\x8f\x29\x00\ \xe0\x9b\xe6\x4f\x2c\x94\x44\x47\x56\xfe\xbe\x9d\x42\xca\x5f\x8c\ \xcf\xb7\xbc\x61\x44\x19\xee\x70\xc5\x7c\xa2\xcf\xa3\x24\x3f\xea\ \xd3\x92\x3c\x65\x82\x44\xde\x04\x6c\x3b\xf5\xb9\xd5\x02\x61\x52\ \x5c\xc6\x50\xd2\xe2\x60\x72\xff\x88\x1a\xfc\xbb\x91\xf2\xbe\x03\ \x79\xca\xb8\xe2\x02\xeb\xeb\xe6\xe0\x3f\x17\x73\x06\xd0\x05\x8b\ \xa5\xb4\x0c\x3e\xe6\xbd\x53\x48\xf1\x5f\x2d\x76\xc6\xae\x3a\xd4\ \xb7\x15\x40\x00\x33\x07\xab\x91\x12\xba\xbb\x45\x08\xf9\xfb\xf1\ \xb9\x96\x57\x7b\xb3\x23\x4f\x4f\x31\x05\xc0\x4f\xde\xde\x2d\x17\ \x1c\x3a\xc1\x53\x2d\x76\xc2\xd7\x85\x25\xc8\xa4\xc4\xc1\xcc\x41\ \xe1\x3e\x96\xe4\x3a\x09\x8f\x97\xe4\x59\xde\x33\xdf\xf6\xdd\x63\ \x0a\x80\x46\x16\x95\xcb\x9b\x3a\x9d\x3c\xde\xd0\xc9\x08\x4f\xb8\ \x8f\x05\x9d\xc9\x4f\xf6\xad\xfd\xc3\x10\x17\x82\x7f\xab\x5e\xe5\ \x0f\xa5\x85\x62\xbb\xd1\xc6\x44\x12\xa6\x00\x04\xc8\xe2\xcd\xb2\ \xa8\xd9\xa3\xfe\xae\xd9\xf6\xff\xb7\x77\x37\x3d\x4d\x04\x61\x1c\ \xc0\xff\xcf\x4c\x09\x50\x8a\x21\x82\xd8\x42\x40\x88\xc1\x10\x09\ \x28\x95\x78\x20\x26\x86\x53\x23\x07\x8d\x9c\xf5\x13\x78\xf4\xa4\ \x37\x3f\x84\x31\x72\xd0\x18\xbd\x10\x3d\x18\xe2\x81\x83\x06\x0f\ \x36\xbe\x04\x4d\x14\xc3\x41\x13\x4b\xe2\xa1\x18\x15\xb5\x94\x76\ \xdf\xe6\x79\x3c\xd4\x78\x52\x23\xd2\x76\xd9\x3a\xbf\x0f\xb0\xf3\ \xec\x66\xe7\x9f\xd9\xdd\xd9\x19\x3a\x5d\x0a\x28\x22\xbb\x7a\x6c\ \x4f\x77\x42\x30\xb9\x6f\x47\x05\xc0\x1b\x00\xd7\xb4\x56\xd7\x0f\ \x25\xa9\xa6\xab\x42\x35\x2a\x1b\x00\x55\x70\xe3\xb9\x9c\xd9\xf4\ \x70\xf1\x73\x09\xc3\xbe\x69\xdc\x6b\xda\xda\x04\x64\x0e\x84\x3e\ \xec\x71\x41\x34\x0f\x31\xb3\xe3\x3d\xb1\x07\x76\x98\xbf\x3d\x0d\ \x7b\xb3\x86\x61\x31\x27\xc9\xf7\xeb\x7c\x69\xbd\xac\x66\xbe\x94\ \xd1\xd5\x88\x7f\x10\x4e\x0f\x73\x18\x5f\x00\x0c\x40\x8b\x44\x3c\ \x07\x57\xdf\x19\x1f\xa4\xaf\x75\xaf\xa0\x41\xd9\x00\xa8\x91\xd9\ \x67\x32\xc2\x06\x17\x4a\x01\x32\xdf\x9c\xc6\x09\x83\x63\x03\x82\ \xae\xfa\x4c\xf7\x65\x40\x1e\x13\xd1\x6d\x3f\xa6\xe6\x8e\x76\xd3\ \x5a\x3d\x1a\xfd\xdf\xd8\x00\xa8\x83\xbb\x2f\xe4\xe0\xba\xe0\xfc\ \x86\x83\x4c\xc1\x41\x4f\x94\x1f\x13\xd2\xbd\x82\xfe\x8e\x9a\x05\ \x40\x01\x42\xf7\x49\xf1\x82\xf1\xf4\xbd\x89\x01\xca\xd7\xaa\x21\ \xab\x22\xb2\x37\x62\x54\x5d\x5e\x94\x44\xbc\x9d\xcf\x39\xbe\x9a\ \x29\x7a\x32\x5a\xf4\xa8\x35\x4a\xa3\x83\x23\xbd\x82\xbe\xea\x06\ \xc0\x4b\x00\x0b\xc4\xbc\x60\x3e\xc4\xb2\x13\x13\xe4\x57\xf3\xe0\ \xd6\x9f\xd9\x00\x08\xd9\xcd\x15\x49\x99\x32\x9f\x2d\x79\x74\xb2\ \xec\xd3\xc8\x86\x83\x8e\xd0\x5f\xb3\xfd\x46\x73\xac\x32\x11\x68\ \x1b\x7b\x24\x04\xa8\x74\xf8\x2c\x89\x3c\x12\xad\x1f\xa6\x53\xf4\ \xb1\x6a\x05\x5a\x5b\x66\x03\x60\x87\xb9\xf5\x44\x76\xe9\x16\x9c\ \x72\x03\x3e\xe1\x06\xea\xf0\xa6\x8f\xbe\x82\x83\x04\x87\x3c\x4a\ \x20\x54\xa6\x00\xef\x4d\xfc\x75\x21\x0e\x80\x15\x01\x5e\x91\x60\ \x99\x48\x3d\x4d\x94\xb0\x34\x34\x44\x6e\xed\xaa\xb4\xb6\xca\x06\ \x40\x04\x5c\x5d\x92\xb8\x12\x4c\x13\xf3\x71\x03\x1a\xf3\x03\xf4\ \xbb\x86\x3a\xcb\x3e\xda\x3c\x53\x9f\xff\x39\xf6\x77\x0a\x46\x93\ \xbf\xe8\xfc\x84\x3c\x44\x72\x02\xca\x11\xe1\x9d\xb0\xbc\x86\xd2\ \xcb\x85\x14\xde\x4e\x11\x05\xf5\xa8\xcd\xfa\x77\x36\x00\x22\xee\ \x4a\x56\xba\x5b\x9a\x31\x49\xe0\xb1\x00\xaa\x4f\x8c\x24\x0d\x63\ \x8f\x2f\xd8\x6d\x98\xda\x03\x96\x36\x16\x34\x31\x93\x06\x80\x80\ \xa1\x01\x90\x61\x90\xf9\xd1\x9f\x35\x01\x5a\xfd\x5c\xf6\x90\x63\ \x0a\xac\x35\x02\x05\xf1\x34\xa1\xac\x15\x8a\xe9\x5e\x99\x8f\x37\ \x21\x0f\x91\x4f\xac\x64\x4d\x10\x5b\xdd\x70\xb1\x3a\x35\x48\x4e\ \x38\x67\x6e\x55\xc3\x77\x15\x65\xab\x07\x70\xac\xd6\x54\x00\x00\ \x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ \x00\x00\x05\xc2\ \x89\ \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ \x00\x00\x18\x00\x00\x00\x18\x08\x06\x00\x00\x00\xe0\x77\x3d\xf8\ \x00\x00\x00\x04\x73\x42\x49\x54\x08\x08\x08\x08\x7c\x08\x64\x88\ \x00\x00\x00\x09\x70\x48\x59\x73\x00\x00\x0d\xd7\x00\x00\x0d\xd7\ \x01\x42\x28\x9b\x78\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6f\x66\ \x74\x77\x61\x72\x65\x00\x77\x77\x77\x2e\x69\x6e\x6b\x73\x63\x61\ \x70\x65\x2e\x6f\x72\x67\x9b\xee\x3c\x1a\x00\x00\x05\x3f\x49\x44\ \x41\x54\x48\x89\x7d\x95\x69\x6c\x54\x55\x14\x80\xbf\xfb\xe6\x31\ \xe5\xcd\x74\x1e\xb5\x53\x5a\x9c\x96\xca\x5a\x01\x31\x85\x52\x11\ \xab\x60\x52\x40\xc3\x22\x1a\x14\x89\x10\x23\xb8\x41\x5c\xb0\x44\ \x82\x06\x7f\x68\xa2\xd1\xa8\x08\x08\x46\x04\x11\x90\x62\xaa\x56\ \x45\x5a\x4b\x54\x44\x04\x62\x44\x96\x41\x0d\x4b\x2d\x52\x5a\xa0\ \x0b\xd0\x5a\x3a\x9d\x4e\x3b\xed\xbc\x77\xfc\x51\x66\xa4\x1b\xe7\ \xd7\xbb\x27\xe7\x7e\xe7\x9d\xf5\x2a\xae\x23\xf9\xc7\x24\xa7\x35\ \x2c\x2f\x36\xb7\x33\xa9\xd5\x52\x66\xb8\x5d\x9c\x22\x8a\x9c\xc1\ \x52\x99\xe4\xa2\x54\x90\xef\x2c\xb4\x9d\xe3\x53\xd5\xf9\xde\x18\ \xaa\x27\xe5\xe6\x23\x72\x7b\x30\x2c\xf9\xf5\x21\x35\x5c\xa4\x43\ \xe7\x33\x85\x54\x13\xaa\x02\x30\x34\x51\xf0\xba\x63\xe6\x36\x4a\ \x7d\xad\x94\x5a\x3e\xf6\x46\x55\xd1\x95\xa5\x75\x55\x7c\x7d\xdc\ \xca\x13\x5b\x0e\x04\xc3\xff\xc3\x5d\x7d\xc0\xa1\x41\xa2\x4b\xc8\ \x4e\x13\x3c\x7d\xff\xb7\x17\x11\x0d\x91\x39\xb6\x6d\xff\xe5\xaf\ \x8a\xcc\xba\x6e\x04\x1b\x0e\xd9\x3b\x83\xad\xcc\x6a\x0c\x34\x71\ \xba\xac\x8c\xfa\xfa\x3a\x0c\x09\xa2\x22\x21\x3c\xf1\x06\x8b\xe6\ \xcd\xc4\x65\xc4\x01\x50\x71\xbe\x96\x55\x9f\x7c\xc3\x1b\xcb\x16\ \x60\xc6\xbb\xae\x89\x86\xa5\x59\x3e\xc7\xda\xa8\x42\x8f\x7e\x6c\ \xfa\x5d\x56\x9d\xa8\x6c\x98\xb5\xef\xc7\x9d\x94\x9d\xfc\x13\xdb\ \xb6\xbb\xa5\xee\xb1\xd9\x53\x71\x3a\x75\x36\x7c\x56\x42\x61\xc9\ \x7e\x22\x96\xd5\x3d\x23\xc2\xea\x92\x93\x91\xd0\x8c\x51\xfa\xa6\ \x58\x8a\xb6\x1f\x95\x09\x87\x4f\x55\xe4\x6d\xfd\xf0\x1d\x4a\x8f\ \x1f\xeb\x11\x1e\x95\x40\xa0\x99\x82\xa2\xbd\x3d\xc1\x63\x4e\x4c\ \x43\xad\xd9\xf6\xbb\x64\xc7\x1c\x5c\xb8\xdc\x54\xb0\xe3\xb3\x4f\ \x54\xb8\x35\x04\x40\x3f\xd3\xcd\x73\x8f\xdd\xcf\xca\x15\x4f\xf7\ \xea\x28\x2a\xaa\x4b\x9b\xd8\x02\xfb\xca\x35\x77\x63\x84\x02\x00\ \xfd\x48\xb5\xa4\x7f\x5a\xb8\x3b\x3d\x18\x0c\x00\x60\x9a\x1e\x3e\ \x7e\xeb\x05\x7c\x29\x5e\x02\xc1\x50\xaf\xe0\x74\x5f\x32\x79\x4f\ \xcc\xc6\xe3\x76\x11\x08\x86\xb8\x12\x08\xe2\xf1\xc4\x73\xfa\x8a\ \x9b\x36\x0b\xea\x42\x0c\x2b\xf0\x4b\xb6\xae\x61\x3f\x70\xe0\xd0\ \x1f\xb1\x6e\x7a\x6a\xee\xbd\xf8\x52\xbc\xbd\x82\x75\xdd\xc1\xe2\ \xf9\x33\x79\x68\xfa\x44\x76\xfd\x72\x98\xf7\xb7\xec\xa0\xf2\xc2\ \xc5\xab\xd1\x28\xd2\x87\x64\x90\x3b\x6d\x36\xde\xfe\x03\x08\xb4\ \xc9\x2b\x3a\xa2\xa6\x55\xd5\xfe\x1b\x03\x4c\x1c\x3f\xba\x47\xf0\ \x84\xac\x91\xb8\xdd\x06\x71\xce\x3e\x8c\xbe\x79\x10\x8b\x56\xbc\ \xcf\x99\xca\xea\x4e\x36\x22\x42\xe5\x99\xbf\xf9\x62\xeb\x07\x2c\ \x78\xe6\x25\x5a\x5c\x9e\x1c\xfd\xd7\x72\xfb\xae\xe6\x50\x47\x2a\ \x34\x4d\x91\xd8\xcf\xec\x74\x29\x35\x25\x89\x25\x0b\x1f\xe0\xce\ \xec\x5b\xa8\x6b\x68\xe4\xed\x8f\x8a\xd9\x7d\xc0\x8f\x5c\x1d\x92\ \x21\xe9\x3e\x32\x47\x0e\xa6\xb6\xae\x81\x83\xfe\x53\x88\x08\xa1\ \x60\x13\x47\x7f\xdb\x47\xe2\xb4\x99\x37\xe8\x83\x93\x94\xee\xd0\ \x1d\x58\x11\x0b\xdb\x16\x82\xcd\x21\x4c\x4f\xc7\x98\xba\x8d\xbe\ \xe4\xaf\x59\x8e\xa6\x69\x14\x14\xed\x65\x73\xe1\x0f\xb4\xb4\x84\ \x01\xb8\x29\x2d\x85\xbc\x85\xb3\xb9\x2d\x33\x03\x80\xcb\xcd\x8a\ \xd5\xf9\x7b\xd8\xff\x53\x31\x00\xe5\x65\x27\x69\xbf\x67\x46\x1f\ \xdd\x67\x12\x49\xf7\x25\x73\xf6\x5c\x0d\x00\x3b\x76\x1f\x64\xec\ \x1d\x53\x68\x0a\x43\xff\x78\x07\xa5\xa5\xff\xf0\x79\xe1\x37\x9c\ \xab\xea\xc8\xb3\x61\xc4\xf1\xf8\x9c\x7b\x99\x33\x63\x12\xb6\x6d\ \x63\x59\x36\x0e\x87\x86\xd7\x2d\xe4\x4e\x9a\x10\x73\xd0\x50\x7f\ \x09\xa4\x63\xd0\xaa\x73\xb2\x46\x65\x44\x1d\x6c\x2a\x28\x61\xf0\ \xe1\x72\xdc\x66\x3f\xaa\xcf\x9d\xa5\xfe\x72\x6d\xac\x80\x53\x26\ \x66\xf1\xec\xa3\xf7\x91\x74\x43\x3f\x7e\x3d\x72\x82\xb5\x5b\xbe\ \xe5\xe3\xb7\x97\x62\xc6\xbb\xd0\x80\x9c\x61\x06\x4a\x53\x88\x2d\ \x44\x22\xed\xc4\xe9\x12\xd6\x41\x95\xcf\x9d\x79\x77\xc6\xae\xbd\ \x87\x68\x68\x6c\x42\x44\x28\x3f\x7d\xb2\x53\x1d\xfa\x0f\xf0\x91\ \x3b\xfd\x21\x46\x8f\x18\x4a\xbc\x2b\xcc\xb2\x37\x37\x72\xd0\x7f\ \xaa\x5b\x23\x04\x9b\x1a\x11\xbb\xa3\x36\xa6\xc7\xcd\xd4\xe1\xea\ \x67\x4d\x90\x92\xc4\x04\x0f\xeb\x5e\x7b\x86\x31\xa3\x47\x74\x9a\ \x9c\xa4\x14\x1f\x53\x66\xcc\x61\xde\x93\x4b\xa9\x39\x77\x96\x8b\ \xf5\xcd\xfc\x75\xbe\xb5\x47\x38\xc0\x81\x43\xc7\xaf\x69\x0e\x2f\ \x0a\x29\xd6\x2d\xb4\x9d\x3a\xf6\x9a\x41\x03\x07\x38\x96\x3e\xbf\ \x98\xa3\x15\xad\x84\x5a\x42\xb8\x0c\x17\xce\xbe\x06\x15\x67\x4a\ \xd9\xb6\xfe\x5d\x1a\xea\x2f\x71\xcb\x98\xf1\x9c\xae\xeb\x71\xc3\ \x53\x75\xb1\x8e\xcd\x85\xdf\xc7\xce\xb9\x77\x64\xda\x0e\xb4\x62\ \x7d\x7c\xaa\x3a\xef\xaf\xb2\xb6\x02\x4f\x24\x18\x42\x9c\x61\xe0\ \xec\x6b\x00\x10\x6e\x0d\xf1\xd5\xb6\xf5\x3d\x02\xa3\xb2\xc7\x5f\ \x49\xcb\xbf\x35\x6c\xff\x76\x4f\x6c\xf2\x93\xbd\x09\x4c\xce\xc9\ \xda\x96\x99\xa6\x2e\xe8\x00\x9a\xad\xbd\x6a\x69\xf6\xc3\x5e\x17\ \x9e\x21\x89\xc2\x99\xfa\x8e\xbf\x8c\xbe\x07\xd7\x93\xf7\xd6\x6e\ \xec\x74\x36\x3d\x6e\x5e\x7f\x71\x41\x73\x52\x42\xe2\x0a\xb8\xba\ \xec\xc6\x0c\x54\x55\x4a\x93\xb9\x80\x35\xb2\xbf\x10\xa7\x77\x86\ \x68\x0e\x07\xd9\x77\xe6\x62\xb8\xdc\xf4\x26\xba\xc3\xc1\xd4\x89\ \x59\x6c\x5d\xb9\xcc\x1e\x95\x91\xfe\x48\xf6\x20\x55\x03\x5d\x1e\ \x1c\x7f\xb5\xb5\x04\x61\x75\x9b\x85\x76\xbc\x56\x51\x56\xd3\x42\ \xd1\x17\x5b\x98\x3c\xfd\x41\xbc\xc9\x03\x00\x68\x6f\x0b\xb3\xff\ \xa7\x62\x5a\x5b\x5a\x18\xe8\x35\x18\x98\xec\x26\xdd\x97\xc2\xb8\ \x5b\x87\x91\x60\xc6\xdb\x40\x5e\x56\xaa\x63\x5d\x94\xd9\xad\x62\ \xfe\xaa\xc8\x2c\x41\x6d\x6f\xb7\xf0\xec\x3a\xa5\xba\xef\xe3\x68\ \x54\x0a\x26\x0f\xb3\x71\x3b\x63\xaa\x80\x88\xcc\x1f\x97\xa6\x7f\ \xd7\xc9\xae\xeb\xc5\xac\x54\xbd\x48\x69\xda\x50\x5d\xb1\x4e\xeb\ \x05\x3e\xd4\x2b\x4c\x1d\x1e\x83\xdb\x08\xf9\x76\xbb\x36\xa2\x2b\ \xbc\xc7\x08\xae\x95\x2f\x4f\xc8\x98\x40\xb3\xfd\x72\x30\xac\x72\ \xdb\x2c\x65\x86\x2d\x9c\x11\x1b\x75\xcf\x70\xfb\x1f\xc3\xa9\xca\ \x94\x48\x89\x26\x5a\x51\x66\x9a\xba\xd0\x1b\xe3\x3f\x12\xae\x2a\ \x58\xfd\xf2\x8c\xf2\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\ \x82\ " qt_resource_name = b"\ \x00\x05\ \x00\x6f\xa6\x53\ \x00\x69\ \x00\x63\x00\x6f\x00\x6e\x00\x73\ \x00\x17\ \x0b\xce\x1a\xe7\ \x00\x71\ \x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x31\x00\x32\x00\x38\x00\x78\x00\x31\ \x00\x32\x00\x38\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x17\ \x07\xc8\x36\xe7\ \x00\x71\ \x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x35\x00\x31\x00\x32\x00\x78\x00\x35\ \x00\x31\x00\x32\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x15\ \x02\x0a\xc7\x87\ \x00\x71\ \x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x33\x00\x32\x00\x78\x00\x33\x00\x32\ \x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x15\ \x02\xf8\xd9\xc7\ \x00\x71\ \x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x36\x00\x34\x00\x78\x00\x36\x00\x34\ \x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x15\ \x02\x26\xc3\x07\ \x00\x71\ \x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x31\x00\x36\x00\x78\x00\x31\x00\x36\ \x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x15\ \x02\x14\xc5\x47\ \x00\x71\ \x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x34\x00\x38\x00\x78\x00\x34\x00\x38\ \x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x15\ \x02\xa6\xd3\x07\ \x00\x71\ \x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x39\x00\x36\x00\x78\x00\x39\x00\x36\ \x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x17\ \x0a\x1f\xbe\xe7\ \x00\x71\ \x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x32\x00\x35\x00\x36\x00\x78\x00\x32\ \x00\x35\x00\x36\x00\x2e\x00\x70\x00\x6e\x00\x67\ \x00\x15\ \x02\x38\xc1\xc7\ \x00\x71\ \x00\x75\x00\x74\x00\x65\x00\x62\x00\x72\x00\x6f\x00\x77\x00\x73\x00\x65\x00\x72\x00\x2d\x00\x32\x00\x34\x00\x78\x00\x32\x00\x34\ \x00\x2e\x00\x70\x00\x6e\x00\x67\ " qt_resource_struct = b"\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x09\x00\x00\x00\x02\ \x00\x00\x00\x78\x00\x00\x00\x00\x00\x01\x00\x00\xbd\xb9\ \x00\x00\x01\x08\x00\x00\x00\x00\x00\x01\x00\x00\xda\x06\ \x00\x00\x00\xd8\x00\x00\x00\x00\x00\x01\x00\x00\xd6\x51\ \x00\x00\x01\x9c\x00\x00\x00\x00\x00\x01\x00\x01\x49\xa3\ \x00\x00\x01\x38\x00\x00\x00\x00\x00\x01\x00\x00\xe6\x0b\ \x00\x00\x00\xa8\x00\x00\x00\x00\x00\x01\x00\x00\xc5\x97\ \x00\x00\x00\x44\x00\x00\x00\x00\x00\x01\x00\x00\x23\x02\ \x00\x00\x01\x68\x00\x00\x00\x00\x00\x01\x00\x00\xff\xe4\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ " def qInitResources(): QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) def qCleanupResources(): QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data) qInitResources() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581772733.9114225 qutebrowser-1.10.1/qutebrowser/utils/0000755000175000017510000000000000000000000021006 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/__init__.py0000644000175000017510000000147500000000000023126 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Misc utility functions.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/debug.py0000644000175000017510000003030000000000000022442 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Utilities used for debugging.""" import re import inspect import logging import functools import datetime import typing import types from PyQt5.QtCore import Qt, QEvent, QMetaMethod, QObject, pyqtSignal from PyQt5.QtWidgets import QApplication from qutebrowser.utils import log, utils, qtutils, objreg def log_events(klass: typing.Type) -> typing.Type: """Class decorator to log Qt events.""" old_event = klass.event @functools.wraps(old_event) def new_event(self: typing.Any, e: QEvent) -> bool: """Wrapper for event() which logs events.""" log.misc.debug("Event in {}: {}".format(utils.qualname(klass), qenum_key(QEvent, e.type()))) return old_event(self, e) klass.event = new_event return klass def log_signals(obj: QObject) -> QObject: """Log all signals of an object or class. Can be used as class decorator. """ def log_slot(obj: QObject, signal: pyqtSignal, *args: typing.Any) -> None: """Slot connected to a signal to log it.""" dbg = dbg_signal(signal, args) try: r = repr(obj) except RuntimeError: # pragma: no cover r = '' log.signals.debug("Signal in {}: {}".format(r, dbg)) def connect_log_slot(obj: QObject) -> None: """Helper function to connect all signals to a logging slot.""" metaobj = obj.metaObject() for i in range(metaobj.methodCount()): meta_method = metaobj.method(i) qtutils.ensure_valid(meta_method) if meta_method.methodType() == QMetaMethod.Signal: name = bytes(meta_method.name()).decode('ascii') if name != 'destroyed': signal = getattr(obj, name) try: signal.connect(functools.partial( log_slot, obj, signal)) except TypeError: # pragma: no cover pass if inspect.isclass(obj): old_init = obj.__init__ # type: ignore @functools.wraps(old_init) def new_init(self: typing.Any, *args: typing.Any, **kwargs: typing.Any) -> None: """Wrapper for __init__() which logs signals.""" old_init(self, *args, **kwargs) connect_log_slot(self) obj.__init__ = new_init # type: ignore else: connect_log_slot(obj) return obj def qenum_key(base: typing.Type, value: int, add_base: bool = False, klass: typing.Type = None) -> str: """Convert a Qt Enum value to its key as a string. Args: base: The object the enum is in, e.g. QFrame. value: The value to get. add_base: Whether the base should be added to the printed name. klass: The enum class the value belongs to. If None, the class will be auto-guessed. Return: The key associated with the value as a string if it could be found. The original value as a string if not. """ if klass is None: klass = value.__class__ if klass == int: raise TypeError("Can't guess enum class of an int!") try: idx = base.staticMetaObject.indexOfEnumerator(klass.__name__) meta_enum = base.staticMetaObject.enumerator(idx) ret = meta_enum.valueToKey(int(value)) except AttributeError: ret = None if ret is None: for name, obj in vars(base).items(): if isinstance(obj, klass) and obj == value: ret = name break else: ret = '0x{:04x}'.format(int(value)) if add_base and hasattr(base, '__name__'): return '.'.join([base.__name__, ret]) else: return ret def qflags_key(base: typing.Type, value: int, add_base: bool = False, klass: typing.Type = None) -> str: """Convert a Qt QFlags value to its keys as string. Note: Passing a combined value (such as Qt.AlignCenter) will get the names for the individual bits (e.g. Qt.AlignVCenter | Qt.AlignHCenter). FIXME https://github.com/qutebrowser/qutebrowser/issues/42 Args: base: The object the flags are in, e.g. QtCore.Qt value: The value to get. add_base: Whether the base should be added to the printed names. klass: The flags class the value belongs to. If None, the class will be auto-guessed. Return: The keys associated with the flags as a '|' separated string if they could be found. Hex values as a string if not. """ if klass is None: # We have to store klass here because it will be lost when iterating # over the bits. klass = value.__class__ if klass == int: raise TypeError("Can't guess enum class of an int!") if not value: return qenum_key(base, value, add_base, klass) bits = [] names = [] mask = 0x01 value = int(value) while mask <= value: if value & mask: bits.append(mask) mask <<= 1 for bit in bits: # We have to re-convert to an enum type here or we'll sometimes get an # empty string back. names.append(qenum_key(base, klass(bit), add_base)) return '|'.join(names) def signal_name(sig: pyqtSignal) -> str: """Get a cleaned up name of a signal. Unfortunately, the way to get the name of a signal differs based on: - PyQt versions (5.11 added .signatures for unbound signals) - Bound vs. unbound signals Here, we try to get the name from .signal or .signatures, or if all else fails, extract it from the repr(). Args: sig: The pyqtSignal Return: The cleaned up signal name. """ if hasattr(sig, 'signal'): # Bound signal # Examples: # sig.signal == '2signal1' # sig.signal == '2signal2(QString,QString)' m = re.fullmatch(r'[0-9]+(?P.*)\(.*\)', sig.signal) # type: ignore elif hasattr(sig, 'signatures'): # Unbound signal, PyQt >= 5.11 # Examples: # sig.signatures == ('signal1()',) # sig.signatures == ('signal2(QString,QString)',) m = re.fullmatch(r'(?P.*)\(.*\)', sig.signatures[0]) # type: ignore else: # pragma: no cover # Unbound signal, PyQt < 5.11 # Examples: # repr(sig) == "" # repr(sig) == "" # repr(sig) == "" # repr(sig) == "" patterns = [ r'[^[]*)\[.*>', r'[^(]*)\(.*>', ] for pattern in patterns: m = re.fullmatch(pattern, repr(sig)) if m is not None: break assert m is not None, sig return m.group('name') def format_args(args: typing.Sequence = None, kwargs: typing.Mapping = None) -> str: """Format a list of arguments/kwargs to a function-call like string.""" if args is not None: arglist = [utils.compact_text(repr(arg), 200) for arg in args] else: arglist = [] if kwargs is not None: for k, v in kwargs.items(): arglist.append('{}={}'.format(k, utils.compact_text(repr(v), 200))) return ', '.join(arglist) def dbg_signal(sig: pyqtSignal, args: typing.Any) -> str: """Get a string representation of a signal for debugging. Args: sig: A pyqtSignal. args: The arguments as list of strings. Return: A human-readable string representation of signal/args. """ return '{}({})'.format(signal_name(sig), format_args(args)) def format_call(func: typing.Callable, args: typing.Sequence = None, kwargs: typing.Mapping = None, full: bool = True) -> str: """Get a string representation of a function calls with the given args. Args: func: The callable to print. args: A list of positional arguments. kwargs: A dict of named arguments. full: Whether to print the full name Return: A string with the function call. """ if full: name = utils.qualname(func) else: name = func.__name__ return '{}({})'.format(name, format_args(args, kwargs)) class log_time: # noqa: N801,N806 pylint: disable=invalid-name """Log the time an operation takes. Usable as context manager or as decorator. """ def __init__(self, logger: typing.Union[logging.Logger, str], action: str = 'operation') -> None: """Constructor. Args: logger: The logging.Logger to use for logging, or a logger name. action: A description of what's being done. """ if isinstance(logger, str): self._logger = logging.getLogger(logger) else: self._logger = logger self._started = None # type: typing.Optional[datetime.datetime] self._action = action def __enter__(self) -> None: self._started = datetime.datetime.now() # The string annotation is a WORKAROUND for a Python 3.5.2 bug: # https://github.com/python/typing/issues/266 def __exit__(self, _exc_type: 'typing.Optional[typing.Type[BaseException]]', _exc_val: typing.Optional[BaseException], _exc_tb: typing.Optional[types.TracebackType]) -> None: assert self._started is not None finished = datetime.datetime.now() delta = (finished - self._started).total_seconds() self._logger.debug("{} took {} seconds.".format( self._action.capitalize(), delta)) def __call__(self, func: typing.Callable) -> typing.Callable: @functools.wraps(func) def wrapped(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: """Call the original function.""" with self: return func(*args, **kwargs) return wrapped def _get_widgets() -> typing.Sequence[str]: """Get a string list of all widgets.""" widgets = QApplication.instance().allWidgets() widgets.sort(key=repr) return [repr(w) for w in widgets] def _get_pyqt_objects(lines: typing.MutableSequence[str], obj: QObject, depth: int = 0) -> None: """Recursive method for get_all_objects to get Qt objects.""" for kid in obj.findChildren(QObject, '', Qt.FindDirectChildrenOnly): lines.append(' ' * depth + repr(kid)) _get_pyqt_objects(lines, kid, depth + 1) def get_all_objects(start_obj: QObject = None) -> str: """Get all children of an object recursively as a string.""" output = [''] widget_lines = _get_widgets() widget_lines = [' ' + e for e in widget_lines] widget_lines.insert(0, "Qt widgets - {} objects:".format( len(widget_lines))) output += widget_lines if start_obj is None: start_obj = QApplication.instance() pyqt_lines = [] # type: typing.List[str] _get_pyqt_objects(pyqt_lines, start_obj) pyqt_lines = [' ' + e for e in pyqt_lines] pyqt_lines.insert(0, 'Qt objects - {} objects:'.format(len(pyqt_lines))) output += [''] output += pyqt_lines output += objreg.dump_objects() return '\n'.join(output) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/docutils.py0000644000175000017510000001455100000000000023214 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Utilities used for the documentation and built-in help.""" import re import sys import inspect import os.path import collections import enum import typing import qutebrowser from qutebrowser.utils import log, utils def is_git_repo() -> bool: """Check if we're running from a git repository.""" gitfolder = os.path.join(qutebrowser.basedir, os.path.pardir, '.git') return os.path.isdir(gitfolder) def docs_up_to_date(path: str) -> bool: """Check if the generated html documentation is up to date. Args: path: The path of the document to check. Return: True if they are up to date or we couldn't check. False if they are outdated. """ if hasattr(sys, 'frozen') or not is_git_repo(): return True html_path = os.path.join(qutebrowser.basedir, 'html', 'doc', path) filename = os.path.splitext(path)[0] asciidoc_path = os.path.join(qutebrowser.basedir, os.path.pardir, 'doc', 'help', filename + '.asciidoc') try: html_time = os.path.getmtime(html_path) asciidoc_time = os.path.getmtime(asciidoc_path) except FileNotFoundError: return True return asciidoc_time <= html_time class DocstringParser: """Generate documentation based on a docstring of a command handler. The docstring needs to follow the format described in doc/contributing. Attributes: _state: The current state of the parser state machine. _cur_arg_name: The name of the argument we're currently handling. _short_desc_parts: The short description of the function as list. _long_desc_parts: The long description of the function as list. short_desc: The short description of the function. long_desc: The long description of the function. arg_descs: A dict of argument names to their descriptions """ State = enum.Enum('State', ['short', 'desc', 'desc_hidden', 'arg_start', 'arg_inside', 'misc']) def __init__(self, func: typing.Callable) -> None: """Constructor. Args: func: The function to parse the docstring for. """ self._state = self.State.short self._cur_arg_name = None # type: typing.Optional[str] self._short_desc_parts = [] # type: typing.List[str] self._long_desc_parts = [] # type: typing.List[str] self.arg_descs = collections.OrderedDict( ) # type: typing.Dict[str, typing.Union[str, typing.List[str]]] doc = inspect.getdoc(func) handlers = { self.State.short: self._parse_short, self.State.desc: self._parse_desc, self.State.desc_hidden: self._skip, self.State.arg_start: self._parse_arg_start, self.State.arg_inside: self._parse_arg_inside, self.State.misc: self._skip, } if doc is None: if sys.flags.optimize < 2: log.commands.warning( "Function {}() from {} has no docstring".format( utils.qualname(func), inspect.getsourcefile(func))) self.long_desc = "" self.short_desc = "" return for line in doc.splitlines(): handler = handlers[self._state] stop = handler(line) if stop: break for k, v in self.arg_descs.items(): desc = ' '.join(v) desc = re.sub(r', or None($|\.)', r'\1', desc) desc = re.sub(r', or None', r', or not given', desc) self.arg_descs[k] = desc self.long_desc = ' '.join(self._long_desc_parts) self.short_desc = ' '.join(self._short_desc_parts) def _process_arg(self, line: str) -> None: """Helper method to process a line like 'fooarg: Blah blub'.""" self._cur_arg_name, argdesc = line.split(':', maxsplit=1) self._cur_arg_name = self._cur_arg_name.strip().lstrip('*') self.arg_descs[self._cur_arg_name] = [argdesc.strip()] def _skip(self, line: str) -> None: """Handler to ignore everything until we get 'Args:'.""" if line.startswith('Args:'): self._state = self.State.arg_start def _parse_short(self, line: str) -> None: """Parse the short description (first block) in the docstring.""" if not line: self._state = self.State.desc else: self._short_desc_parts.append(line.strip()) def _parse_desc(self, line: str) -> None: """Parse the long description in the docstring.""" if line.startswith('Args:'): self._state = self.State.arg_start elif line.strip() == '//' or line.startswith('Attributes:'): self._state = self.State.desc_hidden elif line.strip(): self._long_desc_parts.append(line.strip()) def _parse_arg_start(self, line: str) -> None: """Parse first argument line.""" self._process_arg(line) self._state = self.State.arg_inside def _parse_arg_inside(self, line: str) -> bool: """Parse subsequent argument lines.""" argname = self._cur_arg_name assert argname is not None descs = self.arg_descs[argname] assert isinstance(descs, list) if re.fullmatch(r'[A-Z][a-z]+:', line): if not descs[-1].strip(): del descs[-1] return True elif not line.strip(): descs.append('\n\n') elif line[4:].startswith(' '): descs.append(line.strip() + '\n') else: self._process_arg(line) return False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581772706.0 qutebrowser-1.10.1/qutebrowser/utils/error.py0000644000175000017510000000512700000000000022516 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2015-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Tools related to error printing/displaying.""" import argparse from PyQt5.QtWidgets import QMessageBox from qutebrowser.utils import log, utils def _get_name(exc: BaseException) -> str: """Get a suitable exception name as a string.""" prefixes = ['qutebrowser', 'builtins'] name = utils.qualname(exc.__class__) for prefix in prefixes: if name.startswith(prefix): name = name[len(prefix) + 1:] break return name def handle_fatal_exc(exc: BaseException, args: argparse.Namespace, title: str, *, pre_text: str = '', post_text: str = '') -> None: """Handle a fatal "expected" exception by displaying an error box. If --no-err-windows is given as argument, the text is logged to the error logger instead. Args: exc: The Exception object being handled. args: The argparser namespace. title: The title to be used for the error message. pre_text: The text to be displayed before the exception text. post_text: The text to be displayed after the exception text. """ if args.no_err_windows: lines = [ "Handling fatal {} with --no-err-windows!".format(_get_name(exc)), "", "title: {}".format(title), "pre_text: {}".format(pre_text), "post_text: {}".format(post_text), "exception text: {}".format(str(exc) or 'none'), ] log.misc.exception('\n'.join(lines)) else: if pre_text: msg_text = '{}: {}'.format(pre_text, exc) else: msg_text = str(exc) if post_text: msg_text += '\n\n{}'.format(post_text) msgbox = QMessageBox(QMessageBox.Critical, title, msg_text) msgbox.exec_() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/javascript.py0000644000175000017510000000632600000000000023535 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2016-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Utilities related to javascript interaction.""" import typing _InnerJsArgType = typing.Union[None, str, bool, int, float] _JsArgType = typing.Union[_InnerJsArgType, typing.Sequence[_InnerJsArgType]] def string_escape(text: str) -> str: """Escape values special to javascript in strings. With this we should be able to use something like: elem.evaluateJavaScript("this.value='{}'".format(string_escape(...))) And all values should work. """ # This is a list of tuples because order matters, and using OrderedDict # makes no sense because we don't actually need dict-like properties. replacements = ( ('\\', r'\\'), # First escape all literal \ signs as \\. ("'", r"\'"), # Then escape ' and " as \' and \". ('"', r'\"'), # (note it won't hurt when we escape the wrong one). ('\n', r'\n'), # We also need to escape newlines for some reason. ('\r', r'\r'), ('\x00', r'\x00'), ('\ufeff', r'\ufeff'), # http://stackoverflow.com/questions/2965293/ ('\u2028', r'\u2028'), ('\u2029', r'\u2029'), ) for orig, repl in replacements: text = text.replace(orig, repl) return text def to_js(arg: _JsArgType) -> str: """Convert the given argument so it's the equivalent in JS.""" if arg is None: return 'undefined' elif isinstance(arg, str): return '"{}"'.format(string_escape(arg)) elif isinstance(arg, bool): return str(arg).lower() elif isinstance(arg, (int, float)): return str(arg) elif isinstance(arg, list): return '[{}]'.format(', '.join(to_js(e) for e in arg)) else: raise TypeError("Don't know how to handle {!r} of type {}!".format( arg, type(arg).__name__)) def assemble(module: str, function: str, *args: _JsArgType) -> str: """Assemble a javascript file and a function call.""" js_args = ', '.join(to_js(arg) for arg in args) if module == 'window': parts = ['window', function] else: parts = ['window', '_qutebrowser', module, function] code = '"use strict";\n{}({});'.format('.'.join(parts), js_args) return code def wrap_global(name: str, *sources: str) -> str: """Wrap a script using window._qutebrowser.""" from qutebrowser.utils import jinja # circular import template = jinja.js_environment.get_template('global_wrapper.js') return template.render(code='\n'.join(sources), name=name) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/jinja.py0000644000175000017510000001326500000000000022462 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Utilities related to jinja2.""" import os import os.path import typing import functools import contextlib import html import jinja2 import jinja2.nodes from PyQt5.QtCore import QUrl from qutebrowser.utils import utils, urlutils, log, qtutils, javascript from qutebrowser.misc import debugcachestats html_fallback = """ Error while loading template

The %FILE% template could not be found!
Please check your qutebrowser installation

%ERROR%

""" class Loader(jinja2.BaseLoader): """Jinja loader which uses utils.read_file to load templates. Attributes: _subdir: The subdirectory to find templates in. """ def __init__(self, subdir: str) -> None: self._subdir = subdir def get_source( self, _env: jinja2.Environment, template: str ) -> typing.Tuple[str, str, typing.Callable[[], bool]]: path = os.path.join(self._subdir, template) try: source = utils.read_file(path) except OSError as e: source = html_fallback.replace("%ERROR%", html.escape(str(e))) source = source.replace("%FILE%", html.escape(template)) log.misc.exception("The {} template could not be loaded from {}" .format(template, path)) # Currently we don't implement auto-reloading, so we always return True # for up-to-date. return source, path, lambda: True class Environment(jinja2.Environment): """Our own jinja environment which is more strict.""" def __init__(self) -> None: super().__init__(loader=Loader('html'), autoescape=lambda _name: self._autoescape, undefined=jinja2.StrictUndefined) self.globals['resource_url'] = self._resource_url self.globals['file_url'] = urlutils.file_url self.globals['data_url'] = self._data_url self.globals['qcolor_to_qsscolor'] = qtutils.qcolor_to_qsscolor self.filters['js_string_escape'] = javascript.string_escape self._autoescape = True @contextlib.contextmanager def no_autoescape(self) -> typing.Iterator[None]: """Context manager to temporarily turn off autoescaping.""" self._autoescape = False yield self._autoescape = True def _resource_url(self, path: str) -> str: """Load images from a relative path (to qutebrowser). Arguments: path: The relative path to the image """ image = utils.resource_filename(path) url = QUrl.fromLocalFile(image) urlstr = url.toString(QUrl.FullyEncoded) # type: ignore return urlstr def _data_url(self, path: str) -> str: """Get a data: url for the broken qutebrowser logo.""" data = utils.read_file(path, binary=True) filename = utils.resource_filename(path) mimetype = utils.guess_mimetype(filename) return urlutils.data_url(mimetype, data).toString() def getattr(self, obj: typing.Any, attribute: str) -> typing.Any: """Override jinja's getattr() to be less clever. This means it doesn't fall back to __getitem__, and it doesn't hide AttributeError. """ return getattr(obj, attribute) def render(template: str, **kwargs: typing.Any) -> str: """Render the given template and pass the given arguments to it.""" return environment.get_template(template).render(**kwargs) environment = Environment() js_environment = jinja2.Environment(loader=Loader('javascript')) @debugcachestats.register() @functools.lru_cache() def template_config_variables(template: str) -> typing.FrozenSet[str]: """Return the config variables used in the template.""" unvisted_nodes = [environment.parse(template)] result = set() # type: typing.Set[str] while unvisted_nodes: node = unvisted_nodes.pop() if not isinstance(node, jinja2.nodes.Getattr): unvisted_nodes.extend(node.iter_child_nodes()) continue # List of attribute names in reverse order. # For example it's ['ab', 'c', 'd'] for 'conf.d.c.ab'. attrlist = [] # type: typing.List[str] while isinstance(node, jinja2.nodes.Getattr): attrlist.append(node.attr) # type: ignore node = node.node # type: ignore if isinstance(node, jinja2.nodes.Name): if node.name == 'conf': # type: ignore result.add('.'.join(reversed(attrlist))) # otherwise, the node is a Name node so it doesn't have any # child nodes else: unvisted_nodes.append(node) from qutebrowser.config import config for option in result: config.instance.ensure_has_opt(option) return frozenset(result) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/log.py0000644000175000017510000006170000000000000022145 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Loggers and utilities related to logging.""" import os import sys import html as pyhtml import logging import contextlib import collections import copy import faulthandler import traceback import warnings import json import inspect import typing import argparse from PyQt5 import QtCore # Optional imports try: import colorama except ImportError: colorama = None _log_inited = False _args = None COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'purple', 'cyan', 'white'] COLOR_ESCAPES = {color: '\033[{}m'.format(i) for i, color in enumerate(COLORS, start=30)} RESET_ESCAPE = '\033[0m' # Log formats to use. SIMPLE_FMT = ('{green}{asctime:8}{reset} {log_color}{levelname}{reset}: ' '{message}') EXTENDED_FMT = ('{green}{asctime:8}{reset} ' '{log_color}{levelname:8}{reset} ' '{cyan}{name:10} {module}:{funcName}:{lineno}{reset} ' '{log_color}{message}{reset}') EXTENDED_FMT_HTML = ( '' '
%(green)s%(asctime)-8s%(reset)s
' '
%(log_color)s%(levelname)-8s%(reset)s
' '%(cyan)s%(name)-10s' '
%(cyan)s%(module)s:%(funcName)s:%(lineno)s%(reset)s
' '
%(log_color)s%(message)s%(reset)s
' '' ) DATEFMT = '%H:%M:%S' LOG_COLORS = { 'VDEBUG': 'white', 'DEBUG': 'white', 'INFO': 'green', 'WARNING': 'yellow', 'ERROR': 'red', 'CRITICAL': 'red', } # We first monkey-patch logging to support our VDEBUG level before getting the # loggers. Based on http://stackoverflow.com/a/13638084 # mypy doesn't know about this, so we need to ignore it. VDEBUG_LEVEL = 9 logging.addLevelName(VDEBUG_LEVEL, 'VDEBUG') logging.VDEBUG = VDEBUG_LEVEL # type: ignore LOG_LEVELS = { 'VDEBUG': logging.VDEBUG, # type: ignore 'DEBUG': logging.DEBUG, 'INFO': logging.INFO, 'WARNING': logging.WARNING, 'ERROR': logging.ERROR, 'CRITICAL': logging.CRITICAL, } def vdebug(self: logging.Logger, msg: str, *args: typing.Any, **kwargs: typing.Any) -> None: """Log with a VDEBUG level. VDEBUG is used when a debug message is rather verbose, and probably of little use to the end user or for post-mortem debugging, i.e. the content probably won't change unless the code changes. """ if self.isEnabledFor(VDEBUG_LEVEL): # pylint: disable=protected-access self._log(VDEBUG_LEVEL, msg, args, **kwargs) # type: ignore # pylint: enable=protected-access logging.Logger.vdebug = vdebug # type: ignore # The different loggers used. statusbar = logging.getLogger('statusbar') completion = logging.getLogger('completion') destroy = logging.getLogger('destroy') modes = logging.getLogger('modes') webview = logging.getLogger('webview') mouse = logging.getLogger('mouse') misc = logging.getLogger('misc') url = logging.getLogger('url') procs = logging.getLogger('procs') commands = logging.getLogger('commands') init = logging.getLogger('init') signals = logging.getLogger('signals') hints = logging.getLogger('hints') keyboard = logging.getLogger('keyboard') downloads = logging.getLogger('downloads') js = logging.getLogger('js') # Javascript console messages qt = logging.getLogger('qt') # Warnings produced by Qt rfc6266 = logging.getLogger('rfc6266') ipc = logging.getLogger('ipc') shlexer = logging.getLogger('shlexer') save = logging.getLogger('save') message = logging.getLogger('message') config = logging.getLogger('config') sessions = logging.getLogger('sessions') webelem = logging.getLogger('webelem') prompt = logging.getLogger('prompt') network = logging.getLogger('network') sql = logging.getLogger('sql') greasemonkey = logging.getLogger('greasemonkey') extensions = logging.getLogger('extensions') LOGGER_NAMES = [ 'statusbar', 'completion', 'init', 'url', 'destroy', 'modes', 'webview', 'misc', 'mouse', 'procs', 'hints', 'keyboard', 'commands', 'signals', 'downloads', 'js', 'qt', 'rfc6266', 'ipc', 'shlexer', 'save', 'message', 'config', 'sessions', 'webelem', 'prompt', 'network', 'sql', 'greasemonkey', 'extensions', ] ram_handler = None # type: typing.Optional[RAMHandler] console_handler = None # type: typing.Optional[logging.Handler] console_filter = None def stub(suffix: str = '') -> None: """Show a STUB: message for the calling function.""" try: function = inspect.stack()[1][3] except IndexError: # pragma: no cover misc.exception("Failed to get stack") function = '' text = "STUB: {}".format(function) if suffix: text = '{} ({})'.format(text, suffix) misc.warning(text) def init_log(args: argparse.Namespace) -> None: """Init loggers based on the argparse namespace passed.""" level = args.loglevel.upper() try: numeric_level = getattr(logging, level) except AttributeError: raise ValueError("Invalid log level: {}".format(args.loglevel)) if numeric_level > logging.DEBUG and args.debug: numeric_level = logging.DEBUG console, ram = _init_handlers(numeric_level, args.color, args.force_color, args.json_logging, args.loglines) root = logging.getLogger() global console_filter if console is not None: if not args.logfilter: negate = False names = None elif args.logfilter.startswith('!'): negate = True names = args.logfilter[1:].split(',') else: negate = False names = args.logfilter.split(',') console_filter = LogFilter(names, negate) console.addFilter(console_filter) root.addHandler(console) if ram is not None: root.addHandler(ram) else: # If we add no handler, we shouldn't process non visible logs at all # # disable blocks the current level (while setHandler shows the current # level), so -1 to avoid blocking handled messages. logging.disable(numeric_level - 1) global _log_inited, _args _args = args root.setLevel(logging.NOTSET) logging.captureWarnings(True) _init_py_warnings() QtCore.qInstallMessageHandler(qt_message_handler) # type: ignore _log_inited = True @QtCore.pyqtSlot() def shutdown_log() -> None: QtCore.qInstallMessageHandler(None) def _init_py_warnings() -> None: """Initialize Python warning handling.""" assert _args is not None warnings.simplefilter('error' if 'werror' in _args.debug_flags else 'default') warnings.filterwarnings('ignore', module='pdb', category=ResourceWarning) # This happens in many qutebrowser dependencies... warnings.filterwarnings('ignore', category=DeprecationWarning, message=r"Using or importing the ABCs from " r"'collections' instead of from 'collections.abc' " r"is deprecated.*") @contextlib.contextmanager def disable_qt_msghandler() -> typing.Iterator[None]: """Contextmanager which temporarily disables the Qt message handler.""" old_handler = QtCore.qInstallMessageHandler(None) try: yield finally: QtCore.qInstallMessageHandler(old_handler) @contextlib.contextmanager def ignore_py_warnings(**kwargs: typing.Any) -> typing.Iterator[None]: """Contextmanager to temporarily disable certain Python warnings.""" warnings.filterwarnings('ignore', **kwargs) yield if _log_inited: _init_py_warnings() def _init_handlers( level: int, color: bool, force_color: bool, json_logging: bool, ram_capacity: int ) -> typing.Tuple[logging.StreamHandler, typing.Optional['RAMHandler']]: """Init log handlers. Args: level: The numeric logging level. color: Whether to use color if available. force_color: Force colored output. json_logging: Output log lines in JSON (this disables all colors). """ global ram_handler global console_handler console_fmt, ram_fmt, html_fmt, use_colorama = _init_formatters( level, color, force_color, json_logging) if sys.stderr is None: console_handler = None # type: ignore else: strip = False if force_color else None if use_colorama: stream = colorama.AnsiToWin32(sys.stderr, strip=strip) else: stream = sys.stderr console_handler = logging.StreamHandler(stream) console_handler.setLevel(level) console_handler.setFormatter(console_fmt) if ram_capacity == 0: ram_handler = None else: ram_handler = RAMHandler(capacity=ram_capacity) ram_handler.setLevel(logging.NOTSET) ram_handler.setFormatter(ram_fmt) ram_handler.html_formatter = html_fmt return console_handler, ram_handler def get_console_format(level: int) -> str: """Get the log format the console logger should use. Args: level: The numeric logging level. Return: Format of the requested level. """ return EXTENDED_FMT if level <= logging.DEBUG else SIMPLE_FMT def _init_formatters( level: int, color: bool, force_color: bool, json_logging: bool ) -> typing.Tuple[typing.Union['JSONFormatter', 'ColoredFormatter'], 'ColoredFormatter', 'HTMLFormatter', bool]: """Init log formatters. Args: level: The numeric logging level. color: Whether to use color if available. force_color: Force colored output. json_logging: Format lines as JSON (disables all color). Return: A (console_formatter, ram_formatter, use_colorama) tuple. console_formatter/ram_formatter: logging.Formatter instances. use_colorama: Whether to use colorama. """ console_fmt = get_console_format(level) ram_formatter = ColoredFormatter(EXTENDED_FMT, DATEFMT, '{', use_colors=False) html_formatter = HTMLFormatter(EXTENDED_FMT_HTML, DATEFMT, log_colors=LOG_COLORS) if sys.stderr is None: return None, ram_formatter, html_formatter, False # type: ignore if json_logging: json_formatter = JSONFormatter() return json_formatter, ram_formatter, html_formatter, False use_colorama = False color_supported = os.name == 'posix' or colorama if color_supported and (sys.stderr.isatty() or force_color) and color: use_colors = True if colorama and os.name != 'posix': use_colorama = True else: use_colors = False console_formatter = ColoredFormatter(console_fmt, DATEFMT, '{', use_colors=use_colors) return console_formatter, ram_formatter, html_formatter, use_colorama def change_console_formatter(level: int) -> None: """Change console formatter based on level. Args: level: The numeric logging level """ assert console_handler is not None old_formatter = typing.cast(ColoredFormatter, console_handler.formatter) console_fmt = get_console_format(level) console_formatter = ColoredFormatter(console_fmt, DATEFMT, '{', use_colors=old_formatter.use_colors) console_handler.setFormatter(console_formatter) def qt_message_handler(msg_type: QtCore.QtMsgType, context: QtCore.QMessageLogContext, msg: str) -> None: """Qt message handler to redirect qWarning etc. to the logging system. Args: QtMsgType msg_type: The level of the message. QMessageLogContext context: The source code location of the message. msg: The message text. """ # Mapping from Qt logging levels to the matching logging module levels. # Note we map critical to ERROR as it's actually "just" an error, and fatal # to critical. qt_to_logging = { QtCore.QtDebugMsg: logging.DEBUG, QtCore.QtWarningMsg: logging.WARNING, QtCore.QtCriticalMsg: logging.ERROR, QtCore.QtFatalMsg: logging.CRITICAL, } try: qt_to_logging[QtCore.QtInfoMsg] = logging.INFO except AttributeError: # While we don't support Qt < 5.5 anymore, logging still needs to work pass # Change levels of some well-known messages to debug so they don't get # shown to the user. # # If a message starts with any text in suppressed_msgs, it's not logged as # error. suppressed_msgs = [ # PNGs in Qt with broken color profile # https://bugreports.qt.io/browse/QTBUG-39788 ('libpng warning: iCCP: Not recognizing known sRGB profile that has ' 'been edited'), 'libpng warning: iCCP: known incorrect sRGB profile', # Hopefully harmless warning 'OpenType support missing for script ', # Error if a QNetworkReply gets two different errors set. Harmless Qt # bug on some pages. # https://bugreports.qt.io/browse/QTBUG-30298 ('QNetworkReplyImplPrivate::error: Internal problem, this method must ' 'only be called once.'), # Sometimes indicates missing text, but most of the time harmless 'load glyph failed ', # Harmless, see https://bugreports.qt.io/browse/QTBUG-42479 ('content-type missing in HTTP POST, defaulting to ' 'application/x-www-form-urlencoded. ' 'Use QNetworkRequest::setHeader() to fix this problem.'), # https://bugreports.qt.io/browse/QTBUG-43118 'Using blocking call!', # Hopefully harmless ('"Method "GetAll" with signature "s" on interface ' '"org.freedesktop.DBus.Properties" doesn\'t exist'), ('"Method \\"GetAll\\" with signature \\"s\\" on interface ' '\\"org.freedesktop.DBus.Properties\\" doesn\'t exist\\n"'), 'WOFF support requires QtWebKit to be built with zlib support.', # Weird Enlightment/GTK X extensions 'QXcbWindow: Unhandled client message: "_E_', 'QXcbWindow: Unhandled client message: "_ECORE_', 'QXcbWindow: Unhandled client message: "_GTK_', # Happens on AppVeyor CI 'SetProcessDpiAwareness failed:', # https://bugreports.qt.io/browse/QTBUG-49174 ('QObject::connect: Cannot connect (null)::stateChanged(' 'QNetworkSession::State) to ' 'QNetworkReplyHttpImpl::_q_networkSessionStateChanged(' 'QNetworkSession::State)'), # https://bugreports.qt.io/browse/QTBUG-53989 ("Image of format '' blocked because it is not considered safe. If " "you are sure it is safe to do so, you can white-list the format by " "setting the environment variable QTWEBKIT_IMAGEFORMAT_WHITELIST="), # Installing Qt from the installer may cause it looking for SSL3 or # OpenSSL 1.0 which may not be available on the system "QSslSocket: cannot resolve ", "QSslSocket: cannot call unresolved function ", # When enabling debugging with QtWebEngine ("Remote debugging server started successfully. Try pointing a " "Chromium-based browser to "), # https://github.com/qutebrowser/qutebrowser/issues/1287 "QXcbClipboard: SelectionRequest too old", # https://github.com/qutebrowser/qutebrowser/issues/2071 'QXcbWindow: Unhandled client message: ""', # https://codereview.qt-project.org/176831 "QObject::disconnect: Unexpected null parameter", # https://bugreports.qt.io/browse/QTBUG-76391 "Attribute Qt::AA_ShareOpenGLContexts must be set before " "QCoreApplication is created.", ] # not using utils.is_mac here, because we can't be sure we can successfully # import the utils module here. if sys.platform == 'darwin': suppressed_msgs += [ # https://bugreports.qt.io/browse/QTBUG-47154 ('virtual void QSslSocketBackendPrivate::transmit() SSLRead ' 'failed with: -9805'), ] if not msg: msg = "Logged empty message!" if any(msg.strip().startswith(pattern) for pattern in suppressed_msgs): level = logging.DEBUG else: level = qt_to_logging[msg_type] if context.function is None: func = 'none' # type: ignore elif ':' in context.function: func = '"{}"'.format(context.function) else: func = context.function if (context.category is None or # type: ignore context.category == 'default'): name = 'qt' else: name = 'qt-' + context.category if msg.splitlines()[0] == ('This application failed to start because it ' 'could not find or load the Qt platform plugin ' '"xcb".'): # Handle this message specially. msg += ("\n\nOn Archlinux, this should fix the problem:\n" " pacman -S libxkbcommon-x11") faulthandler.disable() assert _args is not None if _args.debug: stack = ''.join(traceback.format_stack()) # type: typing.Optional[str] else: stack = None record = qt.makeRecord(name, level, context.file, context.line, msg, (), None, func, sinfo=stack) qt.handle(record) @contextlib.contextmanager def hide_qt_warning(pattern: str, logger: str = 'qt') -> typing.Iterator[None]: """Hide Qt warnings matching the given regex.""" log_filter = QtWarningFilter(pattern) logger_obj = logging.getLogger(logger) logger_obj.addFilter(log_filter) try: yield finally: logger_obj.removeFilter(log_filter) class QtWarningFilter(logging.Filter): """Filter to filter Qt warnings. Attributes: _pattern: The start of the message. """ def __init__(self, pattern: str) -> None: super().__init__() self._pattern = pattern def filter(self, record: logging.LogRecord) -> bool: """Determine if the specified record is to be logged.""" do_log = not record.msg.strip().startswith(self._pattern) return do_log class LogFilter(logging.Filter): """Filter to filter log records based on the commandline argument. The default Filter only supports one name to show - we support a comma-separated list instead. Attributes: names: A list of record names to filter. negated: Whether names is a list of records to log or to suppress. """ def __init__(self, names: typing.Optional[typing.Iterable[str]], negate: bool = False) -> None: super().__init__() self.names = names self.negated = negate def filter(self, record: logging.LogRecord) -> bool: """Determine if the specified record is to be logged.""" if self.names is None: return True if record.levelno > logging.DEBUG: # More important than DEBUG, so we won't filter at all return True for name in self.names: if record.name == name: return not self.negated elif not record.name.startswith(name): continue elif record.name[len(name)] == '.': return not self.negated return self.negated class RAMHandler(logging.Handler): """Logging handler which keeps the messages in a deque in RAM. Loosely based on logging.BufferingHandler which is unsuitable because it uses a simple list rather than a deque. Attributes: _data: A deque containing the logging records. """ def __init__(self, capacity: int) -> None: super().__init__() self.html_formatter = None # type: typing.Optional[HTMLFormatter] if capacity != -1: self._data = collections.deque( maxlen=capacity ) # type: typing.MutableSequence[logging.LogRecord] else: self._data = collections.deque() def emit(self, record: logging.LogRecord) -> None: if record.levelno >= logging.DEBUG: # We don't log VDEBUG to RAM. self._data.append(record) def dump_log(self, html: bool = False, level: str = 'vdebug') -> str: """Dump the complete formatted log data as string. FIXME: We should do all the HTML formatter via jinja2. (probably obsolete when moving to a widget for logging, https://github.com/qutebrowser/qutebrowser/issues/34 """ minlevel = LOG_LEVELS.get(level.upper(), VDEBUG_LEVEL) if html: assert self.html_formatter is not None fmt = self.html_formatter.format else: fmt = self.format self.acquire() try: lines = [fmt(record) for record in self._data if record.levelno >= minlevel] finally: self.release() return '\n'.join(lines) def change_log_capacity(self, capacity: int) -> None: self._data = collections.deque(self._data, maxlen=capacity) class ColoredFormatter(logging.Formatter): """Logging formatter to output colored logs. Attributes: use_colors: Whether to do colored logging or not. """ def __init__(self, fmt: str, datefmt: str, style: str, *, use_colors: bool) -> None: super().__init__(fmt, datefmt, style) self.use_colors = use_colors def format(self, record: logging.LogRecord) -> str: if self.use_colors: color_dict = dict(COLOR_ESCAPES) color_dict['reset'] = RESET_ESCAPE log_color = LOG_COLORS[record.levelname] color_dict['log_color'] = COLOR_ESCAPES[log_color] else: color_dict = {color: '' for color in COLOR_ESCAPES} color_dict['reset'] = '' color_dict['log_color'] = '' record.__dict__.update(color_dict) return super().format(record) class HTMLFormatter(logging.Formatter): """Formatter for HTML-colored log messages. Attributes: _log_colors: The colors to use for logging levels. _colordict: The colordict passed to the logger. """ def __init__(self, fmt: str, datefmt: str, log_colors: typing.Mapping[str, str]) -> None: """Constructor. Args: fmt: The format string to use. datefmt: The date format to use. log_colors: The colors to use for logging levels. """ super().__init__(fmt, datefmt) self._log_colors = log_colors # type: typing.Mapping[str, str] self._colordict = {} # type: typing.Mapping[str, str] # We could solve this nicer by using CSS, but for this simple case this # works. for color in COLORS: self._colordict[color] = ''.format(color) self._colordict['reset'] = '' def format(self, record: logging.LogRecord) -> str: record_clone = copy.copy(record) record_clone.__dict__.update(self._colordict) if record_clone.levelname in self._log_colors: color = self._log_colors[record_clone.levelname] record_clone.log_color = self._colordict[color] # type: ignore else: record_clone.log_color = '' # type: ignore for field in ['msg', 'filename', 'funcName', 'levelname', 'module', 'name', 'pathname', 'processName', 'threadName']: data = str(getattr(record_clone, field)) setattr(record_clone, field, pyhtml.escape(data)) msg = super().format(record_clone) if not msg.endswith(self._colordict['reset']): msg += self._colordict['reset'] return msg def formatTime(self, record: logging.LogRecord, datefmt: str = None) -> str: out = super().formatTime(record, datefmt) return pyhtml.escape(out) class JSONFormatter(logging.Formatter): """Formatter for JSON-encoded log messages.""" def format(self, record: logging.LogRecord) -> str: obj = {} for field in ['created', 'msecs', 'levelname', 'name', 'module', 'funcName', 'lineno', 'levelno']: obj[field] = getattr(record, field) obj['message'] = record.getMessage() if record.exc_info is not None: obj['traceback'] = super().formatException(record.exc_info) return json.dumps(obj) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/message.py0000644000175000017510000002404100000000000023005 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . # Because every method needs to have a log_stack argument # pylint: disable=unused-argument """Message singleton so we don't have to define unneeded signals.""" import traceback import typing from PyQt5.QtCore import pyqtSignal, QObject, QUrl from qutebrowser.utils import usertypes, log, utils def _log_stack(typ: str, stack: str) -> None: """Log the given message stacktrace. Args: typ: The type of the message. stack: An optional stacktrace. """ lines = stack.splitlines() stack_text = '\n'.join(line.rstrip() for line in lines) log.message.debug("Stack for {} message:\n{}".format(typ, stack_text)) def error(message: str, *, stack: str = None, replace: bool = False) -> None: """Display an error message. Args: message: The message to show. stack: The stack trace to show (if any). replace: Replace existing messages which are still being shown. """ if stack is None: stack = ''.join(traceback.format_stack()) typ = 'error' else: typ = 'error (from exception)' _log_stack(typ, stack) log.message.error(message) global_bridge.show(usertypes.MessageLevel.error, message, replace) def warning(message: str, *, replace: bool = False) -> None: """Display a warning message. Args: message: The message to show. replace: Replace existing messages which are still being shown. """ _log_stack('warning', ''.join(traceback.format_stack())) log.message.warning(message) global_bridge.show(usertypes.MessageLevel.warning, message, replace) def info(message: str, *, replace: bool = False) -> None: """Display an info message. Args: message: The message to show. replace: Replace existing messages which are still being shown. """ log.message.info(message) global_bridge.show(usertypes.MessageLevel.info, message, replace) def _build_question(title: str, text: str = None, *, mode: usertypes.PromptMode, default: typing.Union[None, bool, str] = None, abort_on: typing.Iterable[pyqtSignal] = (), url: QUrl = None, option: bool = None) -> usertypes.Question: """Common function for ask/ask_async.""" question = usertypes.Question() question.title = title question.text = text question.mode = mode question.default = default question.url = url if option is not None: if mode != usertypes.PromptMode.yesno: raise ValueError("Can only 'option' with PromptMode.yesno") if url is None: raise ValueError("Need 'url' given when 'option' is given") question.option = option for sig in abort_on: sig.connect(question.abort) return question def ask(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: """Ask a modular question in the statusbar (blocking). Args: message: The message to display to the user. mode: A PromptMode. default: The default value to display. text: Additional text to show option: The option for always/never question answers. Only available with PromptMode.yesno. abort_on: A list of signals which abort the question if emitted. Return: The answer the user gave or None if the prompt was cancelled. """ question = _build_question(*args, **kwargs) # pylint: disable=missing-kwoa global_bridge.ask(question, blocking=True) answer = question.answer question.deleteLater() return answer def ask_async(title: str, mode: usertypes.PromptMode, handler: typing.Callable[[typing.Any], None], **kwargs: typing.Any) -> None: """Ask an async question in the statusbar. Args: message: The message to display to the user. mode: A PromptMode. handler: The function to get called with the answer as argument. default: The default value to display. text: Additional text to show. """ question = _build_question(title, mode=mode, **kwargs) question.answered.connect(handler) question.completed.connect(question.deleteLater) global_bridge.ask(question, blocking=False) _ActionType = typing.Callable[[], typing.Any] def confirm_async(*, yes_action: _ActionType, no_action: _ActionType = None, cancel_action: _ActionType = None, **kwargs: typing.Any) -> usertypes.Question: """Ask a yes/no question to the user and execute the given actions. Args: message: The message to display to the user. yes_action: Callable to be called when the user answered yes. no_action: Callable to be called when the user answered no. cancel_action: Callable to be called when the user cancelled the question. default: True/False to set a default value, or None. option: The option for always/never question answers. text: Additional text to show. Return: The question object. """ kwargs['mode'] = usertypes.PromptMode.yesno question = _build_question(**kwargs) # pylint: disable=missing-kwoa question.answered_yes.connect(yes_action) if no_action is not None: question.answered_no.connect(no_action) if cancel_action is not None: question.cancelled.connect(cancel_action) question.completed.connect(question.deleteLater) global_bridge.ask(question, blocking=False) return question class GlobalMessageBridge(QObject): """Global (not per-window) message bridge for errors/infos/warnings. Attributes: _connected: Whether a slot is connected and we can show messages. _cache: Messages shown while we were not connected. Signals: show_message: Show a message arg 0: A MessageLevel member arg 1: The text to show arg 2: Whether to replace other messages with replace=True. prompt_done: Emitted when a prompt was answered somewhere. ask_question: Ask a question to the user. arg 0: The Question object to ask. arg 1: Whether to block (True) or ask async (False). IMPORTANT: Slots need to be connected to this signal via a Qt.DirectConnection! mode_left: Emitted when a keymode was left in any window. """ show_message = pyqtSignal(usertypes.MessageLevel, str, bool) prompt_done = pyqtSignal(usertypes.KeyMode) ask_question = pyqtSignal(usertypes.Question, bool) mode_left = pyqtSignal(usertypes.KeyMode) clear_messages = pyqtSignal() def __init__(self, parent: QObject = None) -> None: super().__init__(parent) self._connected = False self._cache = [ ] # type: typing.List[typing.Tuple[usertypes.MessageLevel, str, bool]] def ask(self, question: usertypes.Question, blocking: bool, *, log_stack: bool = False) -> None: """Ask a question to the user. Note this method doesn't return the answer, it only blocks. The caller needs to construct a Question object and get the answer. Args: question: A Question object. blocking: Whether to return immediately or wait until the question is answered. log_stack: ignored """ self.ask_question.emit(question, blocking) def show(self, level: usertypes.MessageLevel, text: str, replace: bool = False) -> None: """Show the given message.""" if self._connected: self.show_message.emit(level, text, replace) else: self._cache.append((level, text, replace)) def flush(self) -> None: """Flush messages which accumulated while no handler was connected. This is so we don't miss messages shown during some early init phase. It needs to be called once the show_message signal is connected. """ self._connected = True for args in self._cache: self.show(*args) self._cache = [] class MessageBridge(QObject): """Bridge for messages to be shown in the statusbar. Signals: s_set_text: Set a persistent text in the statusbar. arg: The text to set. s_maybe_reset_text: Reset the text if it hasn't been changed yet. arg: The expected text. """ s_set_text = pyqtSignal(str) s_maybe_reset_text = pyqtSignal(str) def __repr__(self) -> str: return utils.get_repr(self) def set_text(self, text: str, *, log_stack: bool = False) -> None: """Set the normal text of the statusbar. Args: text: The text to set. log_stack: ignored """ text = str(text) log.message.debug(text) self.s_set_text.emit(text) def maybe_reset_text(self, text: str, *, log_stack: bool = False) -> None: """Reset the text in the statusbar if it matches an expected text. Args: text: The expected text. log_stack: ignored """ self.s_maybe_reset_text.emit(str(text)) global_bridge = GlobalMessageBridge() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581688282.0 qutebrowser-1.10.1/qutebrowser/utils/objreg.py0000644000175000017510000002647400000000000022645 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """The global object registry and related utility functions.""" import collections import functools import typing from PyQt5.QtCore import QObject, QTimer from PyQt5.QtWidgets import QApplication from PyQt5.QtWidgets import QWidget # pylint: disable=unused-import from qutebrowser.utils import log, usertypes if typing.TYPE_CHECKING: from qutebrowser.mainwindow import mainwindow _WindowTab = typing.Union[str, int, None] class RegistryUnavailableError(Exception): """Exception raised when a certain registry does not exist yet.""" class NoWindow(Exception): """Exception raised by last_window if no window is available.""" class CommandOnlyError(Exception): """Raised when an object is requested which is used for commands only.""" _IndexType = typing.Union[str, int] class ObjectRegistry(collections.UserDict): """A registry of long-living objects in qutebrowser. Inspired by the eric IDE code (E5Gui/E5Application.py). Attributes: _partial_objs: A dictionary of the connected partial objects. command_only: Objects which are only registered for commands. """ def __init__(self) -> None: super().__init__() self._partial_objs = { } # type: typing.MutableMapping[_IndexType, typing.Callable[[], None]] self.command_only = [] # type: typing.MutableSequence[str] def __setitem__(self, name: _IndexType, obj: typing.Any) -> None: """Register an object in the object registry. Sets a slot to remove QObjects when they are destroyed. """ if name is None: raise TypeError("Registering '{}' with name 'None'!".format(obj)) if obj is None: raise TypeError("Registering object None with name '{}'!".format( name)) self._disconnect_destroyed(name) if isinstance(obj, QObject): func = functools.partial(self.on_destroyed, name) obj.destroyed.connect(func) # type: ignore self._partial_objs[name] = func super().__setitem__(name, obj) def __delitem__(self, name: str) -> None: """Extend __delitem__ to disconnect the destroyed signal.""" self._disconnect_destroyed(name) super().__delitem__(name) def _disconnect_destroyed(self, name: _IndexType) -> None: """Disconnect the destroyed slot if it was connected.""" try: partial_objs = self._partial_objs except AttributeError: # This sometimes seems to happen on Travis during # test_history.test_adding_item_during_async_read # and I have no idea why... return if name in partial_objs: func = partial_objs[name] try: self[name].destroyed.disconnect(func) except RuntimeError: # If C++ has deleted the object, the slot is already # disconnected. pass del partial_objs[name] def on_destroyed(self, name: str) -> None: """Schedule removing of a destroyed QObject. We don't remove the destroyed object immediately because it might still be destroying its children, which might still use the object registry. """ log.destroy.debug("schedule removal: {}".format(name)) QTimer.singleShot(0, functools.partial(self._on_destroyed, name)) def _on_destroyed(self, name: str) -> None: """Remove a destroyed QObject.""" log.destroy.debug("removed: {}".format(name)) if not hasattr(self, 'data'): # This sometimes seems to happen on Travis during # test_history.test_adding_item_during_async_read # and I have no idea why... return try: del self[name] del self._partial_objs[name] except KeyError: pass def dump_objects(self) -> typing.Sequence[str]: """Dump all objects as a list of strings.""" lines = [] for name, obj in self.data.items(): try: obj_repr = repr(obj) except RuntimeError: # Underlying object deleted probably obj_repr = '' suffix = (" (for commands only)" if name in self.command_only else "") lines.append("{}: {}{}".format(name, obj_repr, suffix)) return lines # The registry for global objects global_registry = ObjectRegistry() # The window registry. window_registry = ObjectRegistry() def _get_tab_registry(win_id: _WindowTab, tab_id: _WindowTab) -> ObjectRegistry: """Get the registry of a tab.""" if tab_id is None: raise ValueError("Got tab_id None (win_id {})".format(win_id)) if tab_id == 'current' and win_id is None: window = QApplication.activeWindow() # type: typing.Optional[QWidget] if window is None or not hasattr(window, 'win_id'): raise RegistryUnavailableError('tab') win_id = window.win_id elif win_id is not None: window = window_registry[win_id] else: raise TypeError("window is None with scope tab!") if tab_id == 'current': tabbed_browser = get('tabbed-browser', scope='window', window=win_id) tab = tabbed_browser.widget.currentWidget() if tab is None: raise RegistryUnavailableError('window') tab_id = tab.tab_id tab_registry = get('tab-registry', scope='window', window=win_id) try: return tab_registry[tab_id].registry except AttributeError: raise RegistryUnavailableError('tab') def _get_window_registry(window: _WindowTab) -> ObjectRegistry: """Get the registry of a window.""" if window is None: raise TypeError("window is None with scope window!") try: if window == 'current': win = QApplication.activeWindow() # type: typing.Optional[QWidget] elif window == 'last-focused': win = last_focused_window() else: win = window_registry[window] except (KeyError, NoWindow): win = None if win is None: raise RegistryUnavailableError('window') try: return win.registry except AttributeError: raise RegistryUnavailableError('window') def _get_registry(scope: str, window: _WindowTab = None, tab: _WindowTab = None) -> ObjectRegistry: """Get the correct registry for a given scope.""" if window is not None and scope not in ['window', 'tab']: raise TypeError("window is set with scope {}".format(scope)) if tab is not None and scope != 'tab': raise TypeError("tab is set with scope {}".format(scope)) if scope == 'global': return global_registry elif scope == 'tab': return _get_tab_registry(window, tab) elif scope == 'window': return _get_window_registry(window) else: raise ValueError("Invalid scope '{}'!".format(scope)) def get(name: str, default: typing.Any = usertypes.UNSET, scope: str = 'global', window: _WindowTab = None, tab: _WindowTab = None, from_command: bool = False) -> typing.Any: """Helper function to get an object. Args: default: A default to return if the object does not exist. """ reg = _get_registry(scope, window, tab) if name in reg.command_only and not from_command: raise CommandOnlyError("{} is only registered for commands" .format(name)) try: return reg[name] except KeyError: if default is not usertypes.UNSET: return default else: raise def register(name: str, obj: typing.Any, update: bool = False, scope: str = None, registry: ObjectRegistry = None, window: _WindowTab = None, tab: _WindowTab = None, command_only: bool = False) -> None: """Helper function to register an object. Args: name: The name the object will be registered as. obj: The object to register. update: If True, allows to update an already registered object. """ if scope is not None and registry is not None: raise ValueError("scope ({}) and registry ({}) can't be given at the " "same time!".format(scope, registry)) if registry is not None: reg = registry else: if scope is None: scope = 'global' reg = _get_registry(scope, window, tab) if not update and name in reg: raise KeyError("Object '{}' is already registered ({})!".format( name, repr(reg[name]))) reg[name] = obj if command_only: reg.command_only.append(name) def delete(name: str, scope: str = 'global', window: _WindowTab = None, tab: _WindowTab = None) -> None: """Helper function to unregister an object.""" reg = _get_registry(scope, window, tab) del reg[name] def dump_objects() -> typing.Sequence[str]: """Get all registered objects in all registries as a string.""" blocks = [] lines = [] blocks.append(('global', global_registry.dump_objects())) for win_id in window_registry: registry = _get_registry('window', window=win_id) blocks.append(('window-{}'.format(win_id), registry.dump_objects())) tab_registry = get('tab-registry', scope='window', window=win_id) for tab_id, tab in tab_registry.items(): dump = tab.registry.dump_objects() data = [' ' + line for line in dump] blocks.append((' tab-{}'.format(tab_id), data)) for name, block_data in blocks: lines.append("") lines.append("{} object registry - {} objects:".format( name, len(block_data))) for line in block_data: lines.append(" {}".format(line)) return lines def last_visible_window() -> 'mainwindow.MainWindow': """Get the last visible window, or the last focused window if none.""" try: return get('last-visible-main-window') except KeyError: return last_focused_window() def last_focused_window() -> 'mainwindow.MainWindow': """Get the last focused window, or the last window if none.""" try: return get('last-focused-main-window') except KeyError: return window_by_index(-1) def window_by_index(idx: int) -> 'mainwindow.MainWindow': """Get the Nth opened window object.""" if not window_registry: raise NoWindow() key = sorted(window_registry)[idx] return window_registry[key] ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/qtutils.py0000644000175000017510000003143300000000000023071 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . # FIXME:typing Can we have less "# type: ignore" in here? """Misc. utilities related to Qt. Module attributes: MAXVALS: A dictionary of C/Qt types (as string) mapped to their maximum value. MINVALS: A dictionary of C/Qt types (as string) mapped to their minimum value. MAX_WORLD_ID: The highest world ID allowed in this version of QtWebEngine. """ import io import operator import contextlib import typing import pkg_resources from PyQt5.QtCore import (qVersion, QEventLoop, QDataStream, QByteArray, QIODevice, QSaveFile, QT_VERSION_STR, PYQT_VERSION_STR, QFileDevice, QObject) from PyQt5.QtGui import QColor from PyQt5.QtWidgets import QApplication try: from PyQt5.QtWebKit import qWebKitVersion except ImportError: # pragma: no cover qWebKitVersion = None # type: ignore # noqa: N816 from qutebrowser.misc import objects from qutebrowser.utils import usertypes MAXVALS = { 'int': 2 ** 31 - 1, 'int64': 2 ** 63 - 1, } MINVALS = { 'int': -(2 ** 31), 'int64': -(2 ** 63), } class QtOSError(OSError): """An OSError triggered by a QFileDevice. Attributes: qt_errno: The error attribute of the given QFileDevice, if applicable. """ def __init__(self, dev: QFileDevice, msg: str = None) -> None: if msg is None: msg = dev.errorString() super().__init__(msg) self.qt_errno = None # type: typing.Optional[QFileDevice.FileError] try: self.qt_errno = dev.error() except AttributeError: pass def version_check(version: str, exact: bool = False, compiled: bool = True) -> bool: """Check if the Qt runtime version is the version supplied or newer. Args: version: The version to check against. exact: if given, check with == instead of >= compiled: Set to False to not check the compiled version. """ if compiled and exact: raise ValueError("Can't use compiled=True with exact=True!") parsed = pkg_resources.parse_version(version) op = operator.eq if exact else operator.ge result = op(pkg_resources.parse_version(qVersion()), parsed) if compiled and result: # qVersion() ==/>= parsed, now check if QT_VERSION_STR ==/>= parsed. result = op(pkg_resources.parse_version(QT_VERSION_STR), parsed) if compiled and result: # FInally, check PYQT_VERSION_STR as well. result = op(pkg_resources.parse_version(PYQT_VERSION_STR), parsed) return result # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-69904 MAX_WORLD_ID = 256 if version_check('5.11.2') else 11 def is_new_qtwebkit() -> bool: """Check if the given version is a new QtWebKit.""" assert qWebKitVersion is not None return (pkg_resources.parse_version(qWebKitVersion()) > pkg_resources.parse_version('538.1')) def is_single_process() -> bool: """Check whether QtWebEngine is running in single-process mode.""" if objects.backend == usertypes.Backend.QtWebKit: return False args = QApplication.instance().arguments() return '--single-process' in args def check_overflow(arg: int, ctype: str, fatal: bool = True) -> int: """Check if the given argument is in bounds for the given type. Args: arg: The argument to check ctype: The C/Qt type to check as a string. fatal: Whether to raise exceptions (True) or truncate values (False) Return The truncated argument if fatal=False The original argument if it's in bounds. """ maxval = MAXVALS[ctype] minval = MINVALS[ctype] if arg > maxval: if fatal: raise OverflowError(arg) return maxval elif arg < minval: if fatal: raise OverflowError(arg) return minval else: return arg def ensure_valid(obj: QObject) -> None: """Ensure a Qt object with an .isValid() method is valid.""" if not obj.isValid(): raise QtValueError(obj) def check_qdatastream(stream: QDataStream) -> None: """Check the status of a QDataStream and raise OSError if it's not ok.""" status_to_str = { QDataStream.Ok: "The data stream is operating normally.", QDataStream.ReadPastEnd: ("The data stream has read past the end of " "the data in the underlying device."), QDataStream.ReadCorruptData: "The data stream has read corrupt data.", QDataStream.WriteFailed: ("The data stream cannot write to the " "underlying device."), } if stream.status() != QDataStream.Ok: raise OSError(status_to_str[stream.status()]) def serialize(obj: QObject) -> QByteArray: """Serialize an object into a QByteArray.""" data = QByteArray() stream = QDataStream(data, QIODevice.WriteOnly) serialize_stream(stream, obj) return data def deserialize(data: QByteArray, obj: QObject) -> None: """Deserialize an object from a QByteArray.""" stream = QDataStream(data, QIODevice.ReadOnly) deserialize_stream(stream, obj) def serialize_stream(stream: QDataStream, obj: QObject) -> None: """Serialize an object into a QDataStream.""" check_qdatastream(stream) stream << obj # pylint: disable=pointless-statement check_qdatastream(stream) def deserialize_stream(stream: QDataStream, obj: QObject) -> None: """Deserialize a QDataStream into an object.""" check_qdatastream(stream) stream >> obj # pylint: disable=pointless-statement check_qdatastream(stream) @contextlib.contextmanager def savefile_open( filename: str, binary: bool = False, encoding: str = 'utf-8' ) -> typing.Iterator[typing.Union['PyQIODevice', io.TextIOWrapper]]: """Context manager to easily use a QSaveFile.""" f = QSaveFile(filename) cancelled = False try: open_ok = f.open(QIODevice.WriteOnly) if not open_ok: raise QtOSError(f) if binary: new_f = PyQIODevice( f) # type: typing.Union[PyQIODevice, io.TextIOWrapper] else: new_f = io.TextIOWrapper(PyQIODevice(f), # type: ignore encoding=encoding) yield new_f new_f.flush() except: f.cancelWriting() cancelled = True raise finally: commit_ok = f.commit() if not commit_ok and not cancelled: raise QtOSError(f, msg="Commit failed!") def qcolor_to_qsscolor(c: QColor) -> str: """Convert a QColor to a string that can be used in a QStyleSheet.""" ensure_valid(c) return "rgba({}, {}, {}, {})".format( c.red(), c.green(), c.blue(), c.alpha()) class PyQIODevice(io.BufferedIOBase): """Wrapper for a QIODevice which provides a python interface. Attributes: dev: The underlying QIODevice. """ def __init__(self, dev: QIODevice) -> None: super().__init__() self.dev = dev def __len__(self) -> int: return self.dev.size() def _check_open(self) -> None: """Check if the device is open, raise ValueError if not.""" if not self.dev.isOpen(): raise ValueError("IO operation on closed device!") def _check_random(self) -> None: """Check if the device supports random access, raise OSError if not.""" if not self.seekable(): raise OSError("Random access not allowed!") def _check_readable(self) -> None: """Check if the device is readable, raise OSError if not.""" if not self.dev.isReadable(): raise OSError("Trying to read unreadable file!") def _check_writable(self) -> None: """Check if the device is writable, raise OSError if not.""" if not self.writable(): raise OSError("Trying to write to unwritable file!") def open(self, mode: QIODevice.OpenMode) -> contextlib.closing: """Open the underlying device and ensure opening succeeded. Raises OSError if opening failed. Args: mode: QIODevice::OpenMode flags. Return: A contextlib.closing() object so this can be used as contextmanager. """ ok = self.dev.open(mode) if not ok: raise QtOSError(self.dev) return contextlib.closing(self) def close(self) -> None: """Close the underlying device.""" self.dev.close() def fileno(self) -> int: raise io.UnsupportedOperation def seek(self, offset: int, whence: int = io.SEEK_SET) -> int: self._check_open() self._check_random() if whence == io.SEEK_SET: ok = self.dev.seek(offset) elif whence == io.SEEK_CUR: ok = self.dev.seek(self.tell() + offset) elif whence == io.SEEK_END: ok = self.dev.seek(len(self) + offset) else: raise io.UnsupportedOperation("whence = {} is not " "supported!".format(whence)) if not ok: raise QtOSError(self.dev, msg="seek failed!") return self.dev.pos() def truncate(self, size: int = None) -> int: raise io.UnsupportedOperation @property def closed(self) -> bool: return not self.dev.isOpen() def flush(self) -> None: self._check_open() self.dev.waitForBytesWritten(-1) def isatty(self) -> bool: self._check_open() return False def readable(self) -> bool: return self.dev.isReadable() def readline(self, size: int = -1) -> QByteArray: self._check_open() self._check_readable() if size < 0: qt_size = 0 # no maximum size elif size == 0: return QByteArray() else: qt_size = size + 1 # Qt also counts the NUL byte if self.dev.canReadLine(): buf = self.dev.readLine(qt_size) else: if size < 0: buf = self.dev.readAll() else: buf = self.dev.read(size) if buf is None: raise QtOSError(self.dev) return buf # type: ignore def seekable(self) -> bool: return not self.dev.isSequential() def tell(self) -> int: self._check_open() self._check_random() return self.dev.pos() def writable(self) -> bool: return self.dev.isWritable() def write(self, data: str) -> int: # type: ignore self._check_open() self._check_writable() num = self.dev.write(data) # type: ignore if num == -1 or num < len(data): raise QtOSError(self.dev) return num def read(self, size: typing.Optional[int] = None) -> QByteArray: self._check_open() self._check_readable() if size in [None, -1]: buf = self.dev.readAll() else: buf = self.dev.read(size) # type: ignore if buf is None: raise QtOSError(self.dev) return buf class QtValueError(ValueError): """Exception which gets raised by ensure_valid.""" def __init__(self, obj: QObject) -> None: try: self.reason = obj.errorString() except AttributeError: self.reason = None err = "{} is not valid".format(obj) if self.reason: err += ": {}".format(self.reason) super().__init__(err) class EventLoop(QEventLoop): """A thin wrapper around QEventLoop. Raises an exception when doing exec_() multiple times. """ def __init__(self, parent: QObject = None) -> None: super().__init__(parent) self._executing = False def exec_( self, flags: QEventLoop.ProcessEventsFlag = QEventLoop.AllEvents ) -> int: """Override exec_ to raise an exception when re-running.""" if self._executing: raise AssertionError("Eventloop is already running!") self._executing = True status = super().exec_(flags) # type: ignore self._executing = False return status ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/standarddir.py0000644000175000017510000003225200000000000023663 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Utilities to get and initialize data/config paths.""" import os import os.path import sys import shutil import contextlib import enum import argparse import typing from PyQt5.QtCore import QStandardPaths from PyQt5.QtWidgets import QApplication from qutebrowser.utils import log, debug, message, utils # The cached locations _locations = {} class _Location(enum.Enum): """A key for _locations.""" config = 1 auto_config = 2 data = 3 system_data = 4 cache = 5 download = 6 runtime = 7 config_py = 8 APPNAME = 'qutebrowser' class EmptyValueError(Exception): """Error raised when QStandardPaths returns an empty value.""" @contextlib.contextmanager def _unset_organization() -> typing.Iterator[None]: """Temporarily unset QApplication.organizationName(). This is primarily needed in config.py. """ qapp = QApplication.instance() if qapp is not None: orgname = qapp.organizationName() qapp.setOrganizationName(None) # type: ignore try: yield finally: if qapp is not None: qapp.setOrganizationName(orgname) def _init_config(args: typing.Optional[argparse.Namespace]) -> None: """Initialize the location for configs.""" typ = QStandardPaths.ConfigLocation path = _from_args(typ, args) if path is None: if utils.is_windows: app_data_path = _writable_location( QStandardPaths.AppDataLocation) path = os.path.join(app_data_path, 'config') else: path = _writable_location(typ) _create(path) _locations[_Location.config] = path _locations[_Location.auto_config] = path # Override the normal (non-auto) config on macOS if utils.is_mac: path = _from_args(typ, args) if path is None: # pragma: no branch path = os.path.expanduser('~/.' + APPNAME) _create(path) _locations[_Location.config] = path config_py_file = os.path.join(_locations[_Location.config], 'config.py') if getattr(args, 'config_py', None) is not None: assert args is not None config_py_file = os.path.abspath(args.config_py) _locations[_Location.config_py] = config_py_file def config(auto: bool = False) -> str: """Get the location for the config directory. If auto=True is given, get the location for the autoconfig.yml directory, which is different on macOS. """ if auto: return _locations[_Location.auto_config] return _locations[_Location.config] def config_py() -> str: """Get the location for config.py. Usually, config.py is in standarddir.config(), but this can be overridden with the --config-py argument. """ return _locations[_Location.config_py] def _init_data(args: typing.Optional[argparse.Namespace]) -> None: """Initialize the location for data.""" typ = QStandardPaths.DataLocation path = _from_args(typ, args) if path is None: if utils.is_windows: app_data_path = _writable_location(QStandardPaths.AppDataLocation) path = os.path.join(app_data_path, 'data') elif sys.platform.startswith('haiku'): # HaikuOS returns an empty value for AppDataLocation config_path = _writable_location(QStandardPaths.ConfigLocation) path = os.path.join(config_path, 'data') else: path = _writable_location(typ) _create(path) _locations[_Location.data] = path # system_data _locations.pop(_Location.system_data, None) # Remove old state if utils.is_linux: path = '/usr/share/' + APPNAME if os.path.exists(path): _locations[_Location.system_data] = path def data(system: bool = False) -> str: """Get the data directory. If system=True is given, gets the system-wide (probably non-writable) data directory. """ if system: try: return _locations[_Location.system_data] except KeyError: pass return _locations[_Location.data] def _init_cache(args: typing.Optional[argparse.Namespace]) -> None: """Initialize the location for the cache.""" typ = QStandardPaths.CacheLocation path = _from_args(typ, args) if path is None: if utils.is_windows: # Local, not Roaming! data_path = _writable_location(QStandardPaths.DataLocation) path = os.path.join(data_path, 'cache') else: path = _writable_location(typ) _create(path) _locations[_Location.cache] = path def cache() -> str: return _locations[_Location.cache] def _init_download(args: typing.Optional[argparse.Namespace]) -> None: """Initialize the location for downloads. Note this is only the default directory as found by Qt. Therefore, we also don't create it. """ typ = QStandardPaths.DownloadLocation path = _from_args(typ, args) if path is None: path = _writable_location(typ) _locations[_Location.download] = path def download() -> str: return _locations[_Location.download] def _init_runtime(args: typing.Optional[argparse.Namespace]) -> None: """Initialize location for runtime data.""" if utils.is_mac or utils.is_windows: # RuntimeLocation is a weird path on macOS and Windows. typ = QStandardPaths.TempLocation else: typ = QStandardPaths.RuntimeLocation path = _from_args(typ, args) if path is None: try: path = _writable_location(typ) except EmptyValueError: # Fall back to TempLocation when RuntimeLocation is misconfigured if typ == QStandardPaths.TempLocation: raise path = _writable_location( # pragma: no cover QStandardPaths.TempLocation) # This is generic, but per-user. # _writable_location makes sure we have a qutebrowser-specific subdir. # # For TempLocation: # "The returned value might be application-specific, shared among # other applications for this user, or even system-wide." # # Unfortunately this path could get too long for sockets (which have a # maximum length of 104 chars), so we don't add the username here... _create(path) _locations[_Location.runtime] = path def runtime() -> str: return _locations[_Location.runtime] def _writable_location(typ: QStandardPaths.StandardLocation) -> str: """Wrapper around QStandardPaths.writableLocation. Arguments: typ: A QStandardPaths::StandardLocation member. """ typ_str = debug.qenum_key(QStandardPaths, typ) # Types we are sure we handle correctly below. assert typ in [ QStandardPaths.ConfigLocation, QStandardPaths.DataLocation, QStandardPaths.CacheLocation, QStandardPaths.DownloadLocation, QStandardPaths.RuntimeLocation, QStandardPaths.TempLocation, # FIXME old Qt getattr(QStandardPaths, 'AppDataLocation', object())], typ_str with _unset_organization(): path = QStandardPaths.writableLocation(typ) log.misc.debug("writable location for {}: {}".format(typ_str, path)) if not path: raise EmptyValueError("QStandardPaths returned an empty value!") # Qt seems to use '/' as path separator even on Windows... path = path.replace('/', os.sep) # Add the application name to the given path if needed. # This is in order for this to work without a QApplication (and thus # QStandardsPaths not knowing the application name). if (typ != QStandardPaths.DownloadLocation and path.split(os.sep)[-1] != APPNAME): path = os.path.join(path, APPNAME) return path def _from_args( typ: QStandardPaths.StandardLocation, args: typing.Optional[argparse.Namespace] ) -> typing.Optional[str]: """Get the standard directory from an argparse namespace. Return: The overridden path, or None if there is no override. """ basedir_suffix = { QStandardPaths.ConfigLocation: 'config', QStandardPaths.DataLocation: 'data', QStandardPaths.CacheLocation: 'cache', QStandardPaths.DownloadLocation: 'download', QStandardPaths.RuntimeLocation: 'runtime', } if getattr(args, 'basedir', None) is None: return None assert args is not None try: suffix = basedir_suffix[typ] except KeyError: # pragma: no cover return None return os.path.abspath(os.path.join(args.basedir, suffix)) def _create(path: str) -> None: """Create the `path` directory. From the XDG basedir spec: If, when attempting to write a file, the destination directory is non-existent an attempt should be made to create it with permission 0700. If the destination directory exists already the permissions should not be changed. """ os.makedirs(path, 0o700, exist_ok=True) def _init_dirs(args: argparse.Namespace = None) -> None: """Create and cache standard directory locations. Mainly in a separate function because we need to call it in tests. """ _init_config(args) _init_data(args) _init_cache(args) _init_download(args) _init_runtime(args) def init(args: typing.Optional[argparse.Namespace]) -> None: """Initialize all standard dirs.""" if args is not None: # args can be None during tests log.init.debug("Base directory: {}".format(args.basedir)) _init_dirs(args) _init_cachedir_tag() if args is not None and getattr(args, 'basedir', None) is None: if utils.is_mac: # pragma: no cover _move_macos() elif utils.is_windows: # pragma: no cover _move_windows() def _move_macos() -> None: """Move most config files to new location on macOS.""" old_config = config(auto=True) # ~/Library/Preferences/qutebrowser new_config = config() # ~/.qutebrowser for f in os.listdir(old_config): if f not in ['qsettings', 'autoconfig.yml']: _move_data(os.path.join(old_config, f), os.path.join(new_config, f)) def _move_windows() -> None: """Move the whole qutebrowser directory from Local to Roaming AppData.""" # %APPDATA%\Local\qutebrowser old_appdata_dir = _writable_location(QStandardPaths.DataLocation) # %APPDATA%\Roaming\qutebrowser new_appdata_dir = _writable_location(QStandardPaths.AppDataLocation) # data subfolder old_data = os.path.join(old_appdata_dir, 'data') new_data = os.path.join(new_appdata_dir, 'data') ok = _move_data(old_data, new_data) if not ok: # pragma: no cover return # config files new_config_dir = os.path.join(new_appdata_dir, 'config') _create(new_config_dir) for f in os.listdir(old_appdata_dir): if f != 'cache': _move_data(os.path.join(old_appdata_dir, f), os.path.join(new_config_dir, f)) def _init_cachedir_tag() -> None: """Create CACHEDIR.TAG if it doesn't exist. See http://www.brynosaurus.com/cachedir/spec.html """ cachedir_tag = os.path.join(cache(), 'CACHEDIR.TAG') if not os.path.exists(cachedir_tag): try: with open(cachedir_tag, 'w', encoding='utf-8') as f: f.write("Signature: 8a477f597d28d172789f06886806bc55\n") f.write("# This file is a cache directory tag created by " "qutebrowser.\n") f.write("# For information about cache directory tags, see:\n") f.write("# http://www.brynosaurus.com/" "cachedir/\n") except OSError: log.init.exception("Failed to create CACHEDIR.TAG") def _move_data(old: str, new: str) -> bool: """Migrate data from an old to a new directory. If the old directory does not exist, the migration is skipped. If the new directory already exists, an error is shown. Return: True if moving succeeded, False otherwise. """ if not os.path.exists(old): return False log.init.debug("Migrating data from {} to {}".format(old, new)) if os.path.exists(new): if not os.path.isdir(new) or os.listdir(new): message.error("Failed to move data from {} as {} is non-empty!" .format(old, new)) return False os.rmdir(new) try: shutil.move(old, new) except OSError as e: message.error("Failed to move data from {} to {}: {}".format( old, new, e)) return False return True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158376.0 qutebrowser-1.10.1/qutebrowser/utils/testfile0000644000175000017510000000010000000000000022537 0ustar00florianflorian00000000000000Hello World! This file is used to test the read_file function. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/urlmatch.py0000644000175000017510000002514000000000000023201 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2018-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """A Chromium-like URL matching pattern. See: https://developer.chrome.com/apps/match_patterns https://cs.chromium.org/chromium/src/extensions/common/url_pattern.cc https://cs.chromium.org/chromium/src/extensions/common/url_pattern.h """ import ipaddress import fnmatch import typing import urllib.parse from PyQt5.QtCore import QUrl from qutebrowser.utils import utils, qtutils class ParseError(Exception): """Raised when a pattern could not be parsed.""" class UrlPattern: """A Chromium-like URL matching pattern. Class attributes: _DEFAULT_PORTS: The default ports used for schemes which support ports. _SCHEMES_WITHOUT_HOST: Schemes which don't need a host. Attributes: host: The host to match to, or None for any host. _pattern: The given pattern as string. _match_all: Whether the pattern should match all URLs. _match_subdomains: Whether the pattern should match subdomains of the given host. _scheme: The scheme to match to, or None to match any scheme. Note that with Chromium, '*'/None only matches http/https and not file/ftp. We deviate from that as per-URL settings aren't security relevant. _path: The path to match to, or None for any path. _port: The port to match to as integer, or None for any port. """ _DEFAULT_PORTS = {'https': 443, 'http': 80, 'ftp': 21} _SCHEMES_WITHOUT_HOST = ['about', 'file', 'data', 'javascript'] def __init__(self, pattern: str) -> None: # Make sure all attributes are initialized if we exit early. self._pattern = pattern self._match_all = False self._match_subdomains = False # type: bool self._scheme = None # type: typing.Optional[str] self.host = None # type: typing.Optional[str] self._path = None # type: typing.Optional[str] self._port = None # type: typing.Optional[int] # > The special pattern matches any URL that starts with a # > permitted scheme. if pattern == '': self._match_all = True return if '\0' in pattern: raise ParseError("May not contain NUL byte") pattern = self._fixup_pattern(pattern) # We use urllib.parse instead of QUrl here because it can handle # hosts with * in them. try: parsed = urllib.parse.urlparse(pattern) except ValueError as e: raise ParseError(str(e)) assert parsed is not None self._init_scheme(parsed) self._init_host(parsed) self._init_path(parsed) self._init_port(parsed) def _to_tuple(self) -> typing.Tuple: """Get a pattern with information used for __eq__/__hash__.""" return (self._match_all, self._match_subdomains, self._scheme, self.host, self._path, self._port) def __hash__(self) -> int: return hash(self._to_tuple()) def __eq__(self, other: typing.Any) -> bool: if not isinstance(other, UrlPattern): return NotImplemented return self._to_tuple() == other._to_tuple() def __repr__(self) -> str: return utils.get_repr(self, pattern=self._pattern, constructor=True) def __str__(self) -> str: return self._pattern def _fixup_pattern(self, pattern: str) -> str: """Make sure the given pattern is parseable by urllib.parse.""" if pattern.startswith('*:'): # Any scheme, but *:// is unparseable pattern = 'any:' + pattern[2:] schemes = tuple(s + ':' for s in self._SCHEMES_WITHOUT_HOST) if '://' not in pattern and not pattern.startswith(schemes): pattern = 'any://' + pattern # Chromium handles file://foo like file:///foo # FIXME This doesn't actually strip the hostname correctly. if (pattern.startswith('file://') and not pattern.startswith('file:///')): pattern = 'file:///' + pattern[len("file://"):] return pattern def _init_scheme(self, parsed: urllib.parse.ParseResult) -> None: """Parse the scheme from the given URL. Deviation from Chromium: - We assume * when no scheme has been given. """ if not parsed.scheme: raise ParseError("Missing scheme") if parsed.scheme == 'any': self._scheme = None return self._scheme = parsed.scheme def _init_path(self, parsed: urllib.parse.ParseResult) -> None: """Parse the path from the given URL. Deviation from Chromium: - We assume * when no path has been given. """ if self._scheme == 'about' and not parsed.path.strip(): raise ParseError("Pattern without path") if parsed.path == '/*': self._path = None elif not parsed.path: # When the user doesn't add a trailing slash, we assume the pattern # matches any path. self._path = None else: self._path = parsed.path def _init_host(self, parsed: urllib.parse.ParseResult) -> None: """Parse the host from the given URL. Deviation from Chromium: - http://:1234/ is not a valid URL because it has no host. """ if parsed.hostname is None or not parsed.hostname.strip(): if self._scheme not in self._SCHEMES_WITHOUT_HOST: raise ParseError("Pattern without host") assert self.host is None return if parsed.netloc.startswith('['): # Using QUrl parsing to minimize ipv6 addresses url = QUrl() url.setHost(parsed.hostname) if not url.isValid(): raise ParseError(url.errorString()) self.host = url.host() return # FIXME what about multiple dots? host_parts = parsed.hostname.rstrip('.').split('.') if host_parts[0] == '*': host_parts = host_parts[1:] self._match_subdomains = True if not host_parts: self.host = None return self.host = '.'.join(host_parts) if self.host.endswith('.*'): # Special case to have a nicer error raise ParseError("TLD wildcards are not implemented yet") if '*' in self.host: # Only * or *.foo is allowed as host. raise ParseError("Invalid host wildcard") def _init_port(self, parsed: urllib.parse.ParseResult) -> None: """Parse the port from the given URL. Deviation from Chromium: - We use None instead of "*" if there's no port filter. """ if parsed.netloc.endswith(':*'): # We can't access parsed.port as it tries to run int() self._port = None elif parsed.netloc.endswith(':'): raise ParseError("Invalid port: Port is empty") else: try: self._port = parsed.port except ValueError as e: raise ParseError("Invalid port: {}".format(e)) scheme_has_port = (self._scheme in list(self._DEFAULT_PORTS) or self._scheme is None) if self._port is not None and not scheme_has_port: raise ParseError("Ports are unsupported with {} scheme".format( self._scheme)) def _matches_scheme(self, scheme: str) -> bool: return self._scheme is None or self._scheme == scheme def _matches_host(self, host: str) -> bool: # FIXME what about multiple dots? host = host.rstrip('.') # If we have no host in the match pattern, that means that we're # matching all hosts, which means we have a match no matter what the # test host is. # Contrary to Chromium, we don't need to check for # self._match_subdomains, as we want to return True here for e.g. # file:// as well. if self.host is None: return True # If the hosts are exactly equal, we have a match. if host == self.host: return True # Otherwise, we can only match if our match pattern matches subdomains. if not self._match_subdomains: return False # We don't do subdomain matching against IP addresses, so we can give # up now if the test host is an IP address. if not utils.raises(ValueError, ipaddress.ip_address, host): return False # Check if the test host is a subdomain of our host. if len(host) <= (len(self.host) + 1): return False if not host.endswith(self.host): return False return host[len(host) - len(self.host) - 1] == '.' def _matches_port(self, scheme: str, port: int) -> bool: if port == -1 and scheme in self._DEFAULT_PORTS: port = self._DEFAULT_PORTS[scheme] return self._port is None or self._port == port def _matches_path(self, path: str) -> bool: if self._path is None: return True # Match 'google.com' with 'google.com/' if path + '/*' == self._path: return True # FIXME Chromium seems to have a more optimized glob matching which # doesn't rely on regexes. Do we need that too? return fnmatch.fnmatchcase(path, self._path) def matches(self, qurl: QUrl) -> bool: """Check if the pattern matches the given QUrl.""" qtutils.ensure_valid(qurl) if self._match_all: return True if not self._matches_scheme(qurl.scheme()): return False # FIXME ignore for file:// like Chromium? if not self._matches_host(qurl.host()): return False if not self._matches_port(qurl.scheme(), qurl.port()): return False if not self._matches_path(qurl.path()): return False return True ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/urlutils.py0000644000175000017510000004420200000000000023245 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Utils regarding URL handling.""" import re import base64 import os.path import ipaddress import posixpath import urllib.parse import typing from PyQt5.QtCore import QUrl, QUrlQuery from PyQt5.QtNetwork import QHostInfo, QHostAddress, QNetworkProxy from qutebrowser.api import cmdutils from qutebrowser.config import config from qutebrowser.utils import log, qtutils, message, utils from qutebrowser.browser.network import pac # FIXME: we probably could raise some exceptions on invalid URLs # https://github.com/qutebrowser/qutebrowser/issues/108 # URL schemes supported by QtWebEngine WEBENGINE_SCHEMES = [ 'about', 'data', 'file', 'filesystem', 'ftp', 'http', 'https', 'javascript', 'ws', 'wss', ] class InvalidUrlError(Exception): """Error raised if a function got an invalid URL.""" def __init__(self, url: QUrl) -> None: if url.isValid(): raise ValueError("Got valid URL {}!".format(url.toDisplayString())) self.url = url self.msg = get_errstring(url) super().__init__(self.msg) def _parse_search_term(s: str) -> typing.Tuple[typing.Optional[str], typing.Optional[str]]: """Get a search engine name and search term from a string. Args: s: The string to get a search engine for. Return: A (engine, term) tuple, where engine is None for the default engine. """ s = s.strip() split = s.split(maxsplit=1) if not split: raise ValueError("Empty search term!") if len(split) == 2: if split[0] in config.val.url.searchengines: engine = split[0] # type: typing.Optional[str] term = split[1] # type: typing.Optional[str] else: engine = None term = s else: if config.val.url.open_base_url and s in config.val.url.searchengines: engine = s term = None else: engine = None term = s log.url.debug("engine {}, term {!r}".format(engine, term)) return (engine, term) def _get_search_url(txt: str) -> QUrl: """Get a search engine URL for a text. Args: txt: Text to search for. Return: The search URL as a QUrl. """ log.url.debug("Finding search engine for {!r}".format(txt)) engine, term = _parse_search_term(txt) if not engine: engine = 'DEFAULT' if term: template = config.val.url.searchengines[engine] quoted_term = urllib.parse.quote(term, safe='') url = qurl_from_user_input(template.format(quoted_term)) else: url = qurl_from_user_input(config.val.url.searchengines[engine]) url.setPath(None) # type: ignore url.setFragment(None) # type: ignore url.setQuery(None) # type: ignore qtutils.ensure_valid(url) return url def _is_url_naive(urlstr: str) -> bool: """Naive check if given URL is really a URL. Args: urlstr: The URL to check for, as string. Return: True if the URL really is a URL, False otherwise. """ url = qurl_from_user_input(urlstr) assert url.isValid() host = url.host() # Valid IPv4/IPv6 address. Qt converts things like "23.42" or "1337" or # "0xDEAD" to IP addresses, which we don't like, so we check if the host # from Qt is part of the input. if (not utils.raises(ValueError, ipaddress.ip_address, host) and host in urlstr): return True tld = r'\.([^.0-9_-]+|xn--[a-z0-9-]+)$' forbidden = r'[\u0000-\u002c\u002f\u003a-\u0060\u007b-\u00b6]' return bool(re.search(tld, host) and not re.search(forbidden, host)) def _is_url_dns(urlstr: str) -> bool: """Check if a URL is really a URL via DNS. Args: url: The URL to check for as a string. Return: True if the URL really is a URL, False otherwise. """ url = qurl_from_user_input(urlstr) assert url.isValid() if (utils.raises(ValueError, ipaddress.ip_address, urlstr) and not QHostAddress(urlstr).isNull()): log.url.debug("Bogus IP URL -> False") # Qt treats things like "23.42" or "1337" or "0xDEAD" as valid URLs # which we don't want to. return False host = url.host() if not host: log.url.debug("URL has no host -> False") return False log.url.debug("Doing DNS request for {}".format(host)) info = QHostInfo.fromName(host) return not info.error() def fuzzy_url(urlstr: str, cwd: str = None, relative: bool = False, do_search: bool = True, force_search: bool = False) -> QUrl: """Get a QUrl based on a user input which is URL or search term. Args: urlstr: URL to load as a string. cwd: The current working directory, or None. relative: Whether to resolve relative files. do_search: Whether to perform a search on non-URLs. force_search: Whether to force a search even if the content can be interpreted as a URL or a path. Return: A target QUrl to a search page or the original URL. """ urlstr = urlstr.strip() path = get_path_if_valid(urlstr, cwd=cwd, relative=relative, check_exists=True) if not force_search and path is not None: url = QUrl.fromLocalFile(path) elif force_search or (do_search and not is_url(urlstr)): # probably a search term log.url.debug("URL is a fuzzy search term") try: url = _get_search_url(urlstr) except ValueError: # invalid search engine url = qurl_from_user_input(urlstr) else: # probably an address log.url.debug("URL is a fuzzy address") url = qurl_from_user_input(urlstr) log.url.debug("Converting fuzzy term {!r} to URL -> {}".format( urlstr, url.toDisplayString())) ensure_valid(url) return url def _has_explicit_scheme(url: QUrl) -> bool: """Check if a url has an explicit scheme given. Args: url: The URL as QUrl. """ # Note that generic URI syntax actually would allow a second colon # after the scheme delimiter. Since we don't know of any URIs # using this and want to support e.g. searching for scoped C++ # symbols, we treat this as not a URI anyways. return bool(url.isValid() and url.scheme() and (url.host() or url.path()) and not url.path().startswith(':')) def is_special_url(url: QUrl) -> bool: """Return True if url is an about:... or other special URL. Args: url: The URL as QUrl. """ if not url.isValid(): return False special_schemes = ('about', 'qute', 'file') return url.scheme() in special_schemes def is_url(urlstr: str) -> bool: """Check if url seems to be a valid URL. Args: urlstr: The URL as string. Return: True if it is a valid URL, False otherwise. """ autosearch = config.val.url.auto_search log.url.debug("Checking if {!r} is a URL (autosearch={}).".format( urlstr, autosearch)) urlstr = urlstr.strip() qurl = QUrl(urlstr) qurl_userinput = qurl_from_user_input(urlstr) if autosearch == 'never': # no autosearch, so everything is a URL unless it has an explicit # search engine. try: engine, _term = _parse_search_term(urlstr) except ValueError: return False else: return engine is None if not qurl_userinput.isValid(): # This will also catch non-URLs containing spaces. return False if _has_explicit_scheme(qurl) and ' ' not in urlstr: # URLs with explicit schemes are always URLs log.url.debug("Contains explicit scheme") url = True elif qurl_userinput.host() in ['localhost', '127.0.0.1', '::1']: log.url.debug("Is localhost.") url = True elif is_special_url(qurl): # Special URLs are always URLs, even with autosearch=never log.url.debug("Is a special URL.") url = True elif autosearch == 'dns': log.url.debug("Checking via DNS check") # We want to use qurl_from_user_input here, as the user might enter # "foo.de" and that should be treated as URL here. url = ' ' not in qurl_userinput.userName() and _is_url_dns(urlstr) elif autosearch == 'naive': log.url.debug("Checking via naive check") url = ' ' not in qurl_userinput.userName() and _is_url_naive(urlstr) else: # pragma: no cover raise ValueError("Invalid autosearch value") log.url.debug("url = {}".format(url)) return url def qurl_from_user_input(urlstr: str) -> QUrl: """Get a QUrl based on a user input. Additionally handles IPv6 addresses. QUrl.fromUserInput handles something like '::1' as a file URL instead of an IPv6, so we first try to handle it as a valid IPv6, and if that fails we use QUrl.fromUserInput. WORKAROUND - https://bugreports.qt.io/browse/QTBUG-41089 FIXME - Maybe https://codereview.qt-project.org/#/c/93851/ has a better way to solve this? https://github.com/qutebrowser/qutebrowser/issues/109 Args: urlstr: The URL as string. Return: The converted QUrl. """ # First we try very liberally to separate something like an IPv6 from the # rest (e.g. path info or parameters) match = re.fullmatch(r'\[?([0-9a-fA-F:.]+)\]?(.*)', urlstr.strip()) if match: ipstr, rest = match.groups() else: ipstr = urlstr.strip() rest = '' # Then we try to parse it as an IPv6, and if we fail use # QUrl.fromUserInput. try: ipaddress.IPv6Address(ipstr) except ipaddress.AddressValueError: return QUrl.fromUserInput(urlstr) else: return QUrl('http://[{}]{}'.format(ipstr, rest)) def ensure_valid(url: QUrl) -> None: if not url.isValid(): raise InvalidUrlError(url) def invalid_url_error(url: QUrl, action: str) -> None: """Display an error message for a URL. Args: action: The action which was interrupted by the error. """ if url.isValid(): raise ValueError("Calling invalid_url_error with valid URL {}".format( url.toDisplayString())) errstring = get_errstring( url, "Trying to {} with invalid URL".format(action)) message.error(errstring) def raise_cmdexc_if_invalid(url: QUrl) -> None: """Check if the given QUrl is invalid, and if so, raise a CommandError.""" try: ensure_valid(url) except InvalidUrlError as e: raise cmdutils.CommandError(str(e)) def get_path_if_valid(pathstr: str, cwd: str = None, relative: bool = False, check_exists: bool = False) -> typing.Optional[str]: """Check if path is a valid path. Args: pathstr: The path as string. cwd: The current working directory, or None. relative: Whether to resolve relative files. check_exists: Whether to check if the file actually exists of filesystem. Return: The path if it is a valid path, None otherwise. """ pathstr = pathstr.strip() log.url.debug("Checking if {!r} is a path".format(pathstr)) expanded = os.path.expanduser(pathstr) if os.path.isabs(expanded): path = expanded # type: typing.Optional[str] elif relative and cwd: path = os.path.join(cwd, expanded) elif relative: try: path = os.path.abspath(expanded) except OSError: path = None else: path = None if check_exists: if path is not None: try: if os.path.exists(path): log.url.debug("URL is a local file") else: path = None except UnicodeEncodeError: log.url.debug( "URL contains characters which are not present in the " "current locale") path = None return path def filename_from_url(url: QUrl) -> typing.Optional[str]: """Get a suitable filename from a URL. Args: url: The URL to parse, as a QUrl. Return: The suggested filename as a string, or None. """ if not url.isValid(): return None pathname = posixpath.basename(url.path()) if pathname: return pathname elif url.host(): return url.host() + '.html' else: return None HostTupleType = typing.Tuple[str, str, int] def host_tuple(url: QUrl) -> HostTupleType: """Get a (scheme, host, port) tuple from a QUrl. This is suitable to identify a connection, e.g. for SSL errors. """ ensure_valid(url) scheme, host, port = url.scheme(), url.host(), url.port() assert scheme if not host: raise ValueError("Got URL {} without host.".format( url.toDisplayString())) if port == -1: port_mapping = { 'http': 80, 'https': 443, 'ftp': 21, } try: port = port_mapping[scheme] except KeyError: raise ValueError("Got URL {} with unknown port.".format( url.toDisplayString())) return scheme, host, port def get_errstring(url: QUrl, base: str = "Invalid URL") -> str: """Get an error string for a URL. Args: url: The URL as a QUrl. base: The base error string. Return: A new string with url.errorString() is appended if available. """ url_error = url.errorString() if url_error: return base + " - {}".format(url_error) else: return base def same_domain(url1: QUrl, url2: QUrl) -> bool: """Check if url1 and url2 belong to the same website. This will use a "public suffix list" to determine what a "top level domain" is. All further domains are ignored. For example example.com and www.example.com are considered the same. but example.co.uk and test.co.uk are not. Return: True if the domains are the same, False otherwise. """ ensure_valid(url1) ensure_valid(url2) suffix1 = url1.topLevelDomain() suffix2 = url2.topLevelDomain() if not suffix1: return url1.host() == url2.host() if suffix1 != suffix2: return False domain1 = url1.host()[:-len(suffix1)].split('.')[-1] domain2 = url2.host()[:-len(suffix2)].split('.')[-1] return domain1 == domain2 def encoded_url(url: QUrl) -> str: """Return the fully encoded url as string. Args: url: The url to encode as QUrl. """ return bytes(url.toEncoded()).decode('ascii') def file_url(path: str) -> QUrl: """Return a file:// url (as string) to the given local path. Arguments: path: The absolute path to the local file """ url = QUrl.fromLocalFile(path) return url.toString(QUrl.FullyEncoded) # type: ignore def data_url(mimetype: str, data: bytes) -> QUrl: """Get a data: QUrl for the given data.""" b64 = base64.b64encode(data).decode('ascii') url = QUrl('data:{};base64,{}'.format(mimetype, b64)) qtutils.ensure_valid(url) return url def safe_display_string(qurl: QUrl) -> str: """Get a IDN-homograph phishing safe form of the given QUrl. If we're dealing with a Punycode-encoded URL, this prepends the hostname in its encoded form, to make sure those URLs are distinguishable. See https://github.com/qutebrowser/qutebrowser/issues/2547 and https://bugreports.qt.io/browse/QTBUG-60365 """ ensure_valid(qurl) host = qurl.host(QUrl.FullyEncoded) # type: ignore if '..' in host: # pragma: no cover # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-60364 return '(unparseable URL!) {}'.format(qurl.toDisplayString()) for part in host.split('.'): url_host = qurl.host(QUrl.FullyDecoded) # type: ignore if part.startswith('xn--') and host != url_host: return '({}) {}'.format(host, qurl.toDisplayString()) return qurl.toDisplayString() def query_string(qurl: QUrl) -> str: """Get a query string for the given URL. This is a WORKAROUND for: https://www.riverbankcomputing.com/pipermail/pyqt/2017-November/039702.html """ try: return qurl.query() except AttributeError: # pragma: no cover return QUrlQuery(qurl).query() class InvalidProxyTypeError(Exception): """Error raised when proxy_from_url gets an unknown proxy type.""" def __init__(self, typ: str) -> None: super().__init__("Invalid proxy type {}!".format(typ)) def proxy_from_url(url: QUrl) -> QNetworkProxy: """Create a QNetworkProxy from QUrl and a proxy type. Args: url: URL of a proxy (possibly with credentials). Return: New QNetworkProxy. """ ensure_valid(url) scheme = url.scheme() if scheme in ['pac+http', 'pac+https', 'pac+file']: fetcher = pac.PACFetcher(url) fetcher.fetch() return fetcher types = { 'http': QNetworkProxy.HttpProxy, 'socks': QNetworkProxy.Socks5Proxy, 'socks5': QNetworkProxy.Socks5Proxy, 'direct': QNetworkProxy.NoProxy, } if scheme not in types: raise InvalidProxyTypeError(scheme) proxy = QNetworkProxy(types[scheme], url.host()) if url.port() != -1: proxy.setPort(url.port()) if url.userName(): proxy.setUser(url.userName()) if url.password(): proxy.setPassword(url.password()) return proxy ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/usertypes.py0000644000175000017510000003473500000000000023437 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Custom useful data types.""" import operator import enum import typing import attr from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QTimer from PyQt5.QtCore import QUrl from qutebrowser.utils import log, qtutils, utils _T = typing.TypeVar('_T') class Unset: """Class for an unset object.""" __slots__ = () def __repr__(self) -> str: return '' UNSET = Unset() class NeighborList(typing.Sequence[_T]): """A list of items which saves its current position. Class attributes: Modes: Different modes, see constructor documentation. Attributes: fuzzyval: The value which is currently set but not in the list. _idx: The current position in the list. _items: A list of all items, accessed through item property. _mode: The current mode. """ Modes = enum.Enum('Modes', ['edge', 'exception']) def __init__(self, items: typing.Sequence[_T] = None, default: typing.Union[_T, Unset] = UNSET, mode: Modes = Modes.exception) -> None: """Constructor. Args: items: The list of items to iterate in. _default: The initially selected value. _mode: Behavior when the first/last item is reached. Modes.edge: Go to the first/last item Modes.exception: Raise an IndexError. """ if not isinstance(mode, self.Modes): raise TypeError("Mode {} is not a Modes member!".format(mode)) if items is None: self._items = [] # type: typing.Sequence[_T] else: self._items = list(items) self._default = default if not isinstance(default, Unset): idx = self._items.index(default) self._idx = idx # type: typing.Optional[int] else: self._idx = None self._mode = mode self.fuzzyval = None # type: typing.Optional[int] def __getitem__(self, key: int) -> _T: # type: ignore return self._items[key] def __len__(self) -> int: return len(self._items) def __repr__(self) -> str: return utils.get_repr(self, items=self._items, mode=self._mode, idx=self._idx, fuzzyval=self.fuzzyval) def _snap_in(self, offset: int) -> bool: """Set the current item to the closest item to self.fuzzyval. Args: offset: negative to get the next smaller item, positive for the next bigger one. Return: True if the value snapped in (changed), False when the value already was in the list. """ assert isinstance(self.fuzzyval, (int, float)), self.fuzzyval op = operator.le if offset < 0 else operator.ge items = [(idx, e) for (idx, e) in enumerate(self._items) if op(e, self.fuzzyval)] if items: item = min( items, key=lambda tpl: abs(self.fuzzyval - tpl[1])) # type: ignore else: sorted_items = sorted(enumerate(self.items), key=lambda e: e[1]) idx = 0 if offset < 0 else -1 item = sorted_items[idx] self._idx = item[0] return self.fuzzyval not in self._items def _get_new_item(self, offset: int) -> _T: """Logic for getitem to get the item at offset. Args: offset: The offset of the current item, relative to the last one. Return: The new item. """ assert self._idx is not None try: if self._idx + offset >= 0: new = self._items[self._idx + offset] else: raise IndexError except IndexError: if self._mode == self.Modes.edge: assert offset != 0 if offset > 0: new = self.lastitem() else: new = self.firstitem() elif self._mode == self.Modes.exception: # pragma: no branch raise else: self._idx += offset return new @property def items(self) -> typing.Sequence[_T]: """Getter for items, which should not be set.""" return self._items def getitem(self, offset: int) -> _T: """Get the item with a relative position. Args: offset: The offset of the current item, relative to the last one. Return: The new item. """ log.misc.debug("{} items, idx {}, offset {}".format( len(self._items), self._idx, offset)) if not self._items: raise IndexError("No items found!") if self.fuzzyval is not None: # Value has been set to something not in the list, so we snap in to # the closest value in the right direction and count this as one # step towards offset. snapped = self._snap_in(offset) if snapped and offset > 0: offset -= 1 elif snapped: offset += 1 self.fuzzyval = None return self._get_new_item(offset) def curitem(self) -> _T: """Get the current item in the list.""" if self._idx is not None: return self._items[self._idx] else: raise IndexError("No current item!") def nextitem(self) -> _T: """Get the next item in the list.""" return self.getitem(1) def previtem(self) -> _T: """Get the previous item in the list.""" return self.getitem(-1) def firstitem(self) -> _T: """Get the first item in the list.""" if not self._items: raise IndexError("No items found!") self._idx = 0 return self.curitem() def lastitem(self) -> _T: """Get the last item in the list.""" if not self._items: raise IndexError("No items found!") self._idx = len(self._items) - 1 return self.curitem() def reset(self) -> _T: """Reset the position to the default.""" if self._default is UNSET: raise ValueError("No default set!") self._idx = self._items.index(self._default) return self.curitem() # The mode of a Question. PromptMode = enum.Enum('PromptMode', ['yesno', 'text', 'user_pwd', 'alert', 'download']) class ClickTarget(enum.Enum): """How to open a clicked link.""" normal = 0 #: Open the link in the current tab tab = 1 #: Open the link in a new foreground tab tab_bg = 2 #: Open the link in a new background tab window = 3 #: Open the link in a new window hover = 4 #: Only hover over the link class KeyMode(enum.Enum): """Key input modes.""" normal = 1 #: Normal mode (no mode was entered) hint = 2 #: Hint mode (showing labels for links) command = 3 #: Command mode (after pressing the colon key) yesno = 4 #: Yes/No prompts prompt = 5 #: Text prompts insert = 6 #: Insert mode (passing through most keys) passthrough = 7 #: Passthrough mode (passing through all keys) caret = 8 #: Caret mode (moving cursor with keys) set_mark = 9 jump_mark = 10 record_macro = 11 run_macro = 12 class Exit(enum.IntEnum): """Exit statuses for errors. Needs to be an int for sys.exit.""" ok = 0 reserved = 1 exception = 2 err_ipc = 3 err_init = 4 # Load status of a tab LoadStatus = enum.Enum('LoadStatus', ['none', 'success', 'success_https', 'error', 'warn', 'loading']) # Backend of a tab Backend = enum.Enum('Backend', ['QtWebKit', 'QtWebEngine']) class JsWorld(enum.Enum): """World/context to run JavaScript code in.""" main = 1 #: Same world as the web page's JavaScript. application = 2 #: Application world, used by qutebrowser internally. user = 3 #: User world, currently not used. jseval = 4 #: World used for the jseval-command. # Log level of a JS message. This needs to match up with the keys allowed for # the content.javascript.log setting. JsLogLevel = enum.Enum('JsLogLevel', ['unknown', 'info', 'warning', 'error']) MessageLevel = enum.Enum('MessageLevel', ['error', 'warning', 'info']) IgnoreCase = enum.Enum('IgnoreCase', ['smart', 'never', 'always']) class CommandValue(enum.Enum): """Special values which are injected when running a command handler.""" count = 1 win_id = 2 cur_tab = 3 count_tab = 4 class Question(QObject): """A question asked to the user, e.g. via the status bar. Note the creator is responsible for cleaning up the question after it doesn't need it anymore, e.g. via connecting Question.completed to Question.deleteLater. Attributes: mode: A PromptMode enum member. yesno: A question which can be answered with yes/no. text: A question which requires a free text answer. user_pwd: A question for a username and password. default: The default value. For yesno, None (no default), True or False. For text, a default text as string. For user_pwd, a default username as string. title: The question title to show. text: The prompt text to display to the user. url: Any URL referenced in prompts. option: Boolean option to be set when answering always/never. answer: The value the user entered (as password for user_pwd). is_aborted: Whether the question was aborted. interrupted: Whether the question was interrupted by another one. Signals: answered: Emitted when the question has been answered by the user. arg: The answer to the question. cancelled: Emitted when the question has been cancelled by the user. aborted: Emitted when the question was aborted programmatically. In this case, cancelled is not emitted. answered_yes: Convenience signal emitted when a yesno question was answered with yes. answered_no: Convenience signal emitted when a yesno question was answered with no. completed: Emitted when the question was completed in any way. """ answered = pyqtSignal(object) cancelled = pyqtSignal() aborted = pyqtSignal() answered_yes = pyqtSignal() answered_no = pyqtSignal() completed = pyqtSignal() def __init__(self, parent: QObject = None) -> None: super().__init__(parent) self.mode = None # type: typing.Optional[PromptMode] self.default = None # type: typing.Union[bool, str, None] self.title = None # type: typing.Optional[str] self.text = None # type: typing.Optional[str] self.url = None # type: typing.Optional[str] self.option = None # type: typing.Optional[bool] self.answer = None # type: typing.Union[str, bool, None] self.is_aborted = False self.interrupted = False def __repr__(self) -> str: return utils.get_repr(self, title=self.title, text=self.text, mode=self.mode, default=self.default, option=self.option) @pyqtSlot() def done(self) -> None: """Must be called when the question was answered completely.""" self.answered.emit(self.answer) if self.mode == PromptMode.yesno: if self.answer: self.answered_yes.emit() else: self.answered_no.emit() self.completed.emit() @pyqtSlot() def cancel(self) -> None: """Cancel the question (resulting from user-input).""" self.cancelled.emit() self.completed.emit() @pyqtSlot() def abort(self) -> None: """Abort the question.""" if self.is_aborted: log.misc.debug("Question was already aborted") return self.is_aborted = True self.aborted.emit() self.completed.emit() class Timer(QTimer): """A timer which has a name to show in __repr__ and checks for overflows. Attributes: _name: The name of the timer. """ def __init__(self, parent: QObject = None, name: str = None) -> None: super().__init__(parent) if name is None: self._name = "unnamed" else: self.setObjectName(name) self._name = name def __repr__(self) -> str: return utils.get_repr(self, name=self._name) def setInterval(self, msec: int) -> None: """Extend setInterval to check for overflows.""" qtutils.check_overflow(msec, 'int') super().setInterval(msec) def start(self, msec: int = None) -> None: """Extend start to check for overflows.""" if msec is not None: qtutils.check_overflow(msec, 'int') super().start(msec) else: super().start() class AbstractCertificateErrorWrapper: """A wrapper over an SSL/certificate error.""" def __init__(self, error: typing.Any) -> None: self._error = error def __str__(self) -> str: raise NotImplementedError def __repr__(self) -> str: raise NotImplementedError def is_overridable(self) -> bool: raise NotImplementedError @attr.s class NavigationRequest: """A request to navigate to the given URL.""" Type = enum.Enum('Type', [ 'link_clicked', 'typed', # QtWebEngine only 'form_submitted', 'form_resubmitted', # QtWebKit only 'back_forward', 'reloaded', 'redirect', # QtWebEngine >= 5.14 only 'other' ]) url = attr.ib() # type: QUrl navigation_type = attr.ib() # type: Type is_main_frame = attr.ib() # type: bool accepted = attr.ib(default=True) # type: bool ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/utils.py0000644000175000017510000006005400000000000022525 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Other utilities which don't fit anywhere else.""" import os import os.path import io import re import sys import enum import json import datetime import traceback import functools import contextlib import posixpath import socket import shlex import glob import mimetypes import typing from PyQt5.QtCore import QUrl from PyQt5.QtGui import QColor, QClipboard, QDesktopServices from PyQt5.QtWidgets import QApplication import pkg_resources import yaml try: from yaml import (CSafeLoader as YamlLoader, CSafeDumper as YamlDumper) YAML_C_EXT = True except ImportError: # pragma: no cover from yaml import (SafeLoader as YamlLoader, # type: ignore SafeDumper as YamlDumper) YAML_C_EXT = False import qutebrowser from qutebrowser.utils import qtutils, log fake_clipboard = None log_clipboard = False _resource_cache = {} is_mac = sys.platform.startswith('darwin') is_linux = sys.platform.startswith('linux') is_windows = sys.platform.startswith('win') is_posix = os.name == 'posix' class Unreachable(Exception): """Raised when there was unreachable code.""" class ClipboardError(Exception): """Raised if the clipboard contents are unavailable for some reason.""" class SelectionUnsupportedError(ClipboardError): """Raised if [gs]et_clipboard is used and selection=True is unsupported.""" def __init__(self) -> None: super().__init__("Primary selection is not supported on this " "platform!") class ClipboardEmptyError(ClipboardError): """Raised if get_clipboard is used and the clipboard is empty.""" def elide(text: str, length: int) -> str: """Elide text so it uses a maximum of length chars.""" if length < 1: raise ValueError("length must be >= 1!") if len(text) <= length: return text else: return text[:length - 1] + '\u2026' def elide_filename(filename: str, length: int) -> str: """Elide a filename to the given length. The difference to the elide() is that the text is removed from the middle instead of from the end. This preserves file name extensions. Additionally, standard ASCII dots are used ("...") instead of the unicode "…" (U+2026) so it works regardless of the filesystem encoding. This function does not handle path separators. Args: filename: The filename to elide. length: The maximum length of the filename, must be at least 3. Return: The elided filename. """ elidestr = '...' if length < len(elidestr): raise ValueError('length must be greater or equal to 3') if len(filename) <= length: return filename # Account for '...' length -= len(elidestr) left = length // 2 right = length - left if right == 0: return filename[:left] + elidestr else: return filename[:left] + elidestr + filename[-right:] def compact_text(text: str, elidelength: int = None) -> str: """Remove leading whitespace and newlines from a text and maybe elide it. Args: text: The text to compact. elidelength: To how many chars to elide. """ lines = [] for line in text.splitlines(): lines.append(line.strip()) out = ''.join(lines) if elidelength is not None: out = elide(out, elidelength) return out def preload_resources() -> None: """Load resource files into the cache.""" for subdir, pattern in [('html', '*.html'), ('javascript', '*.js')]: path = resource_filename(subdir) for full_path in glob.glob(os.path.join(path, pattern)): sub_path = '/'.join([subdir, os.path.basename(full_path)]) _resource_cache[sub_path] = read_file(sub_path) # FIXME:typing Return value should be bytes/str def read_file(filename: str, binary: bool = False) -> typing.Any: """Get the contents of a file contained with qutebrowser. Args: filename: The filename to open as string. binary: Whether to return a binary string. If False, the data is UTF-8-decoded. Return: The file contents as string. """ assert not posixpath.isabs(filename), filename assert os.path.pardir not in filename.split(posixpath.sep), filename if not binary and filename in _resource_cache: return _resource_cache[filename] if hasattr(sys, 'frozen'): # PyInstaller doesn't support pkg_resources :( # https://github.com/pyinstaller/pyinstaller/wiki/FAQ#misc fn = os.path.join(os.path.dirname(sys.executable), filename) if binary: with open(fn, 'rb') as f: # type: typing.IO return f.read() else: with open(fn, 'r', encoding='utf-8') as f: return f.read() else: data = pkg_resources.resource_string( qutebrowser.__name__, filename) if binary: return data return data.decode('UTF-8') def resource_filename(filename: str) -> str: """Get the absolute filename of a file contained with qutebrowser. Args: filename: The filename. Return: The absolute filename. """ if hasattr(sys, 'frozen'): return os.path.join(os.path.dirname(sys.executable), filename) return pkg_resources.resource_filename(qutebrowser.__name__, filename) def _get_color_percentage(a_c1: int, a_c2: int, a_c3: int, b_c1: int, b_c2: int, b_c3: int, percent: int) -> typing.Tuple[int, int, int]: """Get a color which is percent% interpolated between start and end. Args: a_c1, a_c2, a_c3: Start color components (R, G, B / H, S, V / H, S, L) b_c1, b_c2, b_c3: End color components (R, G, B / H, S, V / H, S, L) percent: Percentage to interpolate, 0-100. 0: Start color will be returned. 100: End color will be returned. Return: A (c1, c2, c3) tuple with the interpolated color components. """ if not 0 <= percent <= 100: raise ValueError("percent needs to be between 0 and 100!") out_c1 = round(a_c1 + (b_c1 - a_c1) * percent / 100) out_c2 = round(a_c2 + (b_c2 - a_c2) * percent / 100) out_c3 = round(a_c3 + (b_c3 - a_c3) * percent / 100) return (out_c1, out_c2, out_c3) def interpolate_color( start: QColor, end: QColor, percent: int, colorspace: typing.Optional[QColor.Spec] = QColor.Rgb ) -> QColor: """Get an interpolated color value. Args: start: The start color. end: The end color. percent: Which value to get (0 - 100) colorspace: The desired interpolation color system, QColor::{Rgb,Hsv,Hsl} (from QColor::Spec enum) If None, start is used except when percent is 100. Return: The interpolated QColor, with the same spec as the given start color. """ qtutils.ensure_valid(start) qtutils.ensure_valid(end) if colorspace is None: if percent == 100: return QColor(*end.getRgb()) else: return QColor(*start.getRgb()) out = QColor() if colorspace == QColor.Rgb: a_c1, a_c2, a_c3, _alpha = start.getRgb() b_c1, b_c2, b_c3, _alpha = end.getRgb() components = _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent) out.setRgb(*components) elif colorspace == QColor.Hsv: a_c1, a_c2, a_c3, _alpha = start.getHsv() b_c1, b_c2, b_c3, _alpha = end.getHsv() components = _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent) out.setHsv(*components) elif colorspace == QColor.Hsl: a_c1, a_c2, a_c3, _alpha = start.getHsl() b_c1, b_c2, b_c3, _alpha = end.getHsl() components = _get_color_percentage(a_c1, a_c2, a_c3, b_c1, b_c2, b_c3, percent) out.setHsl(*components) else: raise ValueError("Invalid colorspace!") out = out.convertTo(start.spec()) qtutils.ensure_valid(out) return out def format_seconds(total_seconds: int) -> str: """Format a count of seconds to get a [H:]M:SS string.""" prefix = '-' if total_seconds < 0 else '' hours, rem = divmod(abs(round(total_seconds)), 3600) minutes, seconds = divmod(rem, 60) chunks = [] if hours: chunks.append(str(hours)) min_format = '{:02}' else: min_format = '{}' chunks.append(min_format.format(minutes)) chunks.append('{:02}'.format(seconds)) return prefix + ':'.join(chunks) def format_size(size: typing.Optional[float], base: int = 1024, suffix: str = '') -> str: """Format a byte size so it's human readable. Inspired by http://stackoverflow.com/q/1094841 """ prefixes = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'] if size is None: return '?.??' + suffix for p in prefixes: if -base < size < base: return '{:.02f}{}{}'.format(size, p, suffix) size /= base return '{:.02f}{}{}'.format(size, prefixes[-1], suffix) class FakeIOStream(io.TextIOBase): """A fake file-like stream which calls a function for write-calls.""" def __init__(self, write_func: typing.Callable[[str], int]) -> None: super().__init__() self.write = write_func # type: ignore @contextlib.contextmanager def fake_io(write_func: typing.Callable[[str], int]) -> typing.Iterator[None]: """Run code with stdout and stderr replaced by FakeIOStreams. Args: write_func: The function to call when write is called. """ old_stdout = sys.stdout old_stderr = sys.stderr fake_stderr = FakeIOStream(write_func) fake_stdout = FakeIOStream(write_func) sys.stderr = fake_stderr # type: ignore sys.stdout = fake_stdout # type: ignore try: yield finally: # If the code we did run did change sys.stdout/sys.stderr, we leave it # unchanged. Otherwise, we reset it. if sys.stdout is fake_stdout: # type: ignore sys.stdout = old_stdout if sys.stderr is fake_stderr: # type: ignore sys.stderr = old_stderr @contextlib.contextmanager def disabled_excepthook() -> typing.Iterator[None]: """Run code with the exception hook temporarily disabled.""" old_excepthook = sys.excepthook sys.excepthook = sys.__excepthook__ try: yield finally: # If the code we did run did change sys.excepthook, we leave it # unchanged. Otherwise, we reset it. if sys.excepthook is sys.__excepthook__: sys.excepthook = old_excepthook class prevent_exceptions: # noqa: N801,N806 pylint: disable=invalid-name """Decorator to ignore and log exceptions. This needs to be used for some places where PyQt segfaults on exceptions or silently ignores them. We used to re-raise the exception with a single-shot QTimer in a similar case, but that lead to a strange problem with a KeyError with some random jinja template stuff as content. For now, we only log it, so it doesn't pass 100% silently. This could also be a function, but as a class (with a "wrong" name) it's much cleaner to implement. Attributes: _retval: The value to return in case of an exception. _predicate: The condition which needs to be True to prevent exceptions """ def __init__(self, retval: typing.Any, predicate: bool = True) -> None: """Save decorator arguments. Gets called on parse-time with the decorator arguments. Args: See class attributes. """ self._retval = retval self._predicate = predicate def __call__(self, func: typing.Callable) -> typing.Callable: """Called when a function should be decorated. Args: func: The function to be decorated. Return: The decorated function. """ if not self._predicate: return func retval = self._retval @functools.wraps(func) def wrapper(*args: typing.Any, **kwargs: typing.Any) -> typing.Any: """Call the original function.""" try: return func(*args, **kwargs) except BaseException: log.misc.exception("Error in {}".format(qualname(func))) return retval return wrapper def is_enum(obj: typing.Any) -> bool: """Check if a given object is an enum.""" try: return issubclass(obj, enum.Enum) except TypeError: return False def get_repr(obj: typing.Any, constructor: bool = False, **attrs: typing.Any) -> str: """Get a suitable __repr__ string for an object. Args: obj: The object to get a repr for. constructor: If True, show the Foo(one=1, two=2) form instead of . attrs: The attributes to add. """ cls = qualname(obj.__class__) parts = [] items = sorted(attrs.items()) for name, val in items: parts.append('{}={!r}'.format(name, val)) if constructor: return '{}({})'.format(cls, ', '.join(parts)) else: if parts: return '<{} {}>'.format(cls, ' '.join(parts)) else: return '<{}>'.format(cls) def qualname(obj: typing.Any) -> str: """Get the fully qualified name of an object. Based on twisted.python.reflect.fullyQualifiedName. Should work with: - functools.partial objects - functions - classes - methods - modules """ if isinstance(obj, functools.partial): obj = obj.func if hasattr(obj, '__module__'): prefix = '{}.'.format(obj.__module__) else: prefix = '' if hasattr(obj, '__qualname__'): return '{}{}'.format(prefix, obj.__qualname__) elif hasattr(obj, '__name__'): return '{}{}'.format(prefix, obj.__name__) else: return repr(obj) # The string annotation is a WORKAROUND for a Python 3.5.2 bug: # https://github.com/python/typing/issues/266 def raises(exc: ('typing.Union[' # pylint: disable=bad-docstring-quotes ' typing.Type[BaseException], ' ' typing.Tuple[typing.Type[BaseException]]]'), func: typing.Callable, *args: typing.Any) -> bool: """Check if a function raises a given exception. Args: exc: A single exception or an iterable of exceptions. func: A function to call. *args: The arguments to pass to the function. Returns: True if the exception was raised, False otherwise. """ try: func(*args) except exc: return True else: return False def force_encoding(text: str, encoding: str) -> str: """Make sure a given text is encodable with the given encoding. This replaces all chars not encodable with question marks. """ return text.encode(encoding, errors='replace').decode(encoding) def sanitize_filename(name: str, replacement: typing.Optional[str] = '_') -> str: """Replace invalid filename characters. Note: This should be used for the basename, as it also removes the path separator. Args: name: The filename. replacement: The replacement character (or None). """ if replacement is None: replacement = '' # Remove chars which can't be encoded in the filename encoding. # See https://github.com/qutebrowser/qutebrowser/issues/427 encoding = sys.getfilesystemencoding() name = force_encoding(name, encoding) # See also # https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words if is_windows: bad_chars = '\\/:*?"<>|' elif is_mac: # Colons can be confusing in finder https://superuser.com/a/326627 bad_chars = '/:' else: bad_chars = '/' for bad_char in bad_chars: name = name.replace(bad_char, replacement) return name def set_clipboard(data: str, selection: bool = False) -> None: """Set the clipboard to some given data.""" global fake_clipboard if selection and not supports_selection(): raise SelectionUnsupportedError if log_clipboard: what = 'primary selection' if selection else 'clipboard' log.misc.debug("Setting fake {}: {}".format(what, json.dumps(data))) fake_clipboard = data else: mode = QClipboard.Selection if selection else QClipboard.Clipboard QApplication.clipboard().setText(data, mode=mode) def get_clipboard(selection: bool = False, fallback: bool = False) -> str: """Get data from the clipboard. Args: selection: Use the primary selection. fallback: Fall back to the clipboard if primary selection is unavailable. """ global fake_clipboard if fallback and not selection: raise ValueError("fallback given without selection!") if selection and not supports_selection(): if fallback: selection = False else: raise SelectionUnsupportedError if fake_clipboard is not None: data = fake_clipboard fake_clipboard = None else: mode = QClipboard.Selection if selection else QClipboard.Clipboard data = QApplication.clipboard().text(mode=mode) target = "Primary selection" if selection else "Clipboard" if not data.strip(): raise ClipboardEmptyError("{} is empty.".format(target)) log.misc.debug("{} contained: {!r}".format(target, data)) return data def supports_selection() -> bool: """Check if the OS supports primary selection.""" return QApplication.clipboard().supportsSelection() def random_port() -> int: """Get a random free port.""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost', 0)) port = sock.getsockname()[1] sock.close() return port def open_file(filename: str, cmdline: str = None) -> None: """Open the given file. If cmdline is not given, downloads.open_dispatcher is used. If open_dispatcher is unset, the system's default application is used. Args: filename: The filename to open. cmdline: The command to use as string. A `{}` is expanded to the filename. None means to use the system's default application or `downloads.open_dispatcher` if set. If no `{}` is found, the filename is appended to the cmdline. """ # Import late to avoid circular imports: # - usertypes -> utils -> guiprocess -> message -> usertypes # - usertypes -> utils -> config -> configdata -> configtypes -> # cmdutils -> command -> message -> usertypes from qutebrowser.config import config from qutebrowser.misc import guiprocess from qutebrowser.utils import version, message # the default program to open downloads with - will be empty string # if we want to use the default override = config.val.downloads.open_dispatcher if version.is_sandboxed(): if cmdline: message.error("Cannot spawn download dispatcher from sandbox") return if override: message.warning("Ignoring download dispatcher from config in " "sandbox environment") override = None # precedence order: cmdline > downloads.open_dispatcher > openUrl if cmdline is None and not override: log.misc.debug("Opening {} with the system application" .format(filename)) url = QUrl.fromLocalFile(filename) QDesktopServices.openUrl(url) return if cmdline is None and override: cmdline = override assert cmdline is not None cmd, *args = shlex.split(cmdline) args = [arg.replace('{}', filename) for arg in args] if '{}' not in cmdline: args.append(filename) log.misc.debug("Opening {} with {}" .format(filename, [cmd] + args)) proc = guiprocess.GUIProcess(what='open-file') proc.start_detached(cmd, args) def unused(_arg: typing.Any) -> None: """Function which does nothing to avoid pylint complaining.""" def expand_windows_drive(path: str) -> str: r"""Expand a drive-path like E: into E:\. Does nothing for other paths. Args: path: The path to expand. """ # Usually, "E:" on Windows refers to the current working directory on drive # E:\. The correct way to specifify drive E: is "E:\", but most users # probably don't use the "multiple working directories" feature and expect # "E:" and "E:\" to be equal. if re.fullmatch(r'[A-Z]:', path, re.IGNORECASE): return path + "\\" else: return path def yaml_load(f: typing.Union[str, typing.IO[str]]) -> typing.Any: """Wrapper over yaml.load using the C loader if possible.""" start = datetime.datetime.now() # WORKAROUND for https://github.com/yaml/pyyaml/pull/181 with log.ignore_py_warnings( category=DeprecationWarning, message=r"Using or importing the ABCs from 'collections' instead " r"of from 'collections\.abc' is deprecated.*"): data = yaml.load(f, Loader=YamlLoader) end = datetime.datetime.now() delta = (end - start).total_seconds() deadline = 10 if 'CI' in os.environ else 2 if delta > deadline: # pragma: no cover log.misc.warning( "YAML load took unusually long, please report this at " "https://github.com/qutebrowser/qutebrowser/issues/2777\n" "duration: {}s\n" "PyYAML version: {}\n" "C extension: {}\n" "Stack:\n\n" "{}".format( delta, yaml.__version__, YAML_C_EXT, ''.join(traceback.format_stack()))) return data def yaml_dump(data: typing.Any, f: typing.IO[str] = None) -> typing.Optional[str]: """Wrapper over yaml.dump using the C dumper if possible. Also returns a str instead of bytes. """ yaml_data = yaml.dump(data, f, Dumper=YamlDumper, default_flow_style=False, encoding='utf-8', allow_unicode=True) if yaml_data is None: return None else: return yaml_data.decode('utf-8') def chunk(elems: typing.Sequence, n: int) -> typing.Iterator[typing.Sequence]: """Yield successive n-sized chunks from elems. If elems % n != 0, the last chunk will be smaller. """ if n < 1: raise ValueError("n needs to be at least 1!") for i in range(0, len(elems), n): yield elems[i:i + n] def guess_mimetype(filename: str, fallback: bool = False) -> str: """Guess a mimetype based on a filename. Args: filename: The filename to check. fallback: Fall back to application/octet-stream if unknown. """ mimetype, _encoding = mimetypes.guess_type(filename) if mimetype is None: if fallback: return 'application/octet-stream' else: raise ValueError("Got None mimetype for {}".format(filename)) return mimetype def ceil_log(number: int, base: int) -> int: """Compute max(1, ceil(log(number, base))). Use only integer arithmetic in order to avoid numerical error. """ if number < 1 or base < 2: raise ValueError("math domain error") result = 1 accum = base while accum < number: result += 1 accum *= base return result ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser/utils/version.py0000644000175000017510000004402300000000000023050 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Utilities to show various version information.""" import re import sys import glob import os.path import platform import subprocess import importlib import collections import enum import datetime import getpass import typing import attr import pkg_resources from PyQt5.QtCore import PYQT_VERSION_STR, QLibraryInfo from PyQt5.QtNetwork import QSslSocket from PyQt5.QtGui import (QOpenGLContext, QOpenGLVersionProfile, QOffscreenSurface) from PyQt5.QtWidgets import QApplication try: from PyQt5.QtWebKit import qWebKitVersion except ImportError: # pragma: no cover qWebKitVersion = None # type: ignore # noqa: N816 import qutebrowser from qutebrowser.utils import log, utils, standarddir, usertypes, message from qutebrowser.misc import objects, earlyinit, sql, httpclient, pastebin from qutebrowser.browser import pdfjs from qutebrowser.config import config try: from qutebrowser.browser.webengine import webenginesettings except ImportError: # pragma: no cover webenginesettings = None # type: ignore @attr.s class DistributionInfo: """Information about the running distribution.""" id = attr.ib() # type: typing.Optional[str] parsed = attr.ib() # type: Distribution version = attr.ib() # type: typing.Optional[typing.Tuple[str, ...]] pretty = attr.ib() # type: str pastebin_url = None Distribution = enum.Enum( 'Distribution', ['unknown', 'ubuntu', 'debian', 'void', 'arch', 'gentoo', 'fedora', 'opensuse', 'linuxmint', 'manjaro', 'kde_flatpak']) def distribution() -> typing.Optional[DistributionInfo]: """Get some information about the running Linux distribution. Returns: A DistributionInfo object, or None if no info could be determined. parsed: A Distribution enum member version: A Version object, or None pretty: Always a string (might be "Unknown") """ filename = os.environ.get('QUTE_FAKE_OS_RELEASE', '/etc/os-release') info = {} try: with open(filename, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if (not line) or line.startswith('#'): continue k, v = line.split("=", maxsplit=1) info[k] = v.strip('"') except (OSError, UnicodeDecodeError): return None pretty = info.get('PRETTY_NAME', None) if pretty in ['Linux', None]: # Funtoo has PRETTY_NAME=Linux pretty = info.get('NAME', 'Unknown') assert pretty is not None if 'VERSION_ID' in info: dist_version = pkg_resources.parse_version( info['VERSION_ID'] ) # type: typing.Optional[typing.Tuple[str, ...]] else: dist_version = None dist_id = info.get('ID', None) id_mappings = { 'funtoo': 'gentoo', # does not have ID_LIKE=gentoo 'org.kde.Platform': 'kde_flatpak', } parsed = Distribution.unknown if dist_id is not None: try: parsed = Distribution[id_mappings.get(dist_id, dist_id)] except KeyError: pass return DistributionInfo(parsed=parsed, version=dist_version, pretty=pretty, id=dist_id) def is_sandboxed() -> bool: """Whether the environment has restricted access to the host system.""" current_distro = distribution() if current_distro is None: return False return current_distro.parsed == Distribution.kde_flatpak def _git_str() -> typing.Optional[str]: """Try to find out git version. Return: string containing the git commit ID. None if there was an error or we're not in a git repo. """ # First try via subprocess if possible commit = None if not hasattr(sys, "frozen"): try: gitpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.path.pardir, os.path.pardir) except (NameError, OSError): log.misc.exception("Error while getting git path") else: commit = _git_str_subprocess(gitpath) if commit is not None: return commit # If that fails, check the git-commit-id file. try: return utils.read_file('git-commit-id') except (OSError, ImportError): return None def _git_str_subprocess(gitpath: str) -> typing.Optional[str]: """Try to get the git commit ID and timestamp by calling git. Args: gitpath: The path where the .git folder is. Return: The ID/timestamp on success, None on failure. """ if not os.path.isdir(os.path.join(gitpath, ".git")): return None try: # https://stackoverflow.com/questions/21017300/21017394#21017394 commit_hash = subprocess.run( ['git', 'describe', '--match=NeVeRmAtCh', '--always', '--dirty'], cwd=gitpath, check=True, stdout=subprocess.PIPE).stdout.decode('UTF-8').strip() date = subprocess.run( ['git', 'show', '-s', '--format=%ci', 'HEAD'], cwd=gitpath, check=True, stdout=subprocess.PIPE).stdout.decode('UTF-8').strip() return '{} ({})'.format(commit_hash, date) except (subprocess.CalledProcessError, OSError): return None def _release_info() -> typing.Sequence[typing.Tuple[str, str]]: """Try to gather distribution release information. Return: list of (filename, content) tuples. """ blacklisted = ['ANSI_COLOR=', 'HOME_URL=', 'SUPPORT_URL=', 'BUG_REPORT_URL='] data = [] for fn in glob.glob("/etc/*-release"): lines = [] try: with open(fn, 'r', encoding='utf-8') as f: for line in f.read().strip().splitlines(): if not any(line.startswith(bl) for bl in blacklisted): lines.append(line) if lines: data.append((fn, '\n'.join(lines))) except OSError: log.misc.exception("Error while reading {}.".format(fn)) return data def _module_versions() -> typing.Sequence[str]: """Get versions of optional modules. Return: A list of lines with version info. """ lines = [] modules = collections.OrderedDict([ ('sip', ['SIP_VERSION_STR']), ('colorama', ['VERSION', '__version__']), ('pypeg2', ['__version__']), ('jinja2', ['__version__']), ('pygments', ['__version__']), ('yaml', ['__version__']), ('cssutils', ['__version__']), ('attr', ['__version__']), ('PyQt5.QtWebEngineWidgets', []), ('PyQt5.QtWebEngine', ['PYQT_WEBENGINE_VERSION_STR']), ('PyQt5.QtWebKitWidgets', []), ]) # type: typing.Mapping[str, typing.Sequence[str]] for modname, attributes in modules.items(): try: module = importlib.import_module(modname) except ImportError: text = '{}: no'.format(modname) else: for name in attributes: try: text = '{}: {}'.format(modname, getattr(module, name)) except AttributeError: pass else: break else: text = '{}: yes'.format(modname) lines.append(text) return lines def _path_info() -> typing.Mapping[str, str]: """Get info about important path names. Return: A dictionary of descriptive to actual path names. """ info = { 'config': standarddir.config(), 'data': standarddir.data(), 'cache': standarddir.cache(), 'runtime': standarddir.runtime(), } if standarddir.config() != standarddir.config(auto=True): info['auto config'] = standarddir.config(auto=True) if standarddir.data() != standarddir.data(system=True): info['system data'] = standarddir.data(system=True) return info def _os_info() -> typing.Sequence[str]: """Get operating system info. Return: A list of lines with version info. """ lines = [] releaseinfo = None if utils.is_linux: osver = '' releaseinfo = _release_info() elif utils.is_windows: osver = ', '.join(platform.win32_ver()) elif utils.is_mac: release, info_tpl, machine = platform.mac_ver() if all(not e for e in info_tpl): versioninfo = '' else: versioninfo = '.'.join(info_tpl) osver = ', '.join([e for e in [release, versioninfo, machine] if e]) elif utils.is_posix: osver = ' '.join(platform.uname()) else: osver = '?' lines.append('OS Version: {}'.format(osver)) if releaseinfo is not None: for (fn, data) in releaseinfo: lines += ['', '--- {} ---'.format(fn), data] return lines def _pdfjs_version() -> str: """Get the pdf.js version. Return: A string with the version number. """ try: pdfjs_file, file_path = pdfjs.get_pdfjs_res_and_path('build/pdf.js') except pdfjs.PDFJSNotFound: return 'no' else: pdfjs_file = pdfjs_file.decode('utf-8') version_re = re.compile( r"^ *(PDFJS\.version|var pdfjsVersion) = '([^']+)';$", re.MULTILINE) match = version_re.search(pdfjs_file) if not match: pdfjs_version = 'unknown' else: pdfjs_version = match.group(2) if file_path is None: file_path = 'bundled' return '{} ({})'.format(pdfjs_version, file_path) def _chromium_version() -> str: """Get the Chromium version for QtWebEngine. This can also be checked by looking at this file with the right Qt tag: http://code.qt.io/cgit/qt/qtwebengine.git/tree/tools/scripts/version_resolver.py#n41 Quick reference: Qt 5.7: Chromium 49 49.0.2623.111 (2016-03-31) 5.7.1: Security fixes up to 54.0.2840.87 (2016-11-01) Qt 5.8: Chromium 53 53.0.2785.148 (2016-08-31) 5.8.0: Security fixes up to 55.0.2883.75 (2016-12-01) Qt 5.9: Chromium 56 (LTS) 56.0.2924.122 (2017-01-25) 5.9.8: Security fixes up to 72.0.3626.121 (2019-03-01) Qt 5.10: Chromium 61 61.0.3163.140 (2017-09-05) 5.10.1: Security fixes up to 64.0.3282.140 (2018-02-01) Qt 5.11: Chromium 65 65.0.3325.151 (.1: .230) (2018-03-06) 5.11.3: Security fixes up to 70.0.3538.102 (2018-11-09) Qt 5.12: Chromium 69 (LTS) 69.0.3497.113 (2018-09-27) 5.12.6: Security fixes up to 76.0.3809.87 (2019-07-30) plus fix for CVE-2019-13720 from Chrome 78 Qt 5.13: Chromium 73 73.0.3683.105 (~2019-02-28) 5.13.2: Security fixes up to 77.0.3865.120 (2019-10-10) Qt 5.14: Chromium 77 77.0.3865.129 (~2019-10-10) 5.14.1: Security fixes up to 79.0.3945.117 (2020-01-07) Also see https://www.chromium.org/developers/calendar and https://chromereleases.googleblog.com/ """ if webenginesettings is None: return 'unavailable' # type: ignore if webenginesettings.parsed_user_agent is None: webenginesettings.init_user_agent() assert webenginesettings.parsed_user_agent is not None return webenginesettings.parsed_user_agent.upstream_browser_version def _backend() -> str: """Get the backend line with relevant information.""" if objects.backend == usertypes.Backend.QtWebKit: return 'new QtWebKit (WebKit {})'.format(qWebKitVersion()) else: webengine = usertypes.Backend.QtWebEngine assert objects.backend == webengine, objects.backend return 'QtWebEngine (Chromium {})'.format(_chromium_version()) def _uptime() -> datetime.timedelta: launch_time = QApplication.instance().launch_time time_delta = datetime.datetime.now() - launch_time # Round off microseconds time_delta -= datetime.timedelta(microseconds=time_delta.microseconds) return time_delta def _autoconfig_loaded() -> str: return "yes" if config.instance.yaml_loaded else "no" def _config_py_loaded() -> str: if config.instance.config_py_loaded: return "{} has been loaded".format(standarddir.config_py()) else: return "no config.py was loaded" def version() -> str: """Return a string with various version information.""" lines = ["qutebrowser v{}".format(qutebrowser.__version__)] gitver = _git_str() if gitver is not None: lines.append("Git commit: {}".format(gitver)) lines.append('Backend: {}'.format(_backend())) lines.append('Qt: {}'.format(earlyinit.qt_version())) lines += [ '', '{}: {}'.format(platform.python_implementation(), platform.python_version()), 'PyQt: {}'.format(PYQT_VERSION_STR), '', ] lines += _module_versions() lines += [ 'pdf.js: {}'.format(_pdfjs_version()), 'sqlite: {}'.format(sql.version()), 'QtNetwork SSL: {}\n'.format(QSslSocket.sslLibraryVersionString() if QSslSocket.supportsSsl() else 'no'), ] qapp = QApplication.instance() if qapp: style = qapp.style() lines.append('Style: {}'.format(style.metaObject().className())) importpath = os.path.dirname(os.path.abspath(qutebrowser.__file__)) lines += [ 'Platform: {}, {}'.format(platform.platform(), platform.architecture()[0]), ] dist = distribution() if dist is not None: lines += [ 'Linux distribution: {} ({})'.format(dist.pretty, dist.parsed.name) ] lines += [ 'Frozen: {}'.format(hasattr(sys, 'frozen')), "Imported from {}".format(importpath), "Using Python from {}".format(sys.executable), "Qt library executable path: {}, data path: {}".format( QLibraryInfo.location(QLibraryInfo.LibraryExecutablesPath), QLibraryInfo.location(QLibraryInfo.DataPath) ) ] if not dist or dist.parsed == Distribution.unknown: lines += _os_info() lines += [ '', 'Paths:', ] for name, path in sorted(_path_info().items()): lines += ['{}: {}'.format(name, path)] lines += [ '', 'Autoconfig loaded: {}'.format(_autoconfig_loaded()), 'Config.py: {}'.format(_config_py_loaded()), 'Uptime: {}'.format(_uptime()) ] return '\n'.join(lines) def opengl_vendor() -> typing.Optional[str]: # pragma: no cover """Get the OpenGL vendor used. This returns a string such as 'nouveau' or 'Intel Open Source Technology Center'; or None if the vendor can't be determined. """ assert QApplication.instance() override = os.environ.get('QUTE_FAKE_OPENGL_VENDOR') if override is not None: log.init.debug("Using override {}".format(override)) return override old_context = typing.cast(typing.Optional[QOpenGLContext], QOpenGLContext.currentContext()) old_surface = None if old_context is None else old_context.surface() surface = QOffscreenSurface() surface.create() ctx = QOpenGLContext() ok = ctx.create() if not ok: log.init.debug("Creating context failed!") return None ok = ctx.makeCurrent(surface) if not ok: log.init.debug("Making context current failed!") return None try: if ctx.isOpenGLES(): # Can't use versionFunctions there return None vp = QOpenGLVersionProfile() vp.setVersion(2, 0) try: vf = ctx.versionFunctions(vp) except ImportError as e: log.init.debug("Importing version functions failed: {}".format(e)) return None if vf is None: log.init.debug("Getting version functions failed!") return None return vf.glGetString(vf.GL_VENDOR) finally: ctx.doneCurrent() if old_context and old_surface: old_context.makeCurrent(old_surface) def pastebin_version(pbclient: pastebin.PastebinClient = None) -> None: """Pastebin the version and log the url to messages.""" def _yank_url(url: str) -> None: utils.set_clipboard(url) message.info("Version url {} yanked to clipboard.".format(url)) def _on_paste_version_success(url: str) -> None: assert pbclient is not None global pastebin_url url = url.strip() _yank_url(url) pbclient.deleteLater() pastebin_url = url def _on_paste_version_err(text: str) -> None: assert pbclient is not None message.error("Failed to pastebin version" " info: {}".format(text)) pbclient.deleteLater() if pastebin_url: _yank_url(pastebin_url) return app = QApplication.instance() http_client = httpclient.HTTPClient() misc_api = pastebin.PastebinClient.MISC_API_URL pbclient = pbclient or pastebin.PastebinClient(http_client, parent=app, api_url=misc_api) pbclient.success.connect(_on_paste_version_success) pbclient.error.connect(_on_paste_version_err) pbclient.paste(getpass.getuser(), "qute version info {}".format(qutebrowser.__version__), version(), private=True) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581772733.8847563 qutebrowser-1.10.1/qutebrowser.egg-info/0000755000175000017510000000000000000000000021340 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581772733.0 qutebrowser-1.10.1/qutebrowser.egg-info/PKG-INFO0000644000175000017510000004070400000000000022442 0ustar00florianflorian00000000000000Metadata-Version: 2.1 Name: qutebrowser Version: 1.10.1 Summary: A keyboard-driven, vim-like browser based on PyQt5. Home-page: https://www.qutebrowser.org/ Author: Florian Bruhin Author-email: mail@qutebrowser.org License: GPL Description: // If you are reading this in plaintext or on PyPi: // // A rendered version is available at: // https://github.com/qutebrowser/qutebrowser/blob/master/README.asciidoc qutebrowser =========== // QUTE_WEB_HIDE image:icons/qutebrowser-64x64.png[qutebrowser logo] *A keyboard-driven, vim-like browser based on PyQt5 and Qt.* image:https://travis-ci.org/qutebrowser/qutebrowser.svg?branch=master["Build Status", link="https://travis-ci.org/qutebrowser/qutebrowser"] image:https://ci.appveyor.com/api/projects/status/5pyauww2k68bbow2/branch/master?svg=true["AppVeyor build status", link="https://ci.appveyor.com/project/qutebrowser/qutebrowser"] image:https://codecov.io/github/qutebrowser/qutebrowser/coverage.svg?branch=master["coverage badge",link="https://codecov.io/github/qutebrowser/qutebrowser?branch=master"] link:https://www.qutebrowser.org[website] | link:https://blog.qutebrowser.org[blog] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/faq.asciidoc[FAQ] | https://www.qutebrowser.org/doc/contributing.html[contributing] | link:https://github.com/qutebrowser/qutebrowser/releases[releases] | https://github.com/qutebrowser/qutebrowser/blob/master/doc/install.asciidoc[installing] // QUTE_WEB_HIDE_END qutebrowser is a keyboard-focused browser with a minimal GUI. It's based on Python and PyQt5 and free software, licensed under the GPL. It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl. // QUTE_WEB_HIDE **qutebrowser's primary maintainer, The-Compiler, is currently working part-time on qutebrowser, funded by donations.** To sustain this for a long time, your help is needed! See the https://github.com/sponsors/The-Compiler/[GitHub Sponsors page] for more information. Depending on your sign-up date and how long you keep a certain level, you can get qutebrowser t-shirts, stickers and more! Thanks to the GitHub Sponsors Matching Fund, all donations done via GitHub Sponsors (up to a $5000 total) will be doubled until October 2020. // QUTE_WEB_HIDE_END Screenshots ----------- image:doc/img/main.png["screenshot 1",width=300,link="doc/img/main.png"] image:doc/img/downloads.png["screenshot 2",width=300,link="doc/img/downloads.png"] image:doc/img/completion.png["screenshot 3",width=300,link="doc/img/completion.png"] image:doc/img/hints.png["screenshot 4",width=300,link="doc/img/hints.png"] Downloads --------- See the https://github.com/qutebrowser/qutebrowser/releases[github releases page] for available downloads and the link:doc/install.asciidoc[INSTALL] file for detailed instructions on how to get qutebrowser running on various platforms. Documentation ------------- In addition to the topics mentioned in this README, the following documents are available: * https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png[Key binding cheatsheet]: + image:https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-small.png["qutebrowser key binding cheatsheet",link="https://raw.githubusercontent.com/qutebrowser/qutebrowser/master/doc/img/cheatsheet-big.png"] * link:doc/quickstart.asciidoc[Quick start guide] * https://www.shortcutfoo.com/app/dojos/qutebrowser[Free training course] to remember those key bindings * link:doc/faq.asciidoc[Frequently asked questions] * link:doc/help/configuring.asciidoc[Configuring qutebrowser] * link:doc/contributing.asciidoc[Contributing to qutebrowser] * link:doc/install.asciidoc[Installing qutebrowser] * link:doc/changelog.asciidoc[Change Log] * link:doc/stacktrace.asciidoc[Reporting segfaults] * link:doc/userscripts.asciidoc[How to write userscripts] Getting help ------------ You can get help in the IRC channel irc://irc.freenode.org/#qutebrowser[`#qutebrowser`] on https://freenode.net/[Freenode] (https://webchat.freenode.net/?channels=#qutebrowser[webchat]), or by writing a message to the https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. There's also an https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser-announce[announce-only mailinglist] at mailto:qutebrowser-announce@lists.qutebrowser.org[] (the announcements also get sent to the general qutebrowser@ list). If you're a reddit user, there's a https://www.reddit.com/r/qutebrowser/[/r/qutebrowser] subreddit there. Contributions / Bugs -------------------- You want to contribute to qutebrowser? Awesome! Please read link:doc/contributing.asciidoc[the contribution guidelines] for details and useful hints. If you found a bug or have a feature request, you can report it in several ways: * Use the built-in `:report` command or the automatic crash dialog. * Open an issue in the Github issue tracker. * Write a mail to the https://lists.schokokeks.org/mailman/listinfo.cgi/qutebrowser[mailinglist] at mailto:qutebrowser@lists.qutebrowser.org[]. For security bugs, please contact me directly at mail@qutebrowser.org, GPG ID https://www.the-compiler.org/pubkey.asc[0x916eb0c8fd55a072]. Requirements ------------ The following software and libraries are required to run qutebrowser: * https://www.python.org/[Python] 3.5.2 or newer (3.6 recommended) * https://www.qt.io/[Qt] 5.7.1 or newer (5.14 recommended; support for < 5.9 will be dropped soon) with the following modules: - QtCore / qtbase - QtQuick (part of qtbase in some distributions) - QtSQL (part of qtbase in some distributions) - QtOpenGL - QtWebEngine, or - alternatively QtWebKit - only the link:https://github.com/qtwebkit/qtwebkit/wiki[updated fork] (5.212) is supported. **Note: The latest QtWebKit release is based on old WebKit revision with known unpatched vulnerabilities. Please use it carefully and avoid visiting untrusted websites and using it for transmission of sensitive data.** * https://www.riverbankcomputing.com/software/pyqt/intro[PyQt] 5.7.0 or newer (5.14 recommended, support for < 5.9 will be dropped soon) for Python 3 * https://pypi.python.org/pypi/setuptools/[pkg_resources/setuptools] * https://fdik.org/pyPEG/[pyPEG2] * http://jinja.pocoo.org/[jinja2] * http://pygments.org/[pygments] * https://github.com/yaml/pyyaml[PyYAML] * https://www.attrs.org/[attrs] The following libraries are optional: * http://cthedot.de/cssutils/[cssutils] (for an improved `:download --mhtml` with QtWebKit). * On Windows, https://pypi.python.org/pypi/colorama/[colorama] for colored log output. * http://asciidoc.org/[asciidoc] to generate the documentation for the `:help` command, when using the git repository (rather than a release). See link:doc/install.asciidoc[the documentation] for directions on how to install qutebrowser and its dependencies. Donating -------- **qutebrowser's primary maintainer, The-Compiler, is currently working part-time on qutebrowser, funded by donations.** To sustain this for a long time, your help is needed! See the https://github.com/sponsors/The-Compiler/[GitHub Sponsors page] for more information. Depending on your sign-up date and how long you keep a certain level, you can get qutebrowser t-shirts, stickers and more! Thanks to the GitHub Sponsors Matching Fund, all donations done via GitHub Sponsors (up to a $5000 total) will be doubled until October 2020! Alternatively, the following donation methods are available -- note that eligibility for swag (shirts/stickers/etc.) is handled on a case-by-case basis for those, please mailto:mail@qutebrowser.org[get in touch] for details. * SEPA bank transfer inside Europe (no fee): - Account holder: Florian Bruhin - Country: Switzerland - IBAN (EUR): CH13 0900 0000 9160 4094 6 - IBAN (other): CH80 0900 0000 8711 8587 3 - Bank: PostFinance AG, Mingerstrasse 20, 3030 Bern, Switzerland (BIC: POFICHBEXXX) - If you need any other information: Contact me at mail@qutebrowser.org. * PayPal: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=me%40the-compiler.org&item_name=qutebrowser¤cy_code=CHF&source=url[CHF], https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=me%40the-compiler.org&item_name=qutebrowser¤cy_code=EUR&source=url[EUR], https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=me%40the-compiler.org&item_name=qutebrowser¤cy_code=USD&source=url[USD] * Bitcoin: link:bitcoin:1PMzbcetAHfpxoXww8Bj5XqquHtVvMjJtE[1PMzbcetAHfpxoXww8Bj5XqquHtVvMjJtE] Sponsors -------- Thanks a lot to https://www.macstadium.com/[MacStadium] for supporting qutebrowser with a free hosted Mac Mini via their https://www.macstadium.com/opensource[Open Source Project]. (They don't require including this here - I've just been very happy with their offer, and without them, no macOS releases or tests would exist) Thanks to the https://www.hsr.ch/[HSR Hochschule für Technik Rapperswil], which made it possible to work on qutebrowser extensions as a student research project. image:doc/img/sponsors/macstadium.png["powered by MacStadium",width=200,link="https://www.macstadium.com/"] image:doc/img/sponsors/hsr.png["HSR Hochschule für Technik Rapperswil",link="https://www.hsr.ch/"] Authors ------- qutebrowser's primary author is Florian Bruhin (The Compiler), but qutebrowser wouldn't be what it is without the help of https://github.com/qutebrowser/qutebrowser/graphs/contributors[hundreds of contributors]! Additionally, the following people have contributed graphics: * Jad/link:https://yelostudio.com[yelo] (new icon) * WOFall (original icon) * regines (key binding cheatsheet) Also, thanks to everyone who contributed to one of qutebrowser's link:doc/backers.asciidoc[crowdfunding campaigns]! Similar projects ---------------- Many projects with a similar goal as qutebrowser exist. Most of them were inspirations for qutebrowser in some way, thanks for that! Active ~~~~~~ * https://fanglingsu.github.io/vimb/[vimb] (C, GTK+ with WebKit2) * https://luakit.github.io/luakit/[luakit] (C/Lua, GTK+ with WebKit2) * https://surf.suckless.org/[surf] (C, GTK+ with WebKit1/WebKit2) * https://github.com/next-browser/next/[next] (Lisp, Emacs-like but also offers Vim bindings, various backends - note there was a http://jgkamat.gitlab.io/blog/next-rce.html[critical remote code execution] which was handled quite badly) * https://github.com/parkouss/webmacs/[webmacs] (Python, Emacs-like with QtWebEngine) * Chrome/Chromium addons: https://vimium.github.io/[Vimium], * Firefox addons (based on WebExtensions): https://github.com/tridactyl/tridactyl[Tridactyl], https://addons.mozilla.org/en-GB/firefox/addon/vimium-ff/[Vimium-FF] (experimental), https://github.com/ueokande/vim-vixen[Vim Vixen], https://github.com/amedama41/vvimpulation[VVimpulation] * Addons for Firefox and Chrome: https://github.com/brookhong/Surfingkeys[Surfingkeys], https://github.com/lusakasa/saka-key[Saka Key], https://krabby.netlify.com/[Krabby], https://lydell.github.io/LinkHints/[Link Hints] (hinting only) * Addons for Safari: https://televator.net/vimari/[Vimari] Inactive ~~~~~~~~ * https://bitbucket.org/portix/dwb[dwb] (C, GTK+ with WebKit1, https://bitbucket.org/portix/dwb/pull-requests/22/several-cleanups-to-increase-portability/diff[unmaintained] - main inspiration for qutebrowser) * https://sourceforge.net/p/vimprobable/wiki/Home/[vimprobable] (C, GTK+ with WebKit1) * https://wiki.archlinux.org/index.php?title=Jumanji[jumanji] (C, GTK+ with WebKit1, original site is gone but the Arch Linux wiki has some data) * http://conkeror.org/[conkeror] (Javascript, Emacs-like, XULRunner/Gecko) * https://www.uzbl.org/[uzbl] (C, GTK+ with WebKit1/WebKit2) * https://github.com/conformal/xombrero[xombrero] (C, GTK+ with WebKit1) * https://github.com/linkdd/cream-browser[Cream Browser] (C, GTK+ with WebKit1) * Firefox addons (not based on WebExtensions or no recent activity): http://www.vimperator.org/[Vimperator], http://bug.5digits.org/pentadactyl/index[Pentadactyl], https://github.com/akhodakivskiy/VimFx[VimFx], https://github.com/shinglyu/QuantumVim[QuantumVim] * Chrome/Chromium addons: https://chrome.google.com/webstore/detail/vichrome/gghkfhpblkcmlkmpcpgaajbbiikbhpdi?hl=en[ViChrome], https://github.com/jinzhu/vrome[Vrome], https://github.com/lusakasa/saka-key[Saka Key], https://github.com/1995eaton/chromium-vim[cVim], https://glee.github.io/[GleeBox] License ------- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, 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 . pdf.js ------ qutebrowser optionally uses https://github.com/mozilla/pdf.js/[pdf.js] to display PDF files in the browser. Windows releases come with a bundled pdf.js. pdf.js is distributed under the terms of the Apache License. You can find a copy of the license in `qutebrowser/3rdparty/pdfjs/LICENSE` (in the Windows release or after running `scripts/dev/update_3rdparty.py`), or online https://www.apache.org/licenses/LICENSE-2.0.html[here]. Keywords: pyqt browser web qt webkit qtwebkit qtwebengine Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: X11 Applications :: Qt Classifier: Intended Audience :: End Users/Desktop Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) Classifier: Natural Language :: English Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX :: Linux Classifier: Operating System :: MacOS Classifier: Operating System :: POSIX :: BSD Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Topic :: Internet Classifier: Topic :: Internet :: WWW/HTTP Classifier: Topic :: Internet :: WWW/HTTP :: Browsers Requires-Python: >=3.5 Description-Content-Type: text/plain ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581772733.0 qutebrowser-1.10.1/qutebrowser.egg-info/SOURCES.txt0000644000175000017510000002706300000000000023234 0ustar00florianflorian00000000000000LICENSE MANIFEST.in README.asciidoc qutebrowser.py requirements.txt setup.py tox.ini doc/changelog.asciidoc doc/changelog.html doc/faq.html doc/qutebrowser.1 doc/qutebrowser.1.asciidoc doc/img/cheatsheet-big.png doc/img/cheatsheet-small.png doc/img/completion.png doc/img/downloads.png doc/img/hints.png doc/img/main.png doc/img/sponsors/hsr.png doc/img/sponsors/macstadium.png icons/qutebrowser-128x128.png icons/qutebrowser-16x16.png icons/qutebrowser-24x24.png icons/qutebrowser-256x256.png icons/qutebrowser-32x32.png icons/qutebrowser-48x48.png icons/qutebrowser-512x512.png icons/qutebrowser-64x64.png icons/qutebrowser-96x96.png icons/qutebrowser-all.svg icons/qutebrowser-favicon.svg icons/qutebrowser.icns icons/qutebrowser.ico icons/qutebrowser.svg icons/qutebrowser.xpm misc/Makefile misc/cheatsheet.svg misc/org.qutebrowser.qutebrowser.appdata.xml misc/org.qutebrowser.qutebrowser.desktop misc/apparmor/usr.bin.qutebrowser misc/requirements/README.md misc/requirements/requirements-check-manifest.txt misc/requirements/requirements-check-manifest.txt-raw misc/requirements/requirements-codecov.txt misc/requirements/requirements-codecov.txt-raw misc/requirements/requirements-dev.txt misc/requirements/requirements-dev.txt-raw misc/requirements/requirements-flake8.txt misc/requirements/requirements-flake8.txt-raw misc/requirements/requirements-mypy.txt misc/requirements/requirements-mypy.txt-raw misc/requirements/requirements-pip.txt misc/requirements/requirements-pyinstaller.txt misc/requirements/requirements-pyinstaller.txt-raw misc/requirements/requirements-pylint.txt misc/requirements/requirements-pylint.txt-raw misc/requirements/requirements-pyqt-5.10.txt misc/requirements/requirements-pyqt-5.10.txt-raw misc/requirements/requirements-pyqt-5.11.txt misc/requirements/requirements-pyqt-5.11.txt-raw misc/requirements/requirements-pyqt-5.12.txt misc/requirements/requirements-pyqt-5.12.txt-raw misc/requirements/requirements-pyqt-5.13.txt misc/requirements/requirements-pyqt-5.13.txt-raw misc/requirements/requirements-pyqt-5.14.txt misc/requirements/requirements-pyqt-5.14.txt-raw misc/requirements/requirements-pyqt-5.7.txt misc/requirements/requirements-pyqt-5.7.txt-raw misc/requirements/requirements-pyqt-5.9.txt misc/requirements/requirements-pyqt-5.9.txt-raw misc/requirements/requirements-pyqt.txt misc/requirements/requirements-pyqt.txt-raw misc/requirements/requirements-pyroma.txt misc/requirements/requirements-pyroma.txt-raw misc/requirements/requirements-qutebrowser.txt-raw misc/requirements/requirements-sphinx.txt misc/requirements/requirements-sphinx.txt-raw misc/requirements/requirements-tests-git.txt misc/requirements/requirements-tests.txt misc/requirements/requirements-tests.txt-raw misc/requirements/requirements-tox.txt misc/requirements/requirements-tox.txt-raw misc/requirements/requirements-vulture.txt misc/requirements/requirements-vulture.txt-raw misc/userscripts/README.md misc/userscripts/cast misc/userscripts/dmenu_qutebrowser misc/userscripts/format_json misc/userscripts/getbib misc/userscripts/open_download misc/userscripts/openfeeds misc/userscripts/password_fill misc/userscripts/qute-bitwarden misc/userscripts/qute-keepass misc/userscripts/qute-lastpass misc/userscripts/qute-pass misc/userscripts/qutedmenu misc/userscripts/readability misc/userscripts/readability-js misc/userscripts/ripbang misc/userscripts/rss misc/userscripts/taskadd misc/userscripts/tor_identity misc/userscripts/view_in_mpv qutebrowser/__init__.py qutebrowser/__main__.py qutebrowser/app.py qutebrowser/git-commit-id qutebrowser/qt.py qutebrowser/qutebrowser.py qutebrowser/resources.py qutebrowser.egg-info/PKG-INFO qutebrowser.egg-info/SOURCES.txt qutebrowser.egg-info/dependency_links.txt qutebrowser.egg-info/entry_points.txt qutebrowser.egg-info/requires.txt qutebrowser.egg-info/top_level.txt qutebrowser.egg-info/zip-safe qutebrowser/api/__init__.py qutebrowser/api/apitypes.py qutebrowser/api/cmdutils.py qutebrowser/api/config.py qutebrowser/api/downloads.py qutebrowser/api/hook.py qutebrowser/api/interceptor.py qutebrowser/api/message.py qutebrowser/api/qtutils.py qutebrowser/browser/__init__.py qutebrowser/browser/browsertab.py qutebrowser/browser/commands.py qutebrowser/browser/downloads.py qutebrowser/browser/downloadview.py qutebrowser/browser/eventfilter.py qutebrowser/browser/greasemonkey.py qutebrowser/browser/hints.py qutebrowser/browser/history.py qutebrowser/browser/inspector.py qutebrowser/browser/navigate.py qutebrowser/browser/pdfjs.py qutebrowser/browser/qtnetworkdownloads.py qutebrowser/browser/qutescheme.py qutebrowser/browser/shared.py qutebrowser/browser/signalfilter.py qutebrowser/browser/urlmarks.py qutebrowser/browser/webelem.py qutebrowser/browser/network/__init__.py qutebrowser/browser/network/pac.py qutebrowser/browser/network/proxy.py qutebrowser/browser/webengine/__init__.py qutebrowser/browser/webengine/certificateerror.py qutebrowser/browser/webengine/cookies.py qutebrowser/browser/webengine/interceptor.py qutebrowser/browser/webengine/spell.py qutebrowser/browser/webengine/tabhistory.py qutebrowser/browser/webengine/webenginedownloads.py qutebrowser/browser/webengine/webengineelem.py qutebrowser/browser/webengine/webengineinspector.py qutebrowser/browser/webengine/webenginequtescheme.py qutebrowser/browser/webengine/webenginesettings.py qutebrowser/browser/webengine/webenginetab.py qutebrowser/browser/webengine/webview.py qutebrowser/browser/webkit/__init__.py qutebrowser/browser/webkit/cache.py qutebrowser/browser/webkit/certificateerror.py qutebrowser/browser/webkit/cookies.py qutebrowser/browser/webkit/http.py qutebrowser/browser/webkit/mhtml.py qutebrowser/browser/webkit/rfc6266.py qutebrowser/browser/webkit/tabhistory.py qutebrowser/browser/webkit/webkitelem.py qutebrowser/browser/webkit/webkithistory.py qutebrowser/browser/webkit/webkitinspector.py qutebrowser/browser/webkit/webkitsettings.py qutebrowser/browser/webkit/webkittab.py qutebrowser/browser/webkit/webpage.py qutebrowser/browser/webkit/webview.py qutebrowser/browser/webkit/network/__init__.py qutebrowser/browser/webkit/network/filescheme.py qutebrowser/browser/webkit/network/networkmanager.py qutebrowser/browser/webkit/network/networkreply.py qutebrowser/browser/webkit/network/webkitqutescheme.py qutebrowser/commands/__init__.py qutebrowser/commands/argparser.py qutebrowser/commands/cmdexc.py qutebrowser/commands/command.py qutebrowser/commands/runners.py qutebrowser/commands/userscripts.py qutebrowser/completion/__init__.py qutebrowser/completion/completer.py qutebrowser/completion/completiondelegate.py qutebrowser/completion/completionwidget.py qutebrowser/completion/models/__init__.py qutebrowser/completion/models/completionmodel.py qutebrowser/completion/models/configmodel.py qutebrowser/completion/models/histcategory.py qutebrowser/completion/models/listcategory.py qutebrowser/completion/models/miscmodels.py qutebrowser/completion/models/urlmodel.py qutebrowser/completion/models/util.py qutebrowser/components/__init__.py qutebrowser/components/adblock.py qutebrowser/components/caretcommands.py qutebrowser/components/misccommands.py qutebrowser/components/readlinecommands.py qutebrowser/components/scrollcommands.py qutebrowser/components/zoomcommands.py qutebrowser/config/__init__.py qutebrowser/config/config.py qutebrowser/config/configcache.py qutebrowser/config/configcommands.py qutebrowser/config/configdata.py qutebrowser/config/configdata.yml qutebrowser/config/configdiff.py qutebrowser/config/configexc.py qutebrowser/config/configfiles.py qutebrowser/config/configinit.py qutebrowser/config/configtypes.py qutebrowser/config/configutils.py qutebrowser/config/stylesheet.py qutebrowser/config/websettings.py qutebrowser/extensions/__init__.py qutebrowser/extensions/interceptors.py qutebrowser/extensions/loader.py qutebrowser/html/back.html qutebrowser/html/base.html qutebrowser/html/bindings.html qutebrowser/html/bookmarks.html qutebrowser/html/dirbrowser.html qutebrowser/html/error.html qutebrowser/html/history.html qutebrowser/html/license.html qutebrowser/html/log.html qutebrowser/html/no_pdfjs.html qutebrowser/html/pre.html qutebrowser/html/settings.html qutebrowser/html/styled.html qutebrowser/html/tabs.html qutebrowser/html/version.html qutebrowser/html/warning-old-qt.html qutebrowser/html/warning-webkit.html qutebrowser/html/doc/changelog.html qutebrowser/html/doc/commands.html qutebrowser/html/doc/configuring.html qutebrowser/html/doc/contributing.html qutebrowser/html/doc/faq.html qutebrowser/html/doc/index.html qutebrowser/html/doc/quickstart.html qutebrowser/html/doc/settings.html qutebrowser/html/doc/userscripts.html qutebrowser/html/doc/img/cheatsheet-big.png qutebrowser/html/doc/img/cheatsheet-small.png qutebrowser/img/broken_qutebrowser_logo.png qutebrowser/img/file.svg qutebrowser/img/folder.svg qutebrowser/javascript/caret.js qutebrowser/javascript/global_wrapper.js qutebrowser/javascript/greasemonkey_wrapper.js qutebrowser/javascript/history.js qutebrowser/javascript/pac_utils.js qutebrowser/javascript/position_caret.js qutebrowser/javascript/print.js qutebrowser/javascript/scroll.js qutebrowser/javascript/stylesheet.js qutebrowser/javascript/webelem.js qutebrowser/keyinput/__init__.py qutebrowser/keyinput/basekeyparser.py qutebrowser/keyinput/eventfilter.py qutebrowser/keyinput/keyutils.py qutebrowser/keyinput/macros.py qutebrowser/keyinput/modeman.py qutebrowser/keyinput/modeparsers.py qutebrowser/mainwindow/__init__.py qutebrowser/mainwindow/mainwindow.py qutebrowser/mainwindow/messageview.py qutebrowser/mainwindow/prompt.py qutebrowser/mainwindow/tabbedbrowser.py qutebrowser/mainwindow/tabwidget.py qutebrowser/mainwindow/statusbar/__init__.py qutebrowser/mainwindow/statusbar/backforward.py qutebrowser/mainwindow/statusbar/bar.py qutebrowser/mainwindow/statusbar/command.py qutebrowser/mainwindow/statusbar/keystring.py qutebrowser/mainwindow/statusbar/percentage.py qutebrowser/mainwindow/statusbar/progress.py qutebrowser/mainwindow/statusbar/tabindex.py qutebrowser/mainwindow/statusbar/text.py qutebrowser/mainwindow/statusbar/textbase.py qutebrowser/mainwindow/statusbar/url.py qutebrowser/misc/__init__.py qutebrowser/misc/autoupdate.py qutebrowser/misc/backendproblem.py qutebrowser/misc/checkpyver.py qutebrowser/misc/cmdhistory.py qutebrowser/misc/consolewidget.py qutebrowser/misc/crashdialog.py qutebrowser/misc/crashsignal.py qutebrowser/misc/debugcachestats.py qutebrowser/misc/earlyinit.py qutebrowser/misc/editor.py qutebrowser/misc/guiprocess.py qutebrowser/misc/httpclient.py qutebrowser/misc/ipc.py qutebrowser/misc/keyhintwidget.py qutebrowser/misc/lineparser.py qutebrowser/misc/miscwidgets.py qutebrowser/misc/msgbox.py qutebrowser/misc/objects.py qutebrowser/misc/pastebin.py qutebrowser/misc/quitter.py qutebrowser/misc/savemanager.py qutebrowser/misc/sessions.py qutebrowser/misc/split.py qutebrowser/misc/sql.py qutebrowser/misc/throttle.py qutebrowser/misc/utilcmds.py qutebrowser/utils/__init__.py qutebrowser/utils/debug.py qutebrowser/utils/docutils.py qutebrowser/utils/error.py qutebrowser/utils/javascript.py qutebrowser/utils/jinja.py qutebrowser/utils/log.py qutebrowser/utils/message.py qutebrowser/utils/objreg.py qutebrowser/utils/qtutils.py qutebrowser/utils/standarddir.py qutebrowser/utils/testfile qutebrowser/utils/urlmatch.py qutebrowser/utils/urlutils.py qutebrowser/utils/usertypes.py qutebrowser/utils/utils.py qutebrowser/utils/version.py scripts/__init__.py scripts/cycle-inputs.js scripts/dictcli.py scripts/hist_importer.py scripts/hostblock_blame.py scripts/importer.py scripts/keytester.py scripts/link_pyqt.py scripts/mkvenv.py scripts/open_url_in_instance.sh scripts/setupcommon.py scripts/utils.py scripts/testbrowser/testbrowser_webengine.py scripts/testbrowser/testbrowser_webkit.py././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581772733.0 qutebrowser-1.10.1/qutebrowser.egg-info/dependency_links.txt0000644000175000017510000000000100000000000025406 0ustar00florianflorian00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581772733.0 qutebrowser-1.10.1/qutebrowser.egg-info/entry_points.txt0000644000175000017510000000007200000000000024635 0ustar00florianflorian00000000000000[gui_scripts] qutebrowser = qutebrowser.qutebrowser:main ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581772733.0 qutebrowser-1.10.1/qutebrowser.egg-info/requires.txt0000644000175000017510000000004400000000000023736 0ustar00florianflorian00000000000000pypeg2 jinja2 pygments PyYAML attrs ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581772733.0 qutebrowser-1.10.1/qutebrowser.egg-info/top_level.txt0000644000175000017510000000001400000000000024065 0ustar00florianflorian00000000000000qutebrowser ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1569428212.0 qutebrowser-1.10.1/qutebrowser.egg-info/zip-safe0000644000175000017510000000000100000000000022770 0ustar00florianflorian00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/qutebrowser.py0000755000175000017510000000172300000000000020226 0ustar00florianflorian00000000000000#!/usr/bin/env python3 # vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2014-2020 Florian Bruhin (The Compiler) # # This file is part of qutebrowser. # # qutebrowser 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. # # qutebrowser 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 qutebrowser. If not, see . """Simple launcher for qutebrowser.""" import sys import qutebrowser.qutebrowser if __name__ == '__main__': sys.exit(qutebrowser.qutebrowser.main()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1581680939.0 qutebrowser-1.10.1/requirements.txt0000644000175000017510000000031300000000000020545 0ustar00florianflorian00000000000000# This file is automatically generated by scripts/dev/recompile_requirements.py attrs==19.3.0 colorama==0.4.3 cssutils==1.0.2 Jinja2==2.11.1 MarkupSafe==1.1.1 Pygments==2.5.2 pyPEG2==2.15.2 PyYAML==5.3 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1581772733.9114225 qutebrowser-1.10.1/scripts/0000755000175000017510000000000000000000000016753 5ustar00florianflorian00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158378.0 qutebrowser-1.10.1/scripts/__init__.py0000644000175000017510000000012300000000000021060 0ustar00florianflorian00000000000000# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: """Various utility scripts.""" ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1578158378.0 qutebrowser-1.10.1/scripts/cycle-inputs.js0000644000175000017510000000246300000000000021735 0ustar00florianflorian00000000000000/* Cycle text boxes. * works with the types defined in 'types'. * Note: Does not work for