+++ /dev/null
-
- GNU GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 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>.
-
-
+++ /dev/null
-include README LICENSE drlaunch_widget.py
-recursive-include src *.py
-recursive-include misc *
--- /dev/null
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 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>.
+
+
--- /dev/null
+include README LICENSE drlaunch_widget.py
+recursive-include src *.py
+recursive-include misc *
--- /dev/null
+begin-base64 644 drlaunch-48.png
+iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgI
+fAhkiAAAAAlwSFlzAAADOgAAAzoBWYNI0gAAABl0RVh0U29mdHdhcmUAd3d3
+Lmlua3NjYXBlLm9yZ5vuPBoAABTMSURBVGiBfZp7jCTXdd5/dep2dfW7e3pm
+Z2aH+9ZyuVy+SVEiRdGULJMSxTCBEtiyHEmUIzkRJASxDdiKg4Q0HCGWYSCw
+HQUGpBgCIcs0JAc2YjhKINA0RSzlXZErkibXpLxe7mN2dnYePf2qrr5961T+
+uD2zXJHIHTR6pqv71r3nfuf7vnN6AuAJAMRgCkUMGQCqyg3HbqSrdZbPvoKx
+I/wQdGYRXT8HCKaxBBMLmnL9/iVe6wmufZAHbtuHXDnHMxe3uO/wYbaimF0h
+PPP0X7Nn9y5WxsqDh+aw6gCIQ7AqkAsiMJgoiVNEoG5Ac4PNQAS0vZ+4FLPp
+ILjjY5/PT794kv3VIaViCWNiUIdTxySMkaiASpFS2oNcARgFhnY1ZmNrk1IY
+QRhBrmgAzev2c7ZjubBuufezv85zf/o1vvTlJ3jx61/hwsYKtWKEjJVd7RYS
+CHaSEBmhFIKIIcCQ5UDuGKlDFaIALqURCyXh8hgGLiW64R4+9HOfI5DQ5Efv
+uhutzOJWzmLiGoz7OAIKcZnRqEurVmE0dFSaMwxTpVKBja2E9kyV4UYP4jqV
+SOmuLdNot1lYuo4zKwPOrFvu++mHeeHk07yrsEXuLJ0E4nwCi4fYt7TE+X6P
+exarKBAHhrhguJLCxjChXRbqIXQcOCI0F9YtmEmPpLOKu+0Rwpl264nFhQWG
+E4VQ0MEGqhPIHFpsImEROymg4hiPEmgvkK5foVE2hHZCYh1qu4wHW5AL47FC
+YIgmA85fXuHCUKlvvklMSlCcIZuM0IVDHG7XOHfpMlGjzWQyoVUqkmSQqYdQ
+IwrZmAT0HJTCkHEWUC/kDGyOhIZ64DC1GYxgEMAkPdyoCyI4pxgMZrgGOdA+
+QDqyyHiE664TT0bEUkbqTbSQUJuk9HsbCIrJHN3NLUoRNO58hP7JP0da89Tq
+ZTr9ASaIaGRDtpIc01+mWYuwCBcGXcpG6OXqT0OEOIDNrR7tw0fRsSMWQ90o
+NjBcmRQYbVkMOR7D1SakQ5xLAQMh04xRSDY5tLSLyAiCQd0M5GDKZZYvniNe
+XEICS7/ThUIE4yFEFfp//XXYew/ajKARQ2cTXMb773o/t992O71BD0E8aaBI
+Lmjg80xyIa7EnDx5kiQHQsEIGCNc6jtmIoNWDAaAScK+Vp0zlx1gEANkDooN
+MAZ6Xb715F+RDBOsddjUMRr758e/8gTrm5v0t7oYiTwjBX5J99/7Ps50HenK
+afpuEdwYAkOr1WJsxzz5zSeRTNgeWZ6hU6IggH1L+zh48CDJcELNCAaoCVQL
+EAfCKy//AFEDk8mE18+dh6hAHEVEhQoEQpp0sYHBitKo1CATojAmimOKpYhC
+KSLpp4y31ljcfysaGtKoSBpAHpV55pnvcuHUM0TlNm7cI81SUjdAjPDQQw/x
+6MOPMrTDnYfLHDL90UxJJyljHaN5ykIJJIQJymLsuJz0aR+5DUN11u94tEFr
+YR/9YcIX/gW8+1iJI3sCim6IHUUA9HpbxMUqmKtRi4wipRqDceLDVvCUyngI
+xJBbdNwBMRDGyDTCUSHiEz//Cf7yf/8lw2TIOw2fDRAJLA+VSkGoGKFroVws
+sjpKkSh3OwLVubIMoz4AexZiTp9J33FigOHQ39T2B+igw3iQQjZChhugDkwR
+jj0ACIwGIJEXwbjt7xYIe/bu4f7334+IvOM9tvNjY6QUpznQncDAOrrDlEY5
+QuzEUWnOeS4KDNpcRNUxPyMstiPOrVjGFpwqmisOhwQhF8+fRxBcoLiohusv
+017YR2NmDuMygjCCv/seBA4NDJI7TGiYLynkV6P7i5/6RW46dhNGfDqqKqrq
+gRQIhaDAfCWiarxaz8ewpxpzQ7vKexaqiGSKm1hMqQHqKGUJqhMkz1mcLbOx
+pVxed37iXFEcEoBMI3Tk8FHsYAOnysbyOUZjxRgDdgCzRyCPUHWICMZZuhtd
+v4Hczzc/P89jn3qM+dl5j/3pBgBE/Cai0G/OBLBlIc0MayPlhU2HME5Q56C6
+CynW6HbWkDDaOcY7jsYMR37CmfYM5bgMwO7FPQA89tgv8bGPfRLnLFRblHa9
+y38wEKR/EUQ9jADiGMKrcNk+hX179/HIP3mEYlS8FkO5fxo4iEJwCqsjy0wR
+yoUIsWOEQImrdRisI6YMcQvN9FosTm9aqVaIpjdpNJsAfOPJ/87rp3+IKUQw
+7DA6d8prhxh0PMKvcUqtqfXXtjcwPQWnjjvvvJM777pzSsHTMf09FkjVy9Ji
+HNGbQCWEcrWCcZU6/Y0V0tEmRgyxCMbUcBKiOmSUCE4t7oXPkskenj014m/e
+UDRzEEaceP44susIc8fup/Pas6SZA4RYDFKI0ElCVFnAZQ4rgjrr4SCGiIie
+65FOUjLN+NDPfIjNziZvvP4GTh02s4x1zGBi6VjDUsUwE8NLmynxJGVfyWEY
+bML8DD5UCtU24HaC8NI/Drjj6BGi3t8h6Yucednxve+D6nShJoL1H9PfOAuF
+Coz7/vN2iLoUWksw7kFcRp1FAtmJrObThHbKOBsjgfC+e99Hd6vL8sryDguV
+C9AsGGzuV3agDr0urKYgIG85NoFBB4pVAF57M+XI/hgmK1Ba9G9xDuIGjb23
++g0XKhC3ICpCNvaUaKY5JAI3PTCdX67CIvD4VvULV6cwATuxRCbijtvuoFar
+7QRxy/qprELifC7kwFwM0jxwjFBCTGAgV0+Xoy02NhwhhlbdIIHi3ADiRc9C
+oz7DC69iggiXTXDqcKEBVSQ3mEwJCiVQC8/+Cc6lSOBhI+U25OBy55NY/UZs
+ZknHKYN0AEC9UYfQ60WzYLbxQTWEKDBUQkOraDCDzR6Li2VMbnD1Gq7X5W9f
+HnN5dcSuVsT3TiTgDG68CnmNU1fmMdEYMiBzuCxFd+1BL5/H5HgKnQDOAjE0
+W96oBYLZfRjSFDLY6mzR6/ZI+gmjoU/2uBgzPztPuVqm2W5SLpV58+ybrCcj
+zASqRXA5DJwhzw27CgZTrtY9ZgM8sLAcP+U4ERhMOM2F3GFzg+Z9yJW41PLx
+yBwuc9DfQnTszzV3UDQQGq/IvQ1oe/Vl8yKNxb089e2nePr7z0BcJlIBm9Ka
+m2djfYVyo0FqyhyaqXJ2PcGNuizt3rMNdqzCrhg2Uw8rk3bWoNbyGwiNBxgC
+gUJYhPEIhB1xAeN9jil5TneAS71YBV5wyBQyC3uPwNYVxExfL87gTERUrnPm
+/GVqC/sZdzdhkmDWNkl7axBV2HPj7SwPoFYvMzNbRwDN/QNg3SpxCK/3QGbf
++zDD0RANFdQS1+aJjSCmRGr7pFhStcRGiEs1YiOkuSWd9EkJiItl5mZ3ezaq
+1rCZhYLQsQbiNsZaKJSJyhGyaw76V+hvrnHs5jvZu7hIunUBOx6SJAMQw1x7
+lpWXf4C6hDh0vLTew+UpIpBmUA6hHFh66YB7bj1MIA98Jm+/9l12X7dAqVj0
+nkR80kzGV11isVCcMokynox3ip1Wa55ksOVpsVBmknZJkpR/vHCRsVVoHIKD
+N3Bo/SSLC4uMAkPRelqZn5/n/MU3vbgFIeVikaBYQpyilSpGHUSGmbhEVDC4
+DPJACSXjyA238K+++KsEBJKTC1FsqNUqCBEUykhrHrv6+lRVBVOsg00AwQV4
+fKvzRUwgUCijuYOCoXvpAhqVUNsluu52dO1NKMRUdu9DSruJLr+AjhMQg8vs
+TrdDAoOU6pibH+DxTz3CN/++wydumud81/JTS2U6I3ABLDbLzNxyH9fXIACT
+i5kqZAgStVDb9UmaG8i9TkihgFavg86P/c0KJXQyRMptNOn4nCGCwMHMHsyw
+h0uHPrFFMSZCKaLNJtFggAsN2l1FoirkGepGSGDQ625k7tCtbMzs4ff/w68g
+ofKdly7x+IPv4kC1RBxCO4LhBH7vDTCId3+lco1ypeytbGsJ0tQnX65QruMG
+PWS2jQZbGNRHPa+iUYTGAjkIBrnuMJ3E4jaW/emFCkGEG1vaMyXEpdCsA4ru
+vZ9osIrLUphMdSFZRjt12udP8Ox/6/DBjzxK/fv/l83WgyQGrAOJqzz80/fy
+6QMQEJp8adc8e/bu8aYt5+oQMy3SEyjVPWzGiW8CZJa3jcDb3yQZ8PqPzzA0
+MfTWAMOxG2+gUa9eO3+AL3TU+oCoUm826XW99SBzNBtNorhGbBwh0Fcom5g7
+bj7KF/7dLxOUKrX85puO0u/1cc75ifAKqGEIeeYL/EoLojJsre5gdtsSAFCq
+QOK7EUWxjDXn3MUBrjpH213gwIF9dDY6UKyBHSFRETUxphTjuhtQrPlGVnuB
+C92EohiG3XWo1Ln5yGHmSxFGIMkgEstga8A9H34UiYsROTnpxIuZEcGIoLnD
+2RFu4tuMpjaLcSkmwFsH9SK2/X4zHkGgOIHxJKNajHCjLfTyyxgR+v0+Lld0
+0se05hFyTNrFbizjCNC0T2djjQtn36SGMlw7T61WRkplTp0+zXLiOL2WIDmQ
+KxIqp//hDGa7tsLINV59mzJBfYFz5exP4EV52wgM1Gdwgx4wQd0Ir3Rm56Q0
+U0i6/lTLLSqR0O/2qNVrDPsDxI3pr54DdXRXhlCdY+ngERIHi40yum0GA1iM
+HeJhoFcV+JoxbTplUzsIsHDo2uth8eq13MHqGWTUnV7fzpNtez7d9CT1qh/G
+9NdXmJtt0h2kSJD7OaajtbBEqz3D8sVlqpMBG6nvCbn8aqyNCYRCoUAcGFyu
+pM5i5m4lmrubKAcNBNyYdNSHDHQyQXYd8rZaHT1rUbtMHFzCRCXiAIgiAuxO
+5F2mvlgKY8ghdWACxY22iJduZv+eOf71Zz/IT933bmxqWV5d4alv/zmvnDpJ
+f+RozR7kpdOvMnfgCGfPd7j90AE0j9E83m6MRlBr+FogN5iF9xJd/xmsdWBH
+4FJIBmDHqLWMbQo2RW2KlRGkzxO7M1DIvK8adKFSRIKphwqmTiyfPgKHmzgE
+5dd/6ZN89MH7cdYxTL3yt1stPvfYL/D6XXfxld//KsN/OAlz+5ht1tnorKGZ
+o2bA5CBeaCJIh2/B/f9/FIOExfn47Rdy9YvPPRw10ylTOb9wZWoaS4Bw77vv
+5p8/8iAvn4cvPTXioSe2+JnfuMDvfXsNgIPXH+RXvvBZnBHiyHD+4gqlWpug
+4AsmMWA0VzTt+a4CPpclMGgAmk8AX+RIINhkk48/PMt9H7ifgVP++JuvcfyZ
+s4iYaRr4At1vYCpiChIaVH1PiekXFlpu8Pjjv8mLF+B/PKc8eEz4j/+sibNV
+/uDPLvDHf7VGkvT4+IMHeOD++3jub55Ddx/m6Pwso8wRq2M4cb7pkWWZvzEe
+qxIIGsBg9Qy//U97/J//vMTP3j+DcVs8+uEb/bcxOczOV6ZdNUHEAOLpNVfy
+PAedagqy0xRzKBLAIx98H/uuW+S/Pi382keUL/+F5WO/vcWvfX2NZ3+Usm8+
+5gcvbZBax4c+8ADSaNOotkjTlALK2thhnUOuQubt0Ln7ENx95wEAPvPRFoPB
+tT3MUslc/axOo16sscNm4tsrb2U3CQREOHTIs9m5DZhvQKsMX/lknffeUObA
+oqHdMBzZW2WjbylXy+ggIWqUOdAo0xn7IOTBNbxp+MnxwzNw4gXP/1/7Xx2i
+6Nr3jEaTq39sNwbG/bc0CWJvBnFeIwIz7URcDVZt2ss6PC/88IzjnhtLHL4u
+5kdvJFxaS6ltB8mNcJfOsppYmkUhEhgrSFSa8TQa+kilzuHUYhSqCzfwpb9o
+8tH/tMJ3vr+OmTnMH/zh0yB+gs0rKeoUVYfLFHIhlog4MARBCLlFRHDq24Jx
+YIglIp0o3/rTp3A4/uV7hN/4M8Pv/kKFVlX57gtdFMeR/RH9kdKsxJx+5TQO
+ZW0wwE4s86WYmolZLMUYHfWAuWtN1tsEbfqqGJ5/o8nzv/wsszNlli9sveUA
+S6DZW/yRL0s1d95qb59K6BV/5fIaJ06+yM/fdQvPvwZf+MaImxemPJjBl79x
+jt/6nIfZ8ePHfcma9LgyStnTrGIVbKbItnkDpscN4HCjDm6wAoBmY9R5/Gu6
+wSQos7Kaounq1c+6ocd84NduCgYpVqYLn8IgZ6r4ioTCb/7WfwHgdz4OH77F
+kKRwqeOQEH733x7i8L4qp069xKunXyUqN1A3oTweEpupyckF0dx6issVAm/O
+JIj4/COL3F5/BUW5bSmhOXgOJORXP3kX6eXjuFz53M8dQ9MVb6NFfF9p2ut0
+E4umE2+88KzlcsUF6u8Rhiy/+WP+zRe/yIkTJ3j4duHzHy7z7392jk8/NEez
+kvKd7/xPvvqHX8UEBi1EmGKNlUS5nDgmucPlDqO5eBrNLSYw3om++jVWn+sy
+eP0F7Okn6F5/lGD5MnH6LeyxT1O69EfE3Zho+CnaW08SxzGYCKdeMwgj8jyb
+Jq/fnNcB31KLQzOFm3D8B8c5/qNXWKyXeeihj6DA+uVLfO/Zv8VmIyQUbABx
+OoFiEQKIjSMIHOXQEbTa7fxdhw6yvrmJacxhht6I+cW4nXyIjdkp5FOXsq3a
+sbmqyN5mexVu1quc+tEp7xp3LdFsNUmtRUJDFABRBbIx6TiB0hy4ASabYMJp
+hxDBhUUYd5CoSoRAY47KzG6O7auhowFm9xEM270cMZD03zF5dzRix26/c5Jj
+4mnhPx2BQUqNab2s7FjrQgnsVFMCQTQFN2JnLXENsUOoNJHI0C7FSGM3aTKA
+QoHlfspMAE///Zp3o8VCkRjBTRzpdMEmEOIw2mGn1Flv+ly64yoJIHXb9tdh
+AkNMBAEEQQAqaNrFxnNEUcz0/0hIR0OoVmAwJC5WYDL23+Sg2AxIh0ixQhzV
+oVpHspRBALNlYfn8q+y+5U7W+13ec+thpNPdIElSSpXSNFJTQxfoVXHO8ZFz
+Foh+4vXt0zC+ixFCu91mdW3KUGqwNqUUF32i6/REB33vnzSDxhyUK/5S4HtE
+YvvUCsCVs4gRxstvcGEkEEZcWl6mXjAk1SUCjMkrpQpHDh3ypgu/CZk2ZLd5
+3eVXoWTeQr1XXxck8F/OdXt9zi+fnyq1V94D+w4wN9uGaUlKYCD3p4aJ/P9m
+qC9rFRATIZUFmGxRLRq2OpsQlYniMtKe5z3vfT83f+BR/h9cGEWXakoA8AAA
+AABJRU5ErkJggg==
+====
--- /dev/null
+[Desktop Entry]
+Name=DrLaunch
+Comment=Application launcer widget with portrait mode
+Type=python
+X-Path=drlaunch_widget.py
+X-Multiple=true
--- /dev/null
+#convert drlaunch-64x64.png -scale 48x48 -depth 8 drlaunch-48x48.png
+uuencode -m drlaunch-48.png drlaunch-48.png > drlaunch-48.base64
--- /dev/null
+#!/bin/sh
+
+VERSION=`cd drlaunch/src ; python2.5 -c "import config ;print config.version"`
+
+if [ -z "$VERSION" ] ; then
+ echo "Version not found"
+ exit 1
+fi
+
+D="drlaunch-$VERSION"
+
+rm -rf tmp
+mkdir tmp
+cp -pR drlaunch "tmp/$D"
+
+FN="drlaunch-$VERSION.tar.gz"
+
+echo "Creating $FN"
+tar -zcf $FN --exclude ".svn" --exclude ".*.swp" -C tmp $D
+
+rm -rf tmp
+
--- /dev/null
+#!/bin/bash
+
+rm MANIFEST dist/*.tar.gz
+python2.5 setup.py sdist
+
+for i in dist/drlaunch-*.tar.gz ; do
+ t=$(basename $i)
+ ver=${t#drlaunch-}
+ ver=${ver%.tar.gz}
+ if ! [ -d "../$ver" ] ; then
+ echo "New version: $ver"
+ cp ../drlaunch/dist/$t ..
+# mkdir "../$ver"
+# cp ../drlaunch/dist/$t ../$ver/$t
+# ln -s $t ../$ver/drlaunch_$ver.orig.tar.gz
+ fi
+done
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of maegirls.
+#
+# maegirls 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.
+#
+# maegirls 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 maegirls. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: setup.py 2266 2010-02-21 19:33:27Z v13 $
+
+from distutils.core import setup
+
+setup(
+ name='drlaunch',
+ version="1.0",
+ description="DrLaunch",
+ author="Stefanos Harhalakis",
+ author_email="v13@v13.gr",
+ url="not-available-yet",
+ packages=['drlaunch', 'drlaunch.xdg'],
+ package_dir={'drlaunch': 'src'},
+# scripts=["maegirls"],
+# data_files=[
+# ("share/maegirls/translations", i18n_qm_files)
+# ],
+ long_description="DrLaunch - Application launcher widget with portrait mode"
+ )
+
+# py_modules=['src/core.py', 'src/graph.py', 'src/wifi-su.py',
+# 'src/wifi.py', 'src/win.py'],
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+*.pyo
+*.pyc
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of DrLaunch.
+#
+# DrLaunch 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.
+#
+# DrLaunch 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 DrLaunch. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+from gtk import AboutDialog
+import config
+from about0 import HeAboutDialog
+from portrait import FremantleRotation
+
+class DlgAbout(HeAboutDialog, FremantleRotation):
+ def __init__(self):
+ HeAboutDialog.__init__(self)
+ FremantleRotation.__init__(self, "DrlaunchPlugin",
+ mode=FremantleRotation.AUTOMATIC)
+
+ @classmethod
+ def present2(cls, parent):
+ _copyright="""\
+Copyright (C) 2010 Stefanos Harhalakis <v13@v13.gr>
+Copyright (C) 2005-2010 Thomas Perl and the gPodder Team (for about0.py
+and portrait.py)
+"""
+ _description="""
+DrLaunch is a desktop launcher widget that
+supports portrait mode by rotating program
+icons. Whenever the device is rotated, icons
+are also rotated.
+
+Thanks to Thomas Perl and the gPodder Team.
+"""
+
+ _license="""\
+Send bug reports and feature requests to maemo's bugzilla:
+https://bugs.maemo.org/enter_bug.cgi?product=DrLaunch (preferred)
+or directly to Stefanos Harhalakis <v13@v13.gr>
+
+DrLaunch 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.
+
+DrLaunch 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 DrLaunch. If not, see <http://www.gnu.org/licenses/>.
+
+DrLaunch uses a modified version of about.py from gpodder which is also
+distributed under the terms of the GPLv3 license. It also uses a
+modified version of portrait.py from gpodder which is also distributed
+under the terms of the GPLv3 license.
+"""
+
+ args={
+ "app_name": "DrLaunch",
+ "version": config.version,
+ "copyright": _copyright + "\n" + _license,
+ "description": _description,
+ "bugtracker_url": "https://bugs.maemo.org/enter_bug.cgi?product=DrLaunch",
+ "parent": parent,
+ }
+
+ cls.present(**args)
+
+ def set_icon_name(self, icon_name):
+ pass
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# gPodder - A media aggregator and podcast client
+# Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
+#
+# gPodder 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.
+#
+# gPodder 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/>.
+#
+
+# Python implementation of HeAboutDialog from hildon-extras
+# Copyright (c) 2010-04-11 Thomas Perl <thp@thpinfo.com>
+
+import hildon
+import gtk
+import dbus
+
+_ = str
+
+class HeAboutDialog(gtk.Dialog):
+ RESPONSE_WEBSITE, \
+ RESPONSE_BUGTRACKER, \
+ RESPONSE_DONATE = range(3)
+
+ def __init__(self):
+ gtk.Dialog.__init__(self)
+
+ self.website_url = None
+ self.bugtracker_url = None
+ self.donate_url = None
+
+ self.set_title(_('About'))
+
+ self.image_icon = gtk.Image()
+ self.label_app_name = gtk.Label()
+ self.label_version = gtk.Label()
+ self.label_description = gtk.Label()
+ self.label_copyright = gtk.Label()
+ self.table_layout = gtk.Table(3, 3, False)
+
+ hildon.hildon_helper_set_logical_font(self.label_app_name, 'X-LargeSystemFont')
+ hildon.hildon_helper_set_logical_font(self.label_version, 'LargeSystemFont')
+ hildon.hildon_helper_set_logical_font(self.label_copyright, 'SmallSystemFont')
+ hildon.hildon_helper_set_logical_color(self.label_copyright, gtk.RC_FG, gtk.STATE_NORMAL, 'SecondaryTextColor')
+
+ self.label_app_name.set_alignment(0, 1)
+ self.label_version.set_alignment(0, 1)
+ self.label_description.set_alignment(0, 0)
+ self.label_copyright.set_alignment(0, 1)
+ self.label_version.set_padding(10, 0)
+ self.label_copyright.set_padding(0, 5)
+ self.image_icon.set_padding(5, 5)
+
+ #content_area = self.get_content_area() # Starting with PyGTK 2.14
+ self.set_size_request(800,800)
+ pa=hildon.PannableArea()
+ pa.set_property('mov-mode',
+ hildon.MOVEMENT_MODE_HORIZ | hildon.MOVEMENT_MODE_VERT )
+ vbox=gtk.VBox()
+ self.vbox.add(pa)
+ pa.add_with_viewport(vbox)
+ content_area = vbox
+
+ self.table_layout.attach(self.image_icon, 0, 1, 0, 2, 0, gtk.EXPAND, 0, 0)
+ self.table_layout.attach(self.label_app_name, 1, 2, 0, 1, 0, gtk.EXPAND | gtk.FILL, 0, 0)
+ self.table_layout.attach(self.label_version, 2, 3, 0, 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, 0, 0)
+ self.table_layout.attach(self.label_description, 1, 3, 1, 2, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, 0, 0)
+ self.table_layout.attach(self.label_copyright, 0, 3, 2, 3, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, 0, 0)
+ content_area.add(self.table_layout)
+ self.connect('response', self._on_response)
+ self.show_all()
+
+ def _on_response(self, dialog, response_id):
+ if response_id == HeAboutDialog.RESPONSE_WEBSITE:
+ self.open_webbrowser(self.website_url)
+ elif response_id == HeAboutDialog.RESPONSE_BUGTRACKER:
+ self.open_webbrowser(self.bugtracker_url)
+ elif response_id == HeAboutDialog.RESPONSE_DONATE:
+ self.open_webbrowser(self.donate_url)
+
+ def set_app_name(self, app_name):
+ self.label_app_name.set_text(app_name)
+ self.set_title(_('About %s') % app_name)
+
+ def set_icon_name(self, icon_name):
+ self.image_icon.set_from_icon_name(icon_name, gtk.ICON_SIZE_DIALOG)
+
+ def set_version(self, version):
+ self.label_version.set_text(version)
+
+ def set_description(self, description):
+ self.label_description.set_text(description)
+
+ def set_copyright(self, copyright):
+ self.label_copyright.set_text(copyright)
+
+ def set_website(self, url):
+ if self.website_url is None:
+ self.add_button(_('Visit website'), HeAboutDialog.RESPONSE_WEBSITE)
+ self.website_url = url
+
+ def set_bugtracker(self, url):
+ if self.bugtracker_url is None:
+ self.add_button(_('Report bug'), HeAboutDialog.RESPONSE_BUGTRACKER)
+ self.bugtracker_url = url
+
+ def set_donate_url(self, url):
+ if self.donate_url is None:
+ self.add_button(_('Donate'), HeAboutDialog.RESPONSE_DONATE)
+ self.donate_url = url
+
+ def open_webbrowser(self, url):
+ bus = dbus.SessionBus()
+ proxy = bus.get_object('com.nokia.osso_browser', '/com/nokia/osso_browser/request', 'com.nokia.osso_browser')
+ proxy.load_url(url, dbus_interface='com.nokia.osso_browser')
+
+ @classmethod
+ def present(cls, parent=None, app_name=None, icon_name=None, \
+ version=None, description=None, copyright=None, \
+ website_url=None, bugtracker_url=None, donate_url=None):
+ ad = cls()
+
+ if parent is not None:
+ ad.set_transient_for(parent)
+ ad.set_destroy_with_parent(True)
+
+ if app_name is not None:
+ ad.set_app_name(app_name)
+
+ ad.set_icon_name(icon_name)
+ ad.set_version(version)
+ ad.set_description(description)
+ ad.set_copyright(copyright)
+
+ if website_url is not None:
+ ad.set_website(website_url)
+
+ if bugtracker_url is not None:
+ ad.set_bugtracker(bugtracker_url)
+
+ if donate_url is not None:
+ ad.set_donate_url(donate_url)
+
+ ad.run()
+ ad.destroy()
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+import os
+
+from gettext import translation
+#import locale
+
+#from xdg.IconTheme import getIconPath
+
+appdir="/usr/share/applications/hildon"
+
+apps={}
+
+def readOneFn(fn):
+ global appdir
+
+ ret={
+ 'id': fn[:-8],
+ 'name': None,
+ 'exec': None,
+ 'icon': None,
+ 'iconpath': None,
+ 'domain': None,
+ 'type': None,
+ }
+
+ fn2=appdir + '/' + fn
+
+ try:
+ f=open(fn2, 'rt')
+ except:
+ return(None)
+
+ inde=False
+ for line in f:
+ line=line.strip()
+ if line=='[Desktop Entry]':
+ inde=True
+ continue
+
+ if inde==False:
+ continue
+
+ # Reached another block
+ if line.startswith('[') and inde:
+ break
+
+ elif line.startswith('Name='):
+ l=line[5:]
+ ret['name']=l
+ elif line.startswith('Exec='):
+ l=line[5:]
+ ret['exec']=l
+ elif line.startswith('Icon='):
+ l=line[5:]
+ ret['icon']=l
+ # ret['iconpath']=getIconPath(l)
+ elif line.startswith('X-Text-Domain='):
+ l=line[14:]
+ ret['domain']=l
+ elif line.startswith('Type='):
+ l=line[5:]
+ ret['type']=l
+
+ if ret['domain']!=None:
+ try:
+ c=translation(ret['domain'])
+ except IOError, e:
+ c=None
+
+ if c!=None:
+ ret['name0']=ret['name']
+ ret['name']=c.gettext(ret['name0'])
+
+ if ret['name']==None:
+ ret['name']=ret['id']
+
+ return(ret)
+
+def readOne(name):
+ fn=name + ".desktop"
+
+ ret=readOneFn(fn)
+
+ return(ret)
+
+def scan():
+ global appdir, apps
+
+ files=os.listdir(appdir)
+
+ ret={}
+
+ for f in files:
+ if not f.endswith('.desktop'):
+ continue
+ if f.startswith('catorise-'):
+ continue
+
+ dt=readOneFn(f)
+
+ if dt==None:
+ continue
+ if dt['type']=='Daemon' or dt['type']=='daemon':
+ continue
+
+ t=f[:-8]
+ ret[t]=dt
+
+ apps=ret
+
+ return(ret)
+
+def getLastScan():
+ global apps
+
+ return(apps)
+
+if __name__=="__main__":
+ #locale.setlocale(locale.LC_ALL, '')
+ print scan()
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+import os
+import pickle
+
+version = "1.0"
+
+try:
+ from glib import get_user_config_dir
+except:
+ def get_user_config_dir():
+ home=os.environ['HOME']
+ if home=='':
+ home="/home/user"
+ cfg="%s/.config" % (home)
+
+ return(cfg)
+
+def ensure_dir():
+ dir0=get_user_config_dir()
+ dir=dir0+'/drlaunch'
+ if not os.path.exists(dir):
+ os.mkdir(dir)
+ if not os.path.isdir(dir):
+ raise Exception('Failed to ensure directory' + dir)
+
+ return(dir)
+
+def get_config_fn():
+ dir=ensure_dir()
+ ret=dir + '/config'
+
+ return(ret)
+
+class Config(object):
+ def __init__(self, id):
+ self.id=id
+
+ self.size = (2,2)
+ self.themebgsize = 96
+ self.iconsize0 = 64
+### self.iconspace = 42 # For 4 icons (height)
+## self.iconspace = 36 # For 8 icons (width)
+
+# self.iconsize = 64
+# self.iconpadding = 12
+# self.iconmargin = 6
+
+ self.apps=None
+ self.indiv=True
+ self.longpress=False
+ self.animate=True
+ self.nobg=False
+ self.themebg=False
+
+ self.setDefaultSizes()
+
+ #self.maxsz=(8,4)
+
+ def setSize(self, sz):
+ maxsz=self.getMaxSize()
+ sz2=(min(sz[0], maxsz[0]), min(sz[1], maxsz[1]))
+ self.size=sz2
+
+ def getSize(self):
+ return(self.size)
+
+ def setDefaultSizes(self):
+ self.iconsize=64
+ self.iconpadding=12
+ self.iconmargin=6
+
+ # Return the maximum grid size
+ def getMaxSize(self):
+ isf=self.getIconSizeFull()
+ retx=int(800/isf)
+ rety=int((480-60)/isf)
+ maxsz=(retx, rety)
+ return(maxsz)
+
+ # Return the maxmimum icon size
+ def getIconSizeRange(self):
+ return((48,128))
+
+ def getIconPaddingRange(self):
+ return((0,32))
+
+ def getIconMarginRange(self):
+ return((0,16))
+
+ def getIconSize(self):
+ return(self.iconsize)
+
+ def setIconSize(self, sz):
+ self.iconsize=sz
+
+ def getIconSpace(self):
+ return((2*self.iconpadding) + (2*self.iconmargin))
+
+ def getIconSizeFull(self):
+ ret=self.iconsize + (2*self.iconpadding) + (2*self.iconmargin)
+ return(ret)
+
+ def getIconMargin(self):
+ return(self.iconmargin)
+
+ def setIconMargin(self, sz):
+ self.iconmargin=sz
+
+ def getIconPadding(self):
+ return(self.iconpadding)
+
+ def setIconPadding(self, sz):
+ self.iconpadding=sz
+
+ def setIndiv(self, indiv):
+ self.indiv=indiv
+
+ def getIndiv(self):
+ return(self.indiv)
+
+ def setLongpress(self, lp):
+ self.longpress=lp
+
+ def getLongpress(self):
+ return(self.longpress)
+
+ def setAnimate(self, ar):
+ self.animate=ar
+
+ def getAnimate(self):
+ return(self.animate)
+
+ def setNoBg(self, nobg):
+ self.nobg=nobg
+
+ def getNoBg(self):
+ return(self.nobg)
+
+ def setThemeBg(self, themebg):
+ self.themebg=themebg
+
+ def getThemeBg(self):
+ return(self.themebg)
+
+ def setApps(self, aps):
+ """ apps is a dictionary of (x,y)=>appname """
+ self.apps=aps
+
+ def getApps(self):
+ if self.apps==None:
+ tmp={
+ (0,0): 'rtcom-call-ui',
+ (0,1): 'rtcom-messaging-ui',
+ (1,0): 'browser',
+ (1,1): 'osso-addressbook',
+ }
+ self.setApps(tmp)
+
+ return(self.apps)
+
+ def filterDefault(self, value, default):
+ if value==default:
+ return(-1)
+ else:
+ return(value)
+
+ def provideDefault(self, value, default):
+ if value==-1:
+ return(default)
+ else:
+ return(value)
+
+ def save(self):
+ self.check_init()
+
+ dt=self.load_all()
+
+ if dt==None:
+ dt={
+ 'version': 7,
+ 'data': {},
+ }
+
+ dt['data'][self.id]={
+ 'size': self.getSize(),
+ 'apps': self.getApps(),
+ 'indiv': self.getIndiv(),
+ 'longpress': self.getLongpress(),
+ 'animate': self.getAnimate(),
+ 'nobg': self.getNoBg(),
+ 'themebg': self.getThemeBg(),
+ 'iconsize': self.filterDefault(self.getIconSize(), 64),
+ 'iconpadding': self.filterDefault(self.getIconPadding(), 12),
+ 'iconmargin': self.filterDefault(self.getIconMargin(), 6),
+ }
+
+ fn=get_config_fn()
+
+ st=pickle.dumps(dt)
+ f=file(fn, 'w')
+ f.write(st)
+ f.close()
+
+ def parse_v1(self, dt0):
+ """ Convert a v1 config to v2 """
+ ret={
+ 'version': 2,
+ 'data': {},
+ }
+
+ ret['data'][self.id]={
+ 'size': dt0['size'],
+ 'apps': dt0['apps'],
+ }
+
+ return(ret)
+
+ def parse_v2(self, dt):
+ # Perhaps copy dt?
+
+ dt['version']=3
+
+ for i in dt['data']:
+ dt['data'][i]['indiv']=False
+ dt['data'][i]['size']=(dt['data'][i]['size'], dt['data'][i]['size'])
+ dt['data'][i]['longpress']=True
+
+ return(dt)
+
+ def parse_v3(self, dt):
+ dt['version']=4
+
+ for i in dt['data']:
+ dt['data'][i]['animate']=True
+
+ return(dt)
+
+ def parse_v4(self, dt):
+ dt['version']=5
+
+ for i in dt['data']:
+ dt['data'][i]['nobg']=False
+
+ return(dt)
+
+ def parse_v5(self, dt):
+ dt['version']=6
+
+ for i in dt['data']:
+ dt['data'][i]['themebg']=False
+
+ return(dt)
+
+ def parse_v6(self, dt):
+ dt['version']=7
+
+ for i in dt['data']:
+ dt['data'][i]['iconsize']=-1
+ dt['data'][i]['iconpadding']=-1
+ dt['data'][i]['iconmargin']=-1
+
+ return(dt)
+
+ def load_all(self):
+ fn=get_config_fn()
+
+ try:
+ f=file(fn, 'r')
+ st=f.read()
+ f.close()
+ ret=pickle.loads(st)
+
+ if ret==None:
+ print "failed to load config"
+ ret=None
+ else:
+ if ret['version']==1:
+ ret=self.parse_v1(ret)
+
+ if ret['version']==2:
+ ret=self.parse_v2(ret)
+
+ if ret['version']==3:
+ ret=self.parse_v3(ret)
+
+ if ret['version']==4:
+ ret=self.parse_v4(ret)
+
+ if ret['version']==5:
+ ret=self.parse_v5(ret)
+
+ if ret['version']==6:
+ ret=self.parse_v6(ret)
+ except Exception, e:
+ print "config error:", e
+ ret=None
+
+ return(ret)
+
+ def load(self):
+ self.check_init()
+
+ fn=get_config_fn()
+
+ dt0=self.load_all()
+
+ if dt0==None or not dt0['data'].has_key(self.id):
+ return
+
+ dt=dt0['data'][self.id]
+
+ self.setSize(dt['size'])
+ self.setApps(dt['apps'])
+ self.setIndiv(dt['indiv'])
+ self.setLongpress(dt['longpress'])
+ self.setAnimate(dt['animate'])
+ self.setNoBg(dt['nobg'])
+ self.setThemeBg(dt['themebg'])
+ self.setIconSize(self.provideDefault(dt['iconsize'], 64))
+ self.setIconPadding(self.provideDefault(dt['iconpadding'], 12))
+ self.setIconMargin(self.provideDefault(dt['iconmargin'], 6))
+
+ def check_init(self):
+ if self.id==None:
+ import sys
+
+ print "config.init() not done"
+ sys.exit(1)
+
+def dump(obj):
+ attrs=[attr for attr in dir(obj) if not callable(getattr(obj,attr))]
+
+ print "obj:", obj
+ for attr in attrs:
+ if attr=='__gdoc__' or attr=='__doc__':
+ continue
+ print " ", attr, ":", getattr(obj, attr)
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+import os.path
+from drlaunch import widget
+
+class DrlaunchPlugin(widget.DrlaunchPlugin):
+ pass
+
+def redirect_err():
+ import sys
+ import time
+
+ print "Opening /tmp/drlaunch.log"
+ f=open('/tmp/drlaunch.log', 'at', buffering=1)
+ sys.stdout=f
+ sys.stderr=f
+ print "Log open:", time.ctime()
+
+hd_plugin_type = DrlaunchPlugin
+
+if os.path.exists('/tmp/drlaunch.log'):
+ redirect_err()
+
+if __name__=="__main__":
+ import gobject
+ import gtk
+
+ gobject.type_register(hd_plugin_type)
+ obj=gobject.new(hd_plugin_type, plugin_id="plugin_id")
+ obj.show_all()
+ gtk.main()
+
+
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+import gtk
+import gobject
+import hildon
+from hildondesktop import *
+from gtk import gdk
+from math import pi
+import cairo
+import time
+
+from portrait import FremantleRotation
+import launcher
+from xdg.IconTheme import getIconPath
+from sig import Disconnector
+
+#import config
+import apps
+
+# Background surface for icons
+iconbg=None
+sthemebg1=None
+sthemebg2=None
+
+# Load an icon
+# Fall-back to default/blue if not found or name==None
+def getIcon(name, iconsize):
+ # Default icon
+ idef='tasklaunch_default_application'
+
+ # If name==None then use the default icon
+ if name==None or name=='':
+ iname=idef
+ else:
+ iname=name
+
+ ico=getIconPath(iname, iconsize)
+
+ # If not found then use the default icon
+ if ico==None:
+ ico=getIconPath(idef, iconsize)
+
+ try:
+ ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
+ except:
+ # On error use the default icon
+ ico=getIconPath(idef, iconsize)
+ ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
+ print "Icon with unhandled format:", iname
+
+ return(ret)
+
+class Icon(Disconnector, gobject.GObject):
+#class Icon(gtk.Widget, Disconnector):
+
+ __v_t=(gobject.SIGNAL_RUN_FIRST | gobject.SIGNAL_ACTION,
+ gobject.TYPE_NONE, ())
+
+ gsignals={
+ 'click': __v_t,
+ 'double-click': __v_t,
+ 'tripple-click': __v_t,
+ 'long-press': __v_t,
+ }
+
+ __gsignals__=gsignals
+
+ def __init__(self, isconfig, config):
+# self.__gobject_init__()
+ gobject.GObject.__init__(self)
+# gtk.Widget.__init__(self)
+ Disconnector.__init__(self)
+
+ self.isconfig=isconfig
+ self.config=config
+
+ self.appname=None
+ self.icon=None
+ self.sicon=None
+ self.lastpress=0
+ self.ispressed=False
+
+ self.x=0
+ self.y=0
+
+ self.presstime=0.25
+
+ self.window_=None
+
+ self.clickcount=0
+
+ self.angle=0
+
+ self.cached_icons={}
+
+ self.draw_queued=False
+
+ #print "icon-init"
+
+ def __del__(self):
+ #print "icon-del"
+ pass
+
+ def timePressed(self):
+ """ return how much time a button is pressed """
+ dt=time.time() - self.lastpress
+
+ return(dt)
+
+ def reload(self):
+ self.clearAnimationCache()
+ self.clearBgCache()
+ self.invalidate()
+
+ def setApp(self, dt):
+ if dt==None:
+ self.appname=None
+ self.icon=None
+ self.sicon=None
+ else:
+ self.appname=dt['id']
+ self.icon=dt['icon2']
+ self.sicon=None
+ self.clearAnimationCache()
+ self.clearBgCache()
+ self.invalidate()
+
+ def clearAnimationCache(self):
+ self.cached_icons={}
+
+ def clearBgCache(self):
+ global iconbg, sthemebg1, sthemebg2
+
+ iconbg=None
+ sthemebg1=None
+ sthemebg2=None
+
+ def getSize(self):
+ return(self.config.getIconSizeFull())
+ # return(self.config.iconsize+self.config.iconspace)
+
+ def setAngle(self, angle):
+ """ Set the angle. Return True if the angle changed or False if it
+ didn't. The caller should invalidate the icon """
+
+ # The step in degrees
+ step=9
+
+ angle2=int(angle/step)*step
+
+ if angle2==self.angle:
+ return(False)
+
+ self.angle=angle2
+
+ # The caller should be responsible for redrawing.
+ # If we call invalidate() here there is the risk of having
+ # icons rotate individually using different angles
+# self.invalidate()
+
+ return(True)
+
+ def mkbg(self, t_pressed):
+ """ Create the background of the icon and cache it as a global
+ variable among all icons and widget instances """
+ global iconbg
+
+ if iconbg!=None and t_pressed<=0.001:
+ return(iconbg)
+
+ w=self.getSize()
+ s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+ cr0=cairo.Context(s)
+ cr=gtk.gdk.CairoContext(cr0)
+
+ cr.set_source_rgba(0.1, 0.1, 0.1, 1)
+ cr.set_line_width(5)
+
+ #if self.ispressed:
+ if t_pressed>0.001 and \
+ (t_pressed <= self.presstime or self.ispressed):
+ t=1.0 * min(t_pressed, self.presstime) / self.presstime
+ g=0.3+0.5*t
+ b=0.3+0.7*t
+ cr.set_source_rgba(0, g, b, 0.7)
+ else:
+ cr.set_source_rgba(0.3, 0.3, 0.3, 0.7)
+
+ x=0
+ y=0
+ x3=x + (self.config.iconmargin)
+ y3=y + (self.config.iconmargin)
+
+ r=10 # Radius
+ w=self.config.iconsize+(self.config.iconpadding*2)
+
+ cr.move_to(x3+r, y3)
+ cr.arc(x3+w-r, y3+r, r, pi*1.5, pi*2)
+ cr.arc(x3+w-r, y3+w-r, r, 0, pi*0.5)
+ cr.arc(x3+r, y3+w-r, r, pi*0.5, pi)
+ cr.arc(x3+r, y3+r, r, pi, pi*1.5)
+
+ cr.stroke_preserve()
+ cr.fill()
+ cr.clip()
+ cr.paint()
+# cr.restore()
+
+ if t_pressed<0.001:
+ iconbg=s
+
+ return(s)
+
+ def get_sthemebg(self, pressed):
+ """ Return the theme's background icon as a surface. Cache it. """
+ global sthemebg1, sthemebg2
+
+ if not pressed and sthemebg1!=None:
+ return(sthemebg1)
+ if pressed and sthemebg2!=None:
+ return(sthemebg2)
+
+ fn="/etc/hildon/theme/images/"
+ if pressed:
+ fn+="ApplicationShortcutAppletPressed.png"
+ else:
+ fn+="ApplicationShortcutApplet.png"
+
+ w=self.config.iconsize + (self.config.iconpadding*2)
+ buf=gtk.gdk.pixbuf_new_from_file_at_size(fn, w, w)
+ s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+ cr0=cairo.Context(s)
+ cr=gtk.gdk.CairoContext(cr0)
+
+ cr.set_source_pixbuf(buf, 0, 0)
+ cr.paint()
+
+ if not pressed:
+ sthemebg1=s
+ else:
+ sthemebg2=s
+
+ return(s)
+
+ def get_sicon(self):
+ """ Return the icon as a surface. Cache it. """
+ if self.sicon!=None:
+ return(self.sicon)
+
+ w=self.config.iconsize
+ s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+ cr0=cairo.Context(s)
+ cr=gtk.gdk.CairoContext(cr0)
+
+ cr.set_source_pixbuf(self.icon, 0, 0)
+ cr.paint()
+
+ self.sicon=s
+
+ return(s)
+
+ def get_paint_icon(self):
+ """ Return the icon to paint as a surface. The icon is rotated
+ as needed. The result is cached. """
+ angle=self.angle
+
+ if self.timePressed() <= self.presstime or self.ispressed:
+ t=self.timePressed()
+ pressed=True
+ else:
+ t=0
+ pressed=False
+
+ if not pressed and self.cached_icons.has_key(angle):
+ return(self.cached_icons[angle])
+
+ w=self.config.getIconSizeFull()
+ s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
+ cr0=cairo.Context(s)
+ cr=gtk.gdk.CairoContext(cr0)
+
+ # Paint the background
+ if self.config.getNoBg():
+ pass
+ elif self.config.getThemeBg(): # Use theme bg
+ s2=self.get_sthemebg(pressed)
+
+ # have in mind the size difference of iconsize+iconspace with
+ # the fixed themebgsize
+ #xy0=int((w-self.config.themebgsize)/2)
+ #xy0=int((w-self.config.iconsize)/2)
+ xy0=self.config.iconmargin
+
+ cr.save()
+ cr.set_source_surface(s2, xy0, xy0)
+ cr.paint()
+ cr.restore()
+ else:
+ s2=self.mkbg(t)
+ cr.save()
+ cr.set_source_surface(s2, 0, 0)
+ cr.paint()
+ cr.restore()
+
+ # If there is no icon then don't do anything more
+ if self.icon!=None:
+ # Get the icon as a surface (get_sicon() will cache the surface)
+ sicon=self.get_sicon()
+
+ # Width is the iconsize plus the empty border around the icon
+ #w=self.config.iconsize + self.config.iconspace
+
+ # This is used to locate the center of the surface
+ dx=int(w/2)
+
+ # This is the delta from the center where icons are drawn
+ dx2=int(self.config.iconsize/2)
+
+# cr.save()
+
+ # A transformation matrix with dx/dy set to point to the center
+ m=cairo.Matrix(1, 0, 0, 1, dx, dx)
+ cr.set_matrix(m)
+ # Transform degrees to rads
+ rot=-1 * pi * 2 * self.angle / 360
+ cr.rotate(rot)
+ # Draw the icon
+ cr.set_source_surface(sicon, -dx2, -dx2) # Faster than pixbuf
+# cr.set_source_pixbuf(icon2, -dx2, -dx2)
+ cr.paint()
+
+# cr.restore()
+
+ if not pressed:
+ self.cached_icons[angle]=s
+
+ return(s)
+
+
+ def draw(self, cr, x, y):
+ self.draw_queued=False
+ self.x=x
+ self.y=y
+
+ if self.icon==None and not self.isconfig:
+ return
+
+ cr.save()
+ s=self.get_paint_icon()
+ cr.set_source_surface(s, x, y)
+ cr.paint()
+
+ cr.restore()
+
+ return(False)
+
+ def timerPressed(self):
+# if not self.ispressed:
+# return(False)
+
+ self.invalidate()
+
+ if self.timePressed()>self.presstime:
+ ret=False
+ else:
+ ret=True
+
+ return(ret)
+
+ def doPress(self):
+ # Double-time: time for pressed and time for not-pressed
+ if time.time() - self.lastpress > self.presstime*2:
+ self.clickcount=0
+
+ self.lastpress=time.time()
+ self.ispressed=True
+ gobject.timeout_add(20, self.timerPressed)
+
+ def doRelease(self):
+ dt=time.time() - self.lastpress
+ self.ispressed=False
+ self.invalidate()
+ if dt<=self.presstime:
+ self.clickcount+=1
+ if self.clickcount==1:
+ self.emit('click')
+# print "emit click", self
+ elif self.clickcount==2:
+ self.emit('double-click')
+ if self.clickcount==3:
+ self.emit('tripple-click')
+ self.clickcount=0
+ elif dt>self.presstime and dt<2:
+ self.emit('long-press')
+# print "Emit lp"
+
+ def doCancel(self):
+ self.ispressed=False
+
+ def setWindow(self, window):
+ self.window_=window
+
+ def invalidate(self, window=None):
+ if window==None:
+ window=self.window_
+ else:
+ self.window_=window
+
+ if window==None:
+ return
+
+ if self.draw_queued:
+# print "queued"
+ return
+
+ self.draw_queued=True
+ w=self.getSize()
+ rect=gdk.Rectangle(self.x, self.y, w, w)
+ gdk.Window.invalidate_rect(window, rect, True)
+
+#gobject.type_register(Icon)
+#signals=['click', 'double-click', 'tripple-click', 'long-press']
+#for s in signals:
+# gobject.signal_new(s, Icon, gobject.SIGNAL_RUN_FIRST | \
+# gobject.SIGNAL_ACTION, gobject.TYPE_NONE, ())
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+import gtk
+import gobject
+import hildon
+from hildondesktop import *
+from gtk import gdk
+from math import pi
+import cairo
+import time
+
+from portrait import FremantleRotation
+#from xdg.IconTheme import getIconPath
+
+from config import dump
+import apps
+import icon
+from icon import Icon
+from icons import Icons
+
+#def getIcon(name, iconsize):
+# ico=getIconPath(name, iconsize)
+# ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
+#
+# return(ret)
+
+# IconGrid is the main class that implements tha drawing of the grid
+# However, since it will be used both by the desktop plugin and the
+# configuration window, it cannot derive either gtk.Widget or HomePluginItem.
+# It is created here in a way that will allow it to work on both cases
+# and it is inherited by appropriate classes (one for the plugin and one
+# for the config widget)
+
+#class IconGrid(gtk.Widget, FremantleRotation):
+class IconGrid: #(gobject.GObject):
+ def __init__(self, isconfig=False):
+# self.__gobject_init__()
+# gtk.Widget.__init__(self)
+
+ self.init_done=False
+
+ self.size=(0,0)
+
+ self.isconfig=isconfig
+
+ self.icons=None
+ self.lasticon=None # The last icon that got selected
+
+ self.draw_pending=False
+
+ self.mode=None
+
+ # If this is False then animations are forcefully disabled
+ self.do_animations=True
+
+ self.angle_timer_start=0
+
+ # Duration of the rotation effect
+ self.rotation_time=0.8
+
+# print "ig-init"
+
+# def __del__(self):
+# print "ig-del"
+
+ def do_realize(self, config):
+# print "ig-realize"
+ self.config=config
+
+ if self.icons!=None:
+ print
+ print
+ print
+ print "WTF??????????????????????"
+ print
+ print
+ print
+
+ self.icons=Icons(self.isconfig, self.config)
+ #print "self:", self
+ #self.icons.set_parent(self)
+ self.setMode('l')
+ self.setSize(config.getMaxSize())
+ self.reloadIcons()
+
+ def do_unrealize(self):
+# print "ig-unrealize"
+ self.config=None
+ self.icons.finish()
+ self.icons=None
+ self.lasticon=None
+
+ def connect(self, what, *args):
+ if what in Icon.gsignals.keys():
+ ret=self.icons.connect(what, *args)
+ else:
+ ret=gobject.GObject.connect(self, what, *args)
+ #ret=super(IconGrid, self).connect(what, *args)
+
+ return(ret)
+
+ def setSize(self, size):
+ self.size=size
+ self.icons.setSize(size)
+
+ def getSize(self):
+ ret=self.icons.getSize()
+ return(ret)
+
+ def setMode(self, mode):
+ if self.mode==mode:
+# print "same mode"
+ return
+
+ self.mode=mode
+ if not isinstance(self, gtk.Widget):
+ return
+
+ do_draw=False
+
+ try:
+ v=self.get_property('is-on-current-desktop')
+ if v:
+ do_draw=True
+ else:
+ self.draw_pending=True
+ except TypeError:
+ do_draw=True
+
+ if do_draw and self.config.getAnimate() and self.do_animations:
+ #self.queue_draw()
+ # Don't start another timer
+ # Instead adjust the time start to produce a nice effect ;-)
+ if self.angle_timer_start==0:
+ self.angle_timer_start=time.time()
+ gobject.timeout_add(20, self.timerAngle)
+ else:
+ dt=time.time()-self.angle_timer_start
+ da=90.0*dt/self.rotation_time
+
+ da2=90.0-da
+ dt2=da2*self.rotation_time/90.0
+ self.angle_timer_start=time.time()-dt2
+ else:
+ if self.mode=='l':
+ self.setAngle(0)
+ else:
+ self.setAngle(90)
+
+ if do_draw:
+ self.queue_draw()
+
+ def disableAnimation(self):
+ self.do_animations=False
+
+ def enableAnimation(self):
+ self.do_animations=True
+
+ def setAnimationEnable(self, value):
+ if value:
+ self.enableAnimation()
+ else:
+ self.disableAnimation()
+
+ def timerAngle(self):
+ if self.angle_timer_start==0:
+ self.angle_timer_start=time.time()-0.05
+
+ dt=time.time()-self.angle_timer_start
+
+ da=90.0*dt/self.rotation_time
+
+ if self.mode=='l':
+ angle=90-da
+ else:
+ angle=da
+
+ if angle>=90:
+ angle=90
+ ret=False
+ elif angle<0:
+ angle=0
+ ret=False
+ else:
+ ret=True
+
+ if self.setAngle(angle):
+ self.queue_draw()
+
+ if ret==False:
+ self.angle_timer_start=0
+
+ return(ret)
+
+ def iconAt(self, x, y):
+ """ Get icon at coordinates x,y. X and Y are in pixels """
+
+ w=self.config.getIconSizeFull()
+
+ if self.mode=='l' or self.config.getIndiv():
+ x2=int(x / w)
+ y2=int(y / w)
+ else:
+ x2=self.size[1] - int(y/w) - 1
+ y2=int(x/w)
+
+ ret=self.get(x2,y2)
+
+ return(ret)
+
+ def get(self, x, y):
+ ret=self.icons.get(x,y)
+
+ return(ret)
+
+ def _draw(self, cr, event):
+ self.draw_pending=False
+
+ w=self.config.getIconSizeFull()
+ for x,y in self.icons:
+ if self.mode=='l' or self.config.getIndiv():
+ #x2=x * (self.config.iconsize + self.config.iconspace)
+ #y2=y * (self.config.iconsize + self.config.iconspace)
+ x2=x * self.config.getIconSizeFull()
+ y2=y * self.config.getIconSizeFull()
+ else:
+ #x2=y * (self.config.iconsize + self.config.iconspace)
+ #y2=(self.size[1]-x-1) * \
+ # (self.config.iconsize + self.config.iconspace)
+ x2=y * self.config.getIconSizeFull()
+ y2=(self.size[1]-x-1) * self.config.getIconSizeFull()
+
+ # Only repaint the needed icons
+ rect=gdk.Rectangle(x2, y2, w, w)
+ t=rect.intersect(event.area)
+ if t.width==0 and t.height==0:
+ continue
+
+ ico=self.icons.get(x,y)
+ ico.draw(cr, x2, y2)
+
+ def setAngle(self, angle):
+ """ Return True/False indicating that angle has changed """
+ ret=False
+ for x,y in self.icons:
+ ic=self.icons.get(x,y)
+ if ic.setAngle(angle):
+ ret=True
+
+ return(ret)
+
+ def clearAnimationCache(self):
+ """ Clear animation cache, freeing memory """
+ for x,y in self.icons:
+ ic=self.icons.get(x,y)
+ ic.clearAnimationCache()
+
+ def clearBgCache(self):
+ """ Clear backgrounds cache """
+ for x,y in self.icons:
+ ic=self.icons.get(x,y)
+ ic.clearBgCache()
+
+ def do_expose_event(self, event):
+ cr=self.window.cairo_create()
+
+ cr.rectangle(event.area.x, event.area.y,
+ event.area.width, event.area.height)
+
+ cr.clip()
+
+ if not self.init_done:
+ self.icons.setWindow(self.window)
+ self.init_done=True
+
+ self._draw(cr, event)
+
+ def setLastIcon(self, icon):
+ if icon==self.lasticon:
+ return
+
+ if self.lasticon!=None:
+ self.lasticon.doCancel()
+ self.lasticon.invalidate(self.window)
+ self.lasticon=icon
+
+ def do_button_press_event(self, event):
+ icon=self.iconAt(event.x, event.y)
+ if icon==None:
+ return
+# rect=gdk.Rectangle(event.x,event.y,1,1)
+# rect=gdk.Rectangle(0, 0, 100, 100)
+ icon.doPress()
+ icon.invalidate(self.window)
+ self.setLastIcon(icon)
+
+# gdk.Window.invalidate_rect(self.window, rect, True)
+
+ return(True)
+
+ def do_button_release_event(self, event):
+ if self.lasticon!=None:
+ self.lasticon.invalidate(self.window)
+ self.lasticon.doRelease()
+
+ self.setLastIcon(None)
+
+ return(True)
+
+ def do_leave_notify_event(self, event):
+ self.setLastIcon(None)
+ return(True)
+
+ def do_pproperty_notify_event(self, event):
+ icon=self.iconAt(event.x, event.y)
+ if icon==None:
+ return
+ icon.doCancel()
+ icon.invalidate(self.window)
+ return(True)
+
+ def do_motion_notify_event(self, event):
+ icon=self.iconAt(event.x, event.y)
+ if self.lasticon==icon:
+ return(True)
+
+ self.setLastIcon(None)
+ icon.doCancel()
+ icon.invalidate(self.window)
+ return(True)
+
+ def do_button_press_event_old(self, event):
+ if event.type==gdk.BUTTON_PRESS:
+ if self.mode=='p':
+ self.setMode('l')
+ else:
+ self.setMode('p')
+ self.queue_draw()
+ return True
+
+ # For debugging
+ def do_event1(self, event):
+ print "event:", event, event.type
+
+ def reloadIcons(self):
+ self.icons.load()
+ self.lasticon=None
+
+# def on_orientation_changed(self, orientation):
+# print "orch:", orientation
+# o=orientation[0]
+# self.setMode(o)
+
+class IconGridWidget(IconGrid, gtk.Widget):
+ def __init__(self, isconfig, config, animation=True):
+ IconGrid.__init__(self, isconfig)
+ gtk.Widget.__init__(self)
+
+ # This must be called before do_realize
+ self.setAnimationEnable(animation)
+
+ self.config=config
+
+ IconGrid.do_realize(self, self.config)
+
+ self.setSize(self.size)
+
+# print "igw-init"
+
+# def __del__(self):
+# print "igw-del"
+
+ def setSize(self, size):
+ IconGrid.setSize(self, size)
+
+ w=self.size[0] * self.config.getIconSizeFull()
+ h=self.size[1] * self.config.getIconSizeFull()
+
+ self.set_size_request(w, h)
+
+ def reconfig(self):
+ self.clearBgCache()
+ self.clearAnimationCache()
+ self.reloadIcons()
+ self.setSize(self.size)
+ self.icons.resizeMax()
+ self.queue_draw()
+
+ def do_realize(self):
+# print "igw-realize"
+ screen=self.get_screen()
+ self.set_colormap(screen.get_rgba_colormap())
+ self.set_app_paintable(True)
+
+ self.set_flags(self.flags() | gtk.REALIZED)
+
+ self.window=gdk.Window(
+ self.get_parent_window(),
+ width=self.allocation.width,
+ height=self.allocation.height,
+ window_type=gdk.WINDOW_CHILD,
+ wclass=gdk.INPUT_OUTPUT,
+ event_mask=self.get_events() | gdk.EXPOSURE_MASK
+ | gdk.BUTTON_PRESS_MASK
+ | gdk.BUTTON_RELEASE_MASK
+ | gdk.BUTTON_MOTION_MASK
+ | gdk.POINTER_MOTION_MASK
+ | gdk.POINTER_MOTION_HINT_MASK
+ | gdk.ENTER_NOTIFY_MASK
+ | gdk.LEAVE_NOTIFY_MASK )
+
+ self.window.set_user_data(self)
+
+ # No matter what the pygtk widget demo says, this is NOT CORRECT!!!
+ # Don't call style.attach(self.window) or else the program will crash
+ # after some time! It seems that there is a style already.
+ # If we want to use the style the we use get_style() instead.
+ # This one was very hard to solve. Thanks to gnome2-globalmenu guys.
+ # It was solved by looking the commit 2666:
+ # see:
+ # svn diff -r2665:2666 http://gnome2-globalmenu.googlecode.com/svn/trunk
+ # which solved cse 490:
+ # http://code.google.com/p/gnome2-globalmenu/issues/detail?id=490
+# self.style.attach(self.window)
+ style=self.get_style()
+
+# style.set_background(self.window, gtk.STATE_NORMAL)
+ self.window.move_resize(*self.allocation)
+
+# self.pixmap, mask = gtk.gdk.pixmap_create_from_xpm_d(
+# self.window, self.style.bg[gtk.STATE_NORMAL], STAR_PIXMAP)
+
+# self.gc = self.style.fg_gc[gtk.STATE_NORMAL]
+
+ #HomePluginItem.do_realize(self)
+
+# screen=self.get_screen()
+# self.set_colormap(screen.get_rgba_colormap())
+# self.set_app_paintable(True)
+
+ def do_unrealize(self):
+# print "igw-unrealize", self
+ IconGrid.do_unrealize(self)
+ self.window.destroy()
+ self.window.set_user_data(None)
+
+ def do_expose_event(self, event):
+ cr=self.window.cairo_create()
+
+ cr.rectangle(event.area.x, event.area.y,
+ event.area.width, event.area.height)
+ cr.clip()
+
+ style=self.get_style()
+ col=style.bg[gtk.STATE_NORMAL]
+ cr.set_source_color(col)
+ cr.paint()
+
+ IconGrid.do_expose_event(self, event)
+
+#gobject.type_register(IconGrid)
+gobject.type_register(IconGridWidget)
+
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+#import config
+import apps
+import icon
+from icon import getIcon, Icon
+from sig import Disconnector
+
+import gobject
+import gtk
+
+class IconIter:
+ def __init__(self, items):
+ self.iter=items.__iter__()
+
+ def __iter__(self):
+ ret=self.iter.__iter__()
+ return(ret)
+
+ def next(self):
+ ret=self.iter.next()
+ return(ret)
+
+class Icons(gobject.GObject, Disconnector):
+#class Icons(gtk.Widget, Disconnector):
+
+ #__gsignals__=Icon.gsignals
+ def __init__(self, isconfig, config):
+ self.__gobject_init__()
+ #gobject.GObject.__init__(self)
+ #gtk.Widget.__init__(self)
+ Disconnector.__init__(self)
+
+ self.icons={}
+ self.allicons={}
+ self.size=0
+ self.isconfig=isconfig
+ self.config=config
+
+ # signal handlers
+ self.handlers={}
+
+ self.maxsz=(0,0)
+
+ # setup allicons
+ self.resizeMax()
+
+# print "icons-init"
+
+ def finish(self):
+ self.dis_finish()
+ return
+# self.icons=None
+# self.allicons=None
+# print "icons-finish"
+
+# def __del__(self):
+# print "icons-del"
+
+ def resizeMax(self):
+ sz=self.maxsz
+ maxsz=self.config.getMaxSize()
+
+ # Create new entries in x
+ if maxsz[0]>sz[0]:
+ for x in xrange(maxsz[0]-sz[0]):
+ for y in xrange(sz[1]):
+ k=(x+sz[0],y)
+ ico=Icon(self.isconfig, self.config)
+ self.allicons[k]=ico
+ self.connect_one(ico)
+ sz=(maxsz[0], sz[1])
+ elif maxsz[0]<sz[0]:
+ for x in xrange(sz[0]-maxsz[0]):
+ for y in xrange(sz[1]):
+ k=(maxsz[0]+x,y)
+ t=self.allicons.pop(k)
+ self.disconnect_one(t)
+ sz=(maxsz[0], sz[1])
+
+ # Create new entries in y
+ if maxsz[1]>sz[1]:
+ for y in xrange(maxsz[1]-sz[1]):
+ for x in xrange(sz[0]):
+ k=(x,y+sz[1])
+ ico=Icon(self.isconfig, self.config)
+ self.allicons[k]=ico
+ self.connect_one(ico)
+ sz=(sz[0], maxsz[1])
+ elif maxsz[1]<sz[1]:
+ for y in xrange(sz[1]-maxsz[1]):
+ for x in xrange(sz[0]):
+ k=(x,y+maxsz[1])
+ t=self.allicons.pop(k)
+ self.disconnect_one(t)
+ sz=(sz[0], maxsz[1])
+
+ self.maxsz=sz
+
+ @classmethod
+ def register_signals(cls):
+ signals=Icon.gsignals
+ for s in signals:
+ ss=signals[s]
+ gobject.signal_new(s, cls, ss[0], ss[1], (Icon,))
+# gobject.SIGNAL_RUN_FIRST,
+# gobject.TYPE_NONE, (Icon,))
+
+ def __iter__(self):
+ return(IconIter(self.icons))
+
+ def connect_one(self, which):
+ self.handlers[which]={
+ 'longpress': self.c(which, 'long-press', self.signalLongpress),
+ 'click': self.c(which, 'click', self.signalClick),
+ 'tripple': self.c(which, 'tripple-click',
+ self.signalTrippleClick)
+ }
+
+ def disconnect_one(self, which):
+ for i in self.handlers[which]:
+ which.disconnect(self.handlers[which][i])
+ self.handlers.pop(which)
+# which.disconnect(self.h[which]_longpress)
+# which.disconnect(self.h_click)
+# which.disconnect(self.h_tripple)
+
+ def setSize(self, sz):
+# print "sz:", sz, self.size
+
+ if sz==self.size:
+ return
+
+ old=self.icons
+ self.icons={}
+
+ for x in xrange(sz[0]):
+ for y in xrange(sz[1]):
+ k=(x,y)
+ ico=self.allicons[k]
+ self.icons[k]=ico
+# if old.has_key(k):
+# old.pop(k) # Re-used
+# else:
+# self.connect_one(ico)
+
+ # Disconnect signals
+# for i in old:
+# self.disconnect_one(old[i])
+
+ self.size=sz
+
+ def getSize(self):
+ return(self.size)
+
+ def setWindow(self, win):
+ """ Set the window for all icons """
+
+ for i in self.icons:
+ ic=self.icons[i]
+ ic.setWindow(win)
+
+ def signalLongpress(self, icon):
+ #print "signalLongpress()", icon
+ self.emit('long-press', icon)
+
+ def signalClick(self, icon):
+ #print "signalClick()", icon
+ self.emit('click', icon)
+
+ def signalTrippleClick(self, icon):
+ #print "signalTrippleClick()", icon
+ self.emit('tripple-click', icon)
+
+ def get(self, x, y):
+ k=(x,y)
+ if self.icons.has_key(k):
+ ret=self.icons[k]
+ else:
+ ret=None
+
+ return(ret)
+
+ def load(self):
+# x=0
+# y=0
+# fn=["maegirls", "wifieye", 'battery-eye', 'image-viewer',
+# 'tecnoballz', 'ncalc', 'rtcom-call-ui', 'rtcom-messaging-ui',
+# 'extcalllog', 'browser', 'modest', 'osso-addressbook']
+
+ wapps=self.config.getApps()
+ #sz=self.config.getSize()
+ sz=self.getSize()
+
+ for k in wapps:
+ x,y=k
+ if x>=sz or y>=sz:
+ continue
+
+ appname=wapps[k]
+ if appname!=None:
+ app=apps.readOne(appname)
+ if app!=None:
+ app['icon2']=getIcon(app['icon'], self.config.getIconSize())
+ self.get(x,y).setApp(app)
+ else:
+ ic=self.get(x,y)
+ ic.setApp(None)
+
+ # Reload icons to make sure backgrounds, etc are valid
+ for x in xrange(sz[0]):
+ for y in xrange(sz[1]):
+ ic=self.get(x,y)
+ ic.reload()
+
+
+# for f in fn:
+# dt=apps.readOne(f)
+# dt['icon2']=getIcon(dt['icon'])
+# print x, y, dt
+# self.get(x,y).setApp(dt)
+# x+=1
+# if x>=config.getSize(getSize()):
+# x=0
+# y+=1
+## self.icons.append(p)
+
+gobject.type_register(Icons)
+Icons.register_signals()
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+from icon import Icon, getIcon
+import apps
+
+import gtk
+import cairo
+from gtk import gdk
+import gobject
+
+class IconWidget(gtk.Widget):
+ def __init__(self, isconfig, config):
+ gtk.Widget.__init__(self)
+
+ self.config=config
+ self.icon=Icon(isconfig, config)
+
+ def getMaxIconSizeFull(self):
+ c=self.config
+ w=c.getIconSizeRange()[1] + 2*c.getIconPaddingRange()[1] + \
+ 2*c.getIconMarginRange()[1]
+
+ return(w)
+
+ def resetSize(self):
+ w=self.getMaxIconSizeFull()
+ self.set_size_request(2*w, 2*w)
+
+ def do_realize(self):
+ screen=self.get_screen()
+ self.set_colormap(screen.get_rgba_colormap())
+ self.set_app_paintable(True)
+
+ self.set_flags(self.flags() | gtk.REALIZED)
+
+ self.window=gdk.Window(
+ self.get_parent_window(),
+ width=self.allocation.width,
+ height=self.allocation.height,
+ window_type=gdk.WINDOW_CHILD,
+ wclass=gdk.INPUT_OUTPUT,
+ event_mask=self.get_events() | gdk.EXPOSURE_MASK)
+
+ self.window.set_user_data(self)
+
+ self.window.move_resize(*self.allocation)
+
+# style=self.get_style()
+# style.set_background(self.window, gtk.STATE_NORMAL)
+# self.gc=style.bg_gc[gtk.STATE_NORMAL]
+
+ self.icon.setWindow(self.window)
+ self.resetSize()
+
+ def do_unrealize(self):
+ self.window.destroy()
+ self.window.set_user_data(None)
+
+ def do_expose_event(self, event):
+ cr=self.window.cairo_create()
+
+ cr.rectangle(event.area.x, event.area.y,
+ event.area.width, event.area.height)
+
+ cr.clip()
+
+ cr.save()
+ # Paint the background
+ style=self.get_style()
+ col=style.bg[gtk.STATE_NORMAL]
+ cr.set_source_color(col)
+ cr.paint()
+
+ x2=self.config.getIconSizeFull()
+
+# col=style.fg[gtk.STATE_ACTIVE]
+# cr.set_source_color(col)
+# #cr.set_source_rgba(0,1,1,0.5)
+# cr.rectangle(1,1,x2+x2+2,x2+2)
+# cr.stroke()
+# cr.restore()
+
+ self.icon.draw(cr, 0, 0)
+ self.icon.draw(cr, x2, 0)
+ self.icon.draw(cr, 0, x2)
+ self.icon.draw(cr, x2, x2)
+
+ def setApp(self, app):
+ self.app=app
+ self.refresh()
+
+ def refresh(self):
+ if self.app!=None:
+ app=apps.readOne(self.app)
+ app['icon2']=getIcon(app['icon'], self.config.getIconSize())
+ self.icon.setApp(app)
+
+ self.queue_draw()
+
+gobject.type_register(IconWidget)
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+import dbus
+
+bus=None
+proxy=None
+
+def init():
+ global bus, proxy
+
+ if bus==None:
+ bus=dbus.SessionBus()
+
+ if proxy==None:
+ proxy=bus.get_object('com.nokia.HildonDesktop.AppMgr',
+ '/com/nokia/HildonDesktop/AppMgr')
+
+def launch(prog):
+ global bus, proxy
+
+ proxy.LaunchApplication(prog,
+ dbus_interface='com.nokia.HildonDesktop.AppMgr')
+
+if __name__=="__main__":
+ init()
+ launch('wifieye')
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# gPodder - A media aggregator and podcast client
+# Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
+#
+# gPodder 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.
+#
+# gPodder 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/>.
+#
+
+import dbus
+import dbus.glib
+from dbus.mainloop.glib import DBusGMainLoop
+
+import hildon
+import osso
+
+# Replace this with your own gettext() functionality
+_ = str
+
+
+class FremantleRotation(object):
+ """thp's screen rotation for Maemo 5
+
+ Simply instantiate an object of this class and let it auto-rotate
+ your StackableWindows depending on the device orientation.
+
+ If you need to relayout a window, connect to its "configure-event"
+ signal and measure the ratio of width/height and relayout for that.
+
+ You can set the mode for rotation to AUTOMATIC (default), NEVER or
+ ALWAYS with the set_mode() method.
+ """
+ AUTOMATIC, NEVER, ALWAYS = range(3)
+
+ # Human-readable captions for the above constants
+ MODE_CAPTIONS = (_('Automatic'), _('Landscape'), _('Portrait'))
+
+ # Privately-used constants
+ _PORTRAIT, _LANDSCAPE = ('portrait', 'landscape')
+ _ENABLE_ACCEL = 'req_accelerometer_enable'
+ _DISABLE_ACCEL = 'req_accelerometer_disable'
+
+ # Defined in mce/dbus-names.h
+ _MCE_SERVICE = 'com.nokia.mce'
+ _MCE_REQUEST_PATH = '/com/nokia/mce/request'
+ _MCE_REQUEST_IF = 'com.nokia.mce.request'
+
+ # sysfs device name for the keyboard slider switch
+ KBD_SLIDER = '/sys/devices/platform/gpio-switch/slide/state'
+ _KBD_OPEN = 'open'
+ _KBD_CLOSED = 'closed'
+
+ def __init__(self, app_name, main_window=None, version='1.0', mode=0,
+ dontrotate=False):
+ """Create a new rotation manager
+
+ app_name ... The name of your application (for osso.Context)
+ main_window ... The root window (optional, hildon.StackableWindow)
+ version ... The version of your application (optional, string)
+ mode ... Initial mode for this manager (default: AUTOMATIC)
+ dontrotate ... Don't rotate the window. (def: False)
+ """
+ self.dontrotate = dontrotate # V13
+ self._orientation = None
+ self._main_window = main_window
+ self._stack = hildon.WindowStack.get_default()
+ self._mode = -1
+ self._last_dbus_orientation = None
+ self._keyboard_state = self._get_keyboard_state()
+ app_id = '-'.join((app_name, self.__class__.__name__))
+ self._osso_context = osso.Context(app_id, version, False)
+ program = hildon.Program.get_instance()
+ program.connect('notify::is-topmost', self._on_topmost_changed)
+
+ # Hack for dbus. See:
+ # https://garage.maemo.org/pipermail/pymaemo-developers/2010-April/001445.html
+ # https://garage.maemo.org/pipermail/pymaemo-developers/2010-April/001454.html
+ # https://garage.maemo.org/pipermail/pymaemo-developers/2010-April/thread.html
+ # https://bugs.maemo.org/show_bug.cgi?id=8611
+ #
+ # If we use dbus.Bus.get_system() or dbus.SystemBus() then the
+ # program fails whenever bluezwitch is installed. This could
+ # also happen whenever another widget is using System Bus (sure?).
+ #
+ #V13 system_bus = dbus.Bus.get_system()
+ dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+ busaddress='unix:path=/var/run/dbus/system_bus_socket'
+ system_bus=dbus.bus.BusConnection(busaddress)
+ self.system_bus=system_bus
+
+ system_bus.add_signal_receiver(self._on_orientation_signal, \
+ signal_name='sig_device_orientation_ind', \
+ dbus_interface='com.nokia.mce.signal', \
+ path='/com/nokia/mce/signal')
+ system_bus.add_signal_receiver(self._on_keyboard_signal, \
+ signal_name='Condition', \
+ dbus_interface='org.freedesktop.Hal.Device', \
+ path='/org/freedesktop/Hal/devices/platform_slide')
+ self.set_mode(mode)
+
+ def get_mode(self):
+ """Get the currently-set rotation mode
+
+ This will return one of three values: AUTOMATIC, ALWAYS or NEVER.
+ """
+ return self._mode
+
+ def set_mode(self, new_mode):
+ """Set the rotation mode
+
+ You can set the rotation mode to AUTOMATIC (use hardware rotation
+ info), ALWAYS (force portrait) and NEVER (force landscape).
+ """
+ if new_mode not in (self.AUTOMATIC, self.ALWAYS, self.NEVER):
+ raise ValueError('Unknown rotation mode')
+
+ if self._mode != new_mode:
+ if self._mode == self.AUTOMATIC:
+ # Remember the current "automatic" orientation for later
+ self._last_dbus_orientation = self._orientation
+ # Tell MCE that we don't need the accelerometer anymore
+ self._send_mce_request(self._DISABLE_ACCEL)
+
+ if new_mode == self.NEVER:
+ self._orientation_changed(self._LANDSCAPE)
+ elif new_mode == self.ALWAYS and \
+ self._keyboard_state != self._KBD_OPEN:
+ self._orientation_changed(self._PORTRAIT)
+ elif new_mode == self.AUTOMATIC:
+ # Restore the last-known "automatic" orientation
+ self._orientation_changed(self._last_dbus_orientation)
+ # Tell MCE that we need the accelerometer again
+ self._send_mce_request(self._ENABLE_ACCEL)
+
+ self._mode = new_mode
+
+ def reset_mode(self):
+ if self._mode==self.AUTOMATIC:
+ self._send_mce_request(self._ENABLE_ACCEL)
+ else:
+ self._send_mce_request(self._DISABLE_ACCEL)
+
+ def _send_mce_request(self, request):
+ rpc = osso.Rpc(self._osso_context)
+ rpc.rpc_run(self._MCE_SERVICE, \
+ self._MCE_REQUEST_PATH, \
+ self._MCE_REQUEST_IF, \
+ request, \
+ use_system_bus=True)
+
+ def _on_topmost_changed(self, program, property_spec):
+ # XXX: This seems to never get called on Fremantle(?)
+ if self._mode == self.AUTOMATIC:
+ if program.get_is_topmost():
+ self._send_mce_request(self._ENABLE_ACCEL)
+ else:
+ self._send_mce_request(self._DISABLE_ACCEL)
+
+ def _get_main_window(self):
+ if self._main_window:
+ # If we have gotten the main window as parameter, return it and
+ # don't try "harder" to find another window using the stack
+ return self._main_window
+ else:
+ # The main window is at the "bottom" of the window stack, and as
+ # the list we get with get_windows() is sorted "topmost first", we
+ # simply take the last item of the list to get our main window
+ windows = self._stack.get_windows()
+ if windows:
+ return windows[-1]
+ else:
+ return None
+
+ def _orientation_changed(self, orientation):
+ if self._orientation == orientation:
+ # Ignore repeated requests
+ return
+
+ flags = 0
+
+ if orientation != self._LANDSCAPE:
+ flags |= hildon.PORTRAIT_MODE_SUPPORT
+
+ if orientation == self._PORTRAIT:
+ flags |= hildon.PORTRAIT_MODE_REQUEST
+
+ window = self._get_main_window()
+ if window is not None and self.dontrotate==False:
+ hildon.hildon_gtk_window_set_portrait_flags(window, flags)
+
+ self._orientation = orientation
+
+ self.on_orientation_changed(orientation)
+
+ def on_orientation_changed(self, orientation):
+ pass
+
+ def _get_keyboard_state(self):
+ # For sbox, if the device does not exist assume that it's closed
+ try:
+ return open(self.KBD_SLIDER).read().strip()
+ except IOError:
+ return self._KBD_CLOSED
+
+ def _keyboard_state_changed(self):
+ state = self._get_keyboard_state()
+
+ if state == self._KBD_OPEN:
+ self._orientation_changed(self._LANDSCAPE)
+ elif state == self._KBD_CLOSED:
+ if self._mode == self.AUTOMATIC:
+ self._orientation_changed(self._last_dbus_orientation)
+ elif self._mode == self.ALWAYS:
+ self._orientation_changed(self._PORTRAIT)
+
+ self._keyboard_state = state
+
+ def _on_keyboard_signal(self, condition, button_name):
+ if condition == 'ButtonPressed' and button_name == 'cover':
+ self._keyboard_state_changed()
+
+ def _on_orientation_signal(self, orientation, stand, face, x, y, z):
+ if orientation in (self._PORTRAIT, self._LANDSCAPE):
+ if self._mode == self.AUTOMATIC and \
+ self._keyboard_state != self._KBD_OPEN:
+ # Automatically set the rotation based on hardware orientation
+ self._orientation_changed(orientation)
+
+ # Save the current orientation for "automatic" mode later on
+ self._last_dbus_orientation = orientation
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
--- /dev/null
+# -*- coding: utf-8 -*-
+#
+# gPodder - A media aggregator and podcast client
+# Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
+#
+# gPodder 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.
+#
+# gPodder 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/>.
+#
+
+import dbus
+import dbus.glib
+
+import hildon
+import osso
+
+# Replace this with your own gettext() functionality
+import gpodder
+_ = gpodder.gettext
+
+
+class FremantleRotation(object):
+ """thp's screen rotation for Maemo 5
+
+ Simply instantiate an object of this class and let it auto-rotate
+ your StackableWindows depending on the device orientation.
+
+ If you need to relayout a window, connect to its "configure-event"
+ signal and measure the ratio of width/height and relayout for that.
+
+ You can set the mode for rotation to AUTOMATIC (default), NEVER or
+ ALWAYS with the set_mode() method.
+ """
+ AUTOMATIC, NEVER, ALWAYS = range(3)
+
+ # Human-readable captions for the above constants
+ MODE_CAPTIONS = (_('Automatic'), _('Landscape'), _('Portrait'))
+
+ # Privately-used constants
+ _PORTRAIT, _LANDSCAPE = ('portrait', 'landscape')
+ _ENABLE_ACCEL = 'req_accelerometer_enable'
+ _DISABLE_ACCEL = 'req_accelerometer_disable'
+
+ # Defined in mce/dbus-names.h
+ _MCE_SERVICE = 'com.nokia.mce'
+ _MCE_REQUEST_PATH = '/com/nokia/mce/request'
+ _MCE_REQUEST_IF = 'com.nokia.mce.request'
+
+ # sysfs device name for the keyboard slider switch
+ KBD_SLIDER = '/sys/devices/platform/gpio-switch/slide/state'
+ _KBD_OPEN = 'open'
+ _KBD_CLOSED = 'closed'
+
+ def __init__(self, app_name, main_window=None, version='1.0', mode=0):
+ """Create a new rotation manager
+
+ app_name ... The name of your application (for osso.Context)
+ main_window ... The root window (optional, hildon.StackableWindow)
+ version ... The version of your application (optional, string)
+ mode ... Initial mode for this manager (default: AUTOMATIC)
+ """
+ self._orientation = None
+ self._main_window = main_window
+ self._stack = hildon.WindowStack.get_default()
+ self._mode = -1
+ self._last_dbus_orientation = None
+ self._keyboard_state = self._get_keyboard_state()
+ app_id = '-'.join((app_name, self.__class__.__name__))
+ self._osso_context = osso.Context(app_id, version, False)
+ program = hildon.Program.get_instance()
+ program.connect('notify::is-topmost', self._on_topmost_changed)
+ system_bus = dbus.Bus.get_system()
+ system_bus.add_signal_receiver(self._on_orientation_signal, \
+ signal_name='sig_device_orientation_ind', \
+ dbus_interface='com.nokia.mce.signal', \
+ path='/com/nokia/mce/signal')
+ system_bus.add_signal_receiver(self._on_keyboard_signal, \
+ signal_name='Condition', \
+ dbus_interface='org.freedesktop.Hal.Device', \
+ path='/org/freedesktop/Hal/devices/platform_slide')
+ self.set_mode(mode)
+
+ def get_mode(self):
+ """Get the currently-set rotation mode
+
+ This will return one of three values: AUTOMATIC, ALWAYS or NEVER.
+ """
+ return self._mode
+
+ def set_mode(self, new_mode):
+ """Set the rotation mode
+
+ You can set the rotation mode to AUTOMATIC (use hardware rotation
+ info), ALWAYS (force portrait) and NEVER (force landscape).
+ """
+ if new_mode not in (self.AUTOMATIC, self.ALWAYS, self.NEVER):
+ raise ValueError('Unknown rotation mode')
+
+ if self._mode != new_mode:
+ if self._mode == self.AUTOMATIC:
+ # Remember the current "automatic" orientation for later
+ self._last_dbus_orientation = self._orientation
+ # Tell MCE that we don't need the accelerometer anymore
+ self._send_mce_request(self._DISABLE_ACCEL)
+
+ if new_mode == self.NEVER:
+ self._orientation_changed(self._LANDSCAPE)
+ elif new_mode == self.ALWAYS and \
+ self._keyboard_state != self._KBD_OPEN:
+ self._orientation_changed(self._PORTRAIT)
+ elif new_mode == self.AUTOMATIC:
+ # Restore the last-known "automatic" orientation
+ self._orientation_changed(self._last_dbus_orientation)
+ # Tell MCE that we need the accelerometer again
+ self._send_mce_request(self._ENABLE_ACCEL)
+
+ self._mode = new_mode
+
+ def _send_mce_request(self, request):
+ rpc = osso.Rpc(self._osso_context)
+ rpc.rpc_run(self._MCE_SERVICE, \
+ self._MCE_REQUEST_PATH, \
+ self._MCE_REQUEST_IF, \
+ request, \
+ use_system_bus=True)
+
+ def _on_topmost_changed(self, program, property_spec):
+ # XXX: This seems to never get called on Fremantle(?)
+ if self._mode == self.AUTOMATIC:
+ if program.get_is_topmost():
+ self._send_mce_request(self._ENABLE_ACCEL)
+ else:
+ self._send_mce_request(self._DISABLE_ACCEL)
+
+ def _get_main_window(self):
+ if self._main_window:
+ # If we have gotten the main window as parameter, return it and
+ # don't try "harder" to find another window using the stack
+ return self._main_window
+ else:
+ # The main window is at the "bottom" of the window stack, and as
+ # the list we get with get_windows() is sorted "topmost first", we
+ # simply take the last item of the list to get our main window
+ windows = self._stack.get_windows()
+ if windows:
+ return windows[-1]
+ else:
+ return None
+
+ def _orientation_changed(self, orientation):
+ if self._orientation == orientation:
+ # Ignore repeated requests
+ return
+
+ flags = 0
+
+ if orientation != self._LANDSCAPE:
+ flags |= hildon.PORTRAIT_MODE_SUPPORT
+
+ if orientation == self._PORTRAIT:
+ flags |= hildon.PORTRAIT_MODE_REQUEST
+
+ window = self._get_main_window()
+ if window is not None:
+ hildon.hildon_gtk_window_set_portrait_flags(window, flags)
+
+ self._orientation = orientation
+
+ def _get_keyboard_state(self):
+ # For sbox, if the device does not exist assume that it's closed
+ try:
+ return open(self.KBD_SLIDER).read().strip()
+ except IOError:
+ return self._KBD_CLOSED
+
+ def _keyboard_state_changed(self):
+ state = self._get_keyboard_state()
+
+ if state == self._KBD_OPEN:
+ self._orientation_changed(self._LANDSCAPE)
+ elif state == self._KBD_CLOSED:
+ if self._mode == self.AUTOMATIC:
+ self._orientation_changed(self._last_dbus_orientation)
+ elif self._mode == self.ALWAYS:
+ self._orientation_changed(self._PORTRAIT)
+
+ self._keyboard_state = state
+
+ def _on_keyboard_signal(self, condition, button_name):
+ if condition == 'ButtonPressed' and button_name == 'cover':
+ self._keyboard_state_changed()
+
+ def _on_orientation_signal(self, orientation, stand, face, x, y, z):
+ if orientation in (self._PORTRAIT, self._LANDSCAPE):
+ if self._mode == self.AUTOMATIC and \
+ self._keyboard_state != self._KBD_OPEN:
+ # Automatically set the rotation based on hardware orientation
+ self._orientation_changed(orientation)
+
+ # Save the current orientation for "automatic" mode later on
+ self._last_dbus_orientation = orientation
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+import gtk
+
+class Disconnector(object):
+ def __init__(self):
+ self.chandles=[]
+ if isinstance(self, gtk.Widget):
+ self.c(self, 'delete-event', self.slotDiscDeleteEvent)
+
+ def c(self, obj, signal, slot, *args):
+ h=obj.connect(signal, slot, *args)
+ self.chandles.append((obj, h))
+
+ return(h)
+
+ def slotDiscDeleteEvent(self, sender, event):
+# print "sig-delete-event"
+ self.dis_finish()
+ return(False)
+
+ def dis_finish(self, obj=None):
+ """ Disconnect all signals. If obj is specified the only disconnect
+ signals from that object """
+ for i in self.chandles:
+ if obj!=None and i[0]!=obj:
+ continue
+ #print i[0], i[1]
+ i[0].disconnect(i[1])
+
+ if obj==None:
+ self.chandles=[]
+ else:
+ self.chandles=[a for a in self.chandles if a[0]!=obj]
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+# HACK
+# Add the current module's directory to sys.path to bypass
+# problems when running as widget.
+# Restore the path at the end of the imports
+import sys
+import os
+
+orig_path=sys.path[:]
+tmp_path=os.path.dirname( os.path.realpath( __file__ ) )
+sys.path.append(tmp_path)
+
+# End of hack
+
+import gtk
+import gobject
+import hildon
+from hildondesktop import *
+from gtk import gdk
+from math import pi
+import cairo
+import gconf
+import time
+
+from subprocess import Popen,PIPE
+
+from portrait import FremantleRotation
+import launcher
+from xdg.IconTheme import getIconPath
+from win_config import WinConfig
+
+import config
+import apps
+from icon import Icon
+from icongrid import IconGrid
+from sig import Disconnector
+
+# Restore path
+sys.path=orig_path
+
+# IconGrid must be before HomePluginItem for its connect()
+# and do_button_*() to override those of HomePluginItem
+class DrlaunchPlugin(IconGrid, HomePluginItem, FremantleRotation, Disconnector):
+ def __init__(self):
+ IconGrid.__init__(self)
+ HomePluginItem.__init__(self)
+ FremantleRotation.__init__(self, 'DrlaunchPlugin',
+ mode=FremantleRotation.AUTOMATIC, dontrotate=True)
+ Disconnector.__init__(self)
+
+ self.winConfig=None
+
+ self.gconf=gconf.client_get_default()
+
+ self.set_settings(True)
+
+ self.id=None
+ self.config=None
+
+ self.reset_mode()
+
+ def get_id0(self):
+ """If this is called from the constructor then the program
+ core dumps """
+ aid=self.get_applet_id()
+
+ # Get desktop activity if D.A.M. is present
+
+ act="/usr/bin/activity"
+
+ if os.path.exists(act):
+ r=Popen([act, "current"], stdout=PIPE).communicate()
+ activity=r[0].strip()
+ else:
+ activity=""
+
+ ret="%s-%s" % (aid, activity)
+
+ return(ret)
+
+ def get_id(self):
+ if self.id==None:
+ self.id=self.get_id0()
+
+ return(self.id)
+
+ def get_config(self):
+ if self.config==None:
+ id=self.get_id()
+ self.config=config.Config(id)
+
+ return(self.config)
+
+ def get_desktop_orientation(self):
+ """
+ Return desktop orientation
+
+ NOTE: This is the desktop orientation as it was introduced in CSSU.
+ Not the device orientation.
+
+ @return "portrait" or "landscape"
+ """
+
+ sw=gdk.screen_width()
+ sh=gdk.screen_height()
+
+ if sw>=sh:
+ ret='landscape'
+ else:
+ ret='portrait'
+
+ return(ret)
+
+ def is_rotating_desktop(self):
+ """
+ Check whether the desktop will change to portrait mode, as
+ added in CSSU.
+
+ @return True/False
+ """
+
+ c=self.gconf
+
+ # This returns False if the key doesn't exist
+ ret=c.get_bool('/apps/osso/hildon-desktop/ui_can_rotate')
+
+ return(ret)
+
+ def do_realize(self):
+ launcher.init()
+ config=self.get_config()
+ config.load()
+
+ IconGrid.do_realize(self, config)
+
+ self.setSize(config.getSize())
+ self.reloadIcons()
+
+ screen=self.get_screen()
+ self.set_colormap(screen.get_rgba_colormap())
+ self.set_app_paintable(True)
+
+ self.c(self, 'show-settings', self.slot_show_settings)
+ self.c(self, 'long-press', self.signalLongpress)
+ self.c(self, 'click', self.signalClick)
+ self.c(self, 'notify', self.signalNotify)
+
+ HomePluginItem.do_realize(self)
+
+ def on_orientation_changed(self, orientation):
+ # Avoid bugs
+ if orientation==None or len(orientation)==0:
+ return
+
+ # Get the first character of the string (l/p)
+ o=orientation[0]
+
+ # Get desktop orientation
+ #do=self.get_desktop_orientation()
+
+ # Is desktop rotation (per CSSU) enabled?
+ rd=self.is_rotating_desktop()
+
+ #print "desktop: %s / %s, device: %s" % (do, rd, o)
+
+ # In case of a rotating desktop, force orientation to be
+ # 'landscape'
+ if rd:
+ o='l'
+
+ self.setMode(o)
+# self.queue_draw()
+
+ def do_expose_event(self, event):
+ IconGrid.do_expose_event(self, event)
+ HomePluginItem.do_expose_event(self, event)
+ self.reset_mode()
+
+ def slot_show_settings(self, dt):
+ if self.winConfig!=None:
+ # Doesn't work
+ # self.winConfig.show_all()
+ return
+
+ s=WinConfig(self.get_config())
+ s.show_all()
+ #s.c(s, 'delete-event', self.slotConfigDestroy)
+ self.c(s, 'delete-event', self.slotConfigDestroy)
+ #s.connect('destroy', self.slotConfigDestroy)
+ self.winConfig=s
+
+ def slotConfigDestroy(self, sender, event):
+# print "Sender:", sender
+ dt=sender.getData()
+
+ # Disconnect signals for that object in order to be deleted
+ self.dis_finish(self.winConfig)
+ #self.winConfig.finish()
+ #self.winConfig.destroy()
+
+ self.winConfig=None
+
+ cfg=self.get_config()
+
+ cfg.setSize(dt['size'])
+ cfg.setApps(dt['apps'])
+ cfg.setIndiv(dt['indiv'])
+ cfg.setLongpress(dt['longpress'])
+ cfg.setAnimate(dt['animate'])
+ cfg.setNoBg(dt['nobg'])
+ cfg.setThemeBg(dt['themebg'])
+ cfg.setIconSize(dt['iconsize'])
+ cfg.setIconPadding(dt['iconpadding'])
+ cfg.setIconMargin(dt['iconmargin'])
+ cfg.save()
+
+ # Resize widget
+ self.icons.resizeMax()
+ self.setSize(dt['size'])
+ self.reloadIcons()
+
+ # Free memory that is used for animations if animations are disabled
+ if not dt['animate']:
+ self.clearAnimationCache()
+
+ # Free memory of backgrounds in case they changed
+ self.clearBgCache()
+
+ self.queue_draw()
+
+# print "slot-config-destroy-end"
+
+ return(False)
+
+ def handle_click(self, sender, icon):
+ """ common handler for longpress and click """
+ if icon.appname!=None and icon.appname!='':
+ launcher.launch(icon.appname)
+
+ def signalLongpress(self, sender, icon):
+ self.handle_click(sender, icon)
+
+ def signalClick(self, sender, icon):
+ config=self.get_config()
+
+ if not config.getLongpress():
+ self.handle_click(sender, icon)
+
+ def signalNotify(self, sender, property):
+ if property.name=='is-on-current-desktop':
+ v=self.get_property(property.name)
+ if v and self.draw_pending:
+ self.queue_draw()
+
+ def resize2(self):
+ config=self.get_config()
+
+ w=(self.size[0] * config.iconsize) + \
+ (self.size[0] * config.getIconSpace())
+ h=(self.size[1] * config.iconsize) + \
+ (self.size[1] * config.getIconSpace())
+ self.set_size_request(w, h)
+ self.resize(w, h)
+
+ def setSize(self, size):
+ IconGrid.setSize(self, size)
+ self.resize2()
+
+hd_plugin_type = DrlaunchPlugin
+
+if __name__=="__main__":
+ gobject.type_register(hd_plugin_type)
+ obj=gobject.new(hd_plugin_type, plugin_id="plugin_id")
+ obj.show_all()
+ gtk.main()
+
+
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of wifieye.
+#
+# wifieye 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.
+#
+# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+import gtk
+import gobject
+import hildon
+import time
+import copy
+
+from hildon import StackableWindow
+#from portrait import FremantleRotation
+#from xdg.IconTheme import getIconPath
+
+import config
+import apps
+from icon import Icon, getIcon
+from iconw import IconWidget
+from icongrid import IconGridWidget
+from about import DlgAbout
+from portrait import FremantleRotation
+from sig import Disconnector
+
+class DialogIconSize(gtk.Dialog):
+ def __init__(self, config):
+ gtk.Dialog.__init__(self, "Adjust icon size")
+
+ # Operate on copy of config
+ self.config=copy.copy(config)
+ # And keep another copy to be able to undo
+ self.config0=copy.copy(config)
+
+ vbox=self.vbox
+
+ self.pa=hildon.PannableArea()
+ vbox.add(self.pa)
+ self.pa.set_property('mov-mode', hildon.MOVEMENT_MODE_VERT)
+
+ vbox=gtk.VBox()
+ self.pa.add_with_viewport(vbox)
+ self.pa.set_size_request(-1, 1000)
+
+ self.labelDim=gtk.Label("koko")
+ vbox.add(self.labelDim)
+
+ vbox.add(gtk.Label("Icon size:"))
+ scale=gtk.HScale()
+ scale.set_digits(0)
+ scale.set_value_pos(gtk.POS_RIGHT)
+ scale.set_range(*self.config.getIconSizeRange())
+ scale.set_increments(8, 16)
+ self.scaleIconSize=scale
+ vbox.add(scale)
+
+ hbox=gtk.HBox()
+ vbox.add(hbox)
+
+ vbox2=gtk.VBox()
+ vbox2.add(gtk.Label("Padding:"))
+ scale=gtk.HScale()
+ scale.set_digits(0)
+ scale.set_value_pos(gtk.POS_RIGHT)
+ scale.set_range(*self.config.getIconPaddingRange())
+ scale.set_increments(2,2)
+ self.scaleIconPadding=scale
+ vbox2.add(scale)
+ hbox.add(vbox2)
+
+ vbox2=gtk.VBox()
+ vbox2.add(gtk.Label("Margin:"))
+ scale=gtk.HScale()
+ scale.set_digits(0)
+ scale.set_value_pos(gtk.POS_RIGHT)
+ scale.set_range(*self.config.getIconMarginRange())
+ scale.set_increments(2,2)
+ self.scaleIconMargin=scale
+ vbox2.add(scale)
+ hbox.add(vbox2)
+
+ self.icon=IconWidget(False, self.config)
+ self.icon.setApp('osso-addressbook')
+ vbox.add(self.icon)
+
+ self.scaleIconSize.set_value(self.config.getIconSize())
+ self.scaleIconPadding.set_value(self.config.getIconPadding())
+ self.scaleIconMargin.set_value(self.config.getIconMargin())
+
+ # Set thos after settings initial values to avoid unwanted effects
+ self.scaleIconSize.connect('value-changed', self.slotSzChangeSize)
+ self.scaleIconPadding.connect('value-changed', self.slotSzChangePadding)
+ self.scaleIconMargin.connect('value-changed', self.slotSzChangeMargin)
+
+ hbox=gtk.HBox()
+ vbox.add(hbox)
+
+ but=hildon.Button(
+ gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
+ hildon.BUTTON_ARRANGEMENT_VERTICAL)
+ but.set_title("OK")
+ but.connect("clicked", self.slotButtonOK)
+ #hbox.add(but)
+ self.action_area.pack_start(but)
+ self.buttonOK=but
+
+ but=hildon.Button(
+ gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
+ hildon.BUTTON_ARRANGEMENT_VERTICAL)
+ but.set_title("Undo")
+ but.connect("clicked", self.slotButtonUndo)
+ #hbox.add(but)
+ self.action_area.pack_start(but)
+ self.buttonUndo=but
+
+ but=hildon.Button(
+ gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
+ hildon.BUTTON_ARRANGEMENT_VERTICAL)
+ but.set_title("Restore defaults")
+ but.connect("clicked", self.slotButtonDefaults)
+ #hbox.add(but)
+ self.action_area.pack_start(but)
+ self.buttonDefaults=but
+
+ self.show_all()
+
+ self.recalc()
+
+ def setWH(self, w, h):
+ self.labelDim.set_text("Grid size: %d x %d icons" % (w, h))
+ #self.labelWidth.set_text("Width: %d icons" % w)
+ #self.labelHeight.set_text("Height: %d icons" % h)
+
+ def recalc(self):
+ maxsz=self.config.getMaxSize()
+ self.setWH(maxsz[0], maxsz[1])
+ self.icon.refresh()
+
+ def getIconSize(self):
+ return(self.scaleIconSize.get_value())
+
+ def setIconSize(self, iconsize):
+ self.scaleIconSize.set_value(iconsize)
+
+ def getIconPadding(self):
+ return(self.scaleIconPadding.get_value())
+
+ def setIconPadding(self, pad):
+ self.scaleIconPadding.set_value(pad)
+
+ def getIconMargin(self):
+ return(self.scaleIconMargin.get_value())
+
+ def setIconMargin(self, margin):
+ self.scaleIconMargin.set_value(margin)
+
+ def slotSzChangeSize(self, sender):
+ self.config.setIconSize(int(self.getIconSize()))
+ self.recalc()
+
+ def slotSzChangeMargin(self, sender):
+ self.config.setIconMargin(int(self.getIconMargin()))
+ self.recalc()
+
+ def slotSzChangePadding(self, sender):
+ self.config.setIconPadding(int(self.getIconPadding()))
+ self.recalc()
+
+ def slotButtonUndo(self, sender):
+ self.config=copy.copy(self.config0)
+ self.recalc()
+ self.resetSliders()
+
+ def slotButtonDefaults(self, sender):
+ self.config.setDefaultSizes()
+ self.recalc()
+ self.resetSliders()
+
+ def slotButtonOK(self, sender):
+ self.response(gtk.RESPONSE_OK)
+
+ def resetSliders(self):
+ self.scaleIconSize.set_value(self.config.getIconSize())
+ self.scaleIconPadding.set_value(self.config.getIconPadding())
+ self.scaleIconMargin.set_value(self.config.getIconMargin())
+
+ def getData(self):
+ ret={
+ 'size': self.config.getIconSize(),
+ 'padding': self.config.getIconPadding(),
+ 'margin': self.config.getIconMargin()
+ }
+
+ return(ret)
+
+
+class WinConfig(StackableWindow, Disconnector): #, FremantleRotation):
+ def __init__(self, config, *args):
+ StackableWindow.__init__(self)
+ Disconnector.__init__(self)
+# FremantleRotation.__init__(self, "DrlaunchPlugin",
+# mode=FremantleRotation.AUTOMATIC)
+
+ self.config=copy.copy(config)
+
+ self.setupUi()
+
+# h=self.c(self, 'delete-event', self.slotDeleteEvent)
+
+# This is a nice test. If it is displayed then the window is actually
+# destroyed and there is no memory leak
+# def __del__(self):
+# print "wc-del"
+
+ def setupUi(self):
+ """
+ self.pa Main Pannable Area
+ self.col1 A VBox for the first column
+ self.col2 A VBox for the second column
+ self.w_igw The IGW in an alignment
+ """
+ self.set_title("DrLaunch v" + config.version)
+
+ self.pa=hildon.PannableArea()
+# self.add(self.pa)
+ self.pa.set_property('mov-mode', hildon.MOVEMENT_MODE_HORIZ)
+
+ self.add(self.pa)
+
+#1 hbox=gtk.HBox()
+#1 self.pa.add_with_viewport(hbox)
+
+ # Add the first column of options
+ al=gtk.Alignment(yscale=1)
+#1 hbox.add(al)
+
+ vbox=gtk.VBox()
+ al.add(vbox)
+ self.col1=al
+
+ maxsz=self.config.getMaxSize()
+
+ self.maxmaxsz=(16,16)
+
+ # ----------------------------------------------
+ vbox.add(gtk.Label('Width:'))
+
+ hbox2=gtk.HBox()
+ vbox.add(hbox2)
+
+ self.butsSizeX=[]
+ for i in xrange(self.maxmaxsz[0]):
+ but=hildon.GtkToggleButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
+ but.set_label("%s" % (i+1,))
+ self.c(but, 'toggled', self.slotButtonSizeX, i)
+
+ self.butsSizeX.append(but)
+
+ hbox2.add(but)
+
+ # ----------------------------------------------
+ vbox.add(gtk.Label('Height:'))
+
+ hbox2=gtk.HBox()
+ vbox.add(hbox2)
+
+ self.butsSizeY=[]
+ for i in xrange(self.maxmaxsz[1]):
+ but=hildon.GtkToggleButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
+ but.set_label("%s" % (i+1,))
+ self.c(but, 'toggled', self.slotButtonSizeY, i)
+
+ self.butsSizeY.append(but)
+
+ hbox2.add(but)
+
+ vbox2=gtk.VBox()
+
+ al=gtk.Alignment(xalign=0, yalign=1, xscale=1)
+ al.add(vbox2)
+ self.col11=al
+
+ vbox.add(al)
+ vbox=vbox2
+
+ but=hildon.CheckButton(
+ gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
+ but.set_label("Rotate icons individually")
+ self.c(but, 'toggled', self.slotButtonRotateIndividually)
+ self.buttonRotateIndividually=but
+ vbox.add(but)
+
+ but=hildon.CheckButton(
+ gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
+ but.set_label("Require long press")
+# but.connect('toggled', self.slotButtonLongpress)
+ self.buttonRequireLongpress=but
+ vbox.add(but)
+
+ but=hildon.CheckButton(
+ gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
+ but.set_label("Animate rotation")
+ self.buttonAnimateRotation=but
+ vbox.add(but)
+
+ # -----------------------------------------------
+ # Second column of options
+ vbox=gtk.VBox()
+
+ al=gtk.Alignment(xalign=0, yalign=1, xscale=1)
+ al.add(vbox)
+ self.col2=al
+
+ but=hildon.CheckButton(
+ gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
+ but.set_label("Theme background")
+ but.connect('toggled', self.slotButtonThemeBackground)
+ self.buttonThemeBackground=but
+ vbox.add(but)
+
+ but=hildon.CheckButton(
+ gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
+ but.set_label("No background")
+ self.c(but, 'toggled', self.slotButtonNoBackground)
+ self.buttonNoBackground=but
+ vbox.add(but)
+
+ but=hildon.Button(
+ gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
+ hildon.BUTTON_ARRANGEMENT_VERTICAL)
+ but.set_label("Adjust icon size")
+ self.c(but, 'clicked', self.slotButtonIconSize)
+ self.buttonIconSize=but
+ vbox.add(but)
+
+#1 hbox.add(al)
+ but=hildon.Button(
+ gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
+ hildon.BUTTON_ARRANGEMENT_VERTICAL)
+ but.set_title("About")
+ self.c(but, "clicked", self.slotButtonAbout)
+ vbox.add(but)
+ self.buttonAbout=but
+
+ # -----------------------------------------------
+ # Add the icongrid
+ self.igw=IconGridWidget(True, self.config, False)
+# self.igw.do_realize()
+# self.igw.setSize(config.getSize())
+ al=gtk.Alignment(xalign=0, xscale=0)
+ al.add(self.igw)
+ al.set_padding(0, 0, 20, 0)
+ self.w_igw=al
+#1 hbox.add(al)
+
+ self.igw.connect('long-press', self.slotLongpress)
+ self.igw.connect('click', self.slotLongpress)
+
+ self.ignore_toggle=False
+
+ self.setSize(self.config.getSize())
+ self.setIndiv(self.config.getIndiv())
+ self.setLongpress(self.config.getLongpress())
+ self.setAnimate(self.config.getAnimate())
+ self.setNoBg(self.config.getNoBg())
+ self.setThemeBg(self.config.getThemeBg())
+# self.setIconSize(self.config.getIconSize())
+# self.setIconPadding(self.config.getIconPadding())
+# self.setIconMargin(self.config.getIconMargin())
+
+ hbox=gtk.HBox()
+ hbox.add(self.col1)
+ hbox.add(self.col2)
+ hbox.add(self.w_igw)
+ self.pa.add_with_viewport(hbox)
+
+ self.adjustMaxSize()
+
+# def setupUi(self, orientation):
+#
+# self.setupUi0()
+#
+# hbox=gtk.HBox()
+#
+# if orientation=='l':
+# hbox.add(self.col1)
+# hbox.add(self.col2)
+# hbox.add(self.w_igw)
+# else:
+# vbox=gtk.VBox()
+# hbox.add(vbox)
+# vbox.add(self.col1)
+# vbox.add(self.col2)
+# hbox.add(self.w_igw)
+#
+# self.pa.add_with_viewport(hbox)
+#
+# if self.get_child()!=None:
+# self.remove(self.get_child())
+# self.add(self.pa)
+# self.pa.show_all()
+
+ def adjustMaxSize(self):
+ sz=self.config.getMaxSize()
+ maxsz=self.maxmaxsz
+
+ for x in xrange(maxsz[0]):
+ if x<sz[0]:
+ self.butsSizeX[x].show()
+ else:
+ self.butsSizeX[x].hide()
+
+ for y in xrange(maxsz[1]):
+ if y<sz[1]:
+ self.butsSizeY[y].show()
+ else:
+ self.butsSizeY[y].hide()
+
+ osz=self.getSize()
+ nsz=[osz[0], osz[1]]
+
+ if osz[0]>sz[0]:
+ nsz[0]=sz[0]
+ if osz[1]>sz[1]:
+ nsz[1]=sz[1]
+ self.setSize(nsz)
+
+ self.setIndiv(self.getIndiv())
+
+ def setLayoutPortrait(self):
+ print "lo: p"
+ hbox=gtk.HBox()
+
+ vbox=gtk.VBox()
+ hbox.add(vbox)
+ self.col1.reparent(vbox)
+ self.col2.reparent(vbox)
+ self.w_igw.reparent(hbox)
+
+ r=self.pa.get_children()[0]
+ self.pa.remove(r)
+ r.destroy()
+ self.pa.add_with_viewport(hbox)
+
+ self.pa.show_all()
+
+ def setLayoutLandscape(self):
+ print "lo: l"
+ hbox=gtk.HBox()
+
+ self.col1.reparent(hbox)
+ self.col2.reparent(hbox)
+ self.w_igw.reparent(hbox)
+
+ r=self.pa.get_children()[0]
+ self.pa.remove(r)
+ r.destroy()
+ self.pa.add_with_viewport(hbox)
+
+ self.pa.show_all()
+
+ def on_orientation_changed(self, orientation):
+ # This is disabled for now since I've not found any reliable
+ # way for supporting orientation changes (#$#%$#*&% GTK)
+ return
+
+ print "orch:", orientation
+ if orientation=='portrait':
+ self.setLayoutPortrait()
+ else:
+ self.setLayoutLandscape()
+
+# def slotDeleteEvent(self, sender, event):
+# print "wc-del-event"
+# return(False)
+
+ def slotLongpress(self, sender, icon):
+ self.doConfig(icon)
+
+ def slotButtonSizeX(self, sender, id):
+ if self.getIndiv():
+ old=self.getSize()
+ sz=(id+1, old[1])
+ else:
+ sz=(id+1, id+1)
+
+ self.setSize(sz)
+
+ def slotButtonSizeY(self, sender, id):
+ old=self.getSize()
+ sz=(old[0], id+1)
+ self.setSize(sz)
+
+ def slotButtonRotateIndividually(self, sender):
+ but=self.buttonRotateIndividually
+ self.setIndiv(but.get_active())
+
+ def slotButtonNoBackground(self, sender):
+ nobg=self.getNoBg()
+ self.setNoBg(nobg)
+
+ def slotButtonThemeBackground(self, sender):
+ themebg=self.getThemeBg()
+ self.setThemeBg(themebg)
+
+ def slotButtonAbout(self, sender):
+ DlgAbout.present2(self)
+
+ def slotButtonIconSize(self, sender):
+ dlg=DialogIconSize(self.config)
+ ret=dlg.run()
+ dt=dlg.getData()
+ dlg.destroy()
+
+ if ret!=gtk.RESPONSE_OK:
+ return
+
+ self.config.setIconSize(dt['size'])
+ self.config.setIconMargin(dt['margin'])
+ self.config.setIconPadding(dt['padding'])
+ self.igw.reconfig()
+ self.adjustMaxSize()
+ self.queue_draw()
+
+ def show_all(self):
+ StackableWindow.show_all(self)
+# return
+ self.adjustMaxSize()
+ self.queue_draw()
+
+# def slotScaleIconSzChange(self, sender):
+# return
+# self.config.setIconSize(self.getIconSize())
+# self.config.setIconMargin(self.getIconMargin())
+# self.config.setIconPadding(self.getIconPadding())
+# self.igw.reconfig()
+# self.adjustMaxSize()
+# self.queue_draw()
+
+# def slotButtonLongpress(self, sender):
+# but=self.buttonRequireLongpress
+# self.set
+
+ def setSize(self, sz):
+ if self.ignore_toggle:
+ return
+
+ self.ignore_toggle=True
+
+ maxsz=self.config.getMaxSize()
+
+ id=sz[0]-1
+
+ for i in xrange(maxsz[0]):
+ but=self.butsSizeX[i]
+ but.set_active(i==id)
+
+ id=sz[1]-1
+
+ for i in xrange(maxsz[1]):
+ but=self.butsSizeY[i]
+ but.set_active(i==id)
+
+ self.ignore_toggle=False
+
+ self.igw.setSize(sz)
+
+ self.igw.queue_draw()
+ self.queue_draw()
+
+ def getSize(self):
+ return(self.igw.getSize())
+
+ def getIndiv(self):
+ ret=self.buttonRotateIndividually.get_active()
+
+ return(ret)
+
+ def setIndiv(self, indiv):
+ if indiv:
+ for i in self.butsSizeY:
+ i.set_sensitive(True)
+ for i in self.butsSizeX:
+ i.set_sensitive(True)
+
+ else:
+ sz=self.config.getMaxSize()
+
+ for i in self.butsSizeY:
+ i.set_sensitive(False)
+
+ cnt=0
+ for i in self.butsSizeX:
+ cnt+=1
+ # Height is always the narrower, so use that as a limit
+ if cnt>sz[1]:
+ i.set_sensitive(False)
+ else:
+ i.set_sensitive(True)
+
+ sz=self.getSize()
+ szx=sz[0]
+ if szx>sz[1]:
+ szx=sz[1]
+ self.setSize((szx, szx))
+
+ self.buttonRotateIndividually.set_active(indiv)
+
+ def setLongpress(self, lp):
+ self.buttonRequireLongpress.set_active(lp)
+
+ def setAnimate(self, ar):
+ self.buttonAnimateRotation.set_active(ar)
+
+ def getNoBg(self):
+ ret=self.buttonNoBackground.get_active()
+ return(ret)
+
+ def setNoBg(self, nobg):
+ self.buttonNoBackground.set_active(nobg)
+
+ self.buttonThemeBackground.set_sensitive(not nobg)
+ self.config.setNoBg(nobg)
+ self.igw.reconfig()
+
+ def getThemeBg(self):
+ ret=self.buttonThemeBackground.get_active()
+ return(ret)
+
+ def setThemeBg(self, themebg):
+ self.buttonThemeBackground.set_active(themebg)
+ self.config.setThemeBg(themebg)
+ self.igw.reconfig()
+
+ def doConfig(self, icon):
+ aps=apps.scan()
+
+ lst=[aps[x]['name'] for x in aps]
+ lst.sort()
+
+ dialog=gtk.Dialog('App select', None,
+ gtk.DIALOG_DESTROY_WITH_PARENT, buttons=())
+
+ selector=hildon.TouchSelectorEntry(text=True)
+ selector.set_column_selection_mode(
+ hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
+
+ dialog.vbox.pack_start(selector, True, True, 0)
+ dialog.set_size_request(0,900)
+ dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
+ dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
+
+ selector.append_text('None')
+
+ idx=0
+ cnt=1
+ for app in lst:
+ if app==None:
+ continue
+ selector.append_text(app)
+ if icon.appname!=None and aps[icon.appname]['name']==app:
+ idx=cnt
+ cnt+=1
+
+ selector.set_active(0, idx)
+
+ dialog.show_all()
+
+ app=None
+
+ r=dialog.run()
+
+ if r==gtk.RESPONSE_OK:
+ idx2=selector.get_active(0)
+ if idx2<1:
+ app=None
+ else:
+ cur=lst[idx2-1]
+ for i in aps:
+ if aps[i]['name']==cur:
+ app=aps[i]
+ break
+ if app!=None:
+ app['icon2']=getIcon(app['icon'], self.config.getIconSize())
+ else:
+ app={
+ 'id': None,
+ 'icon2': None,
+ }
+ icon.setApp(app)
+
+ dialog.destroy()
+
+# def finish(self):
+# print "wc-finish"
+# self.igw=None
+#
+# self.dis_finish()
+
+ def getData(self):
+ szx=0
+ szy=0
+ for but in self.butsSizeX:
+ szx+=1
+ if but.get_active()==True:
+ break
+
+ for but in self.butsSizeY:
+ szy+=1
+ if but.get_active()==True:
+ break
+
+ if self.getIndiv():
+ sz=(szx, szy)
+ else:
+ sz=(szx, szx)
+
+ wapps={}
+
+ for x in xrange(sz[0]):
+ for y in xrange(sz[1]):
+ ico=self.igw.get(x,y)
+ k=(x,y)
+ wapps[k]=ico.appname
+
+ indiv=self.buttonRotateIndividually.get_active()
+ lp=self.buttonRequireLongpress.get_active()
+ ar=self.buttonAnimateRotation.get_active()
+ nobg=self.buttonNoBackground.get_active()
+ themebg=self.buttonThemeBackground.get_active()
+
+ ret={
+ 'size': sz,
+ 'apps': wapps,
+ 'indiv': indiv,
+ 'longpress': lp,
+ 'animate': ar,
+ 'nobg': nobg,
+ 'themebg': themebg,
+ 'iconsize': self.config.getIconSize(),
+ 'iconpadding': self.config.getIconPadding(),
+ 'iconmargin': self.config.getIconMargin(),
+ }
+
+ return(ret)
+
+if __name__=="__main__":
+ win=WinConfig()
+ win.connect('delete-event', gtk.main_quit)
+
+ win.show_all()
+ gtk.main()
+
+
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+"""
+This module is based on a rox module (LGPL):
+
+http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/basedir.py?rev=1.9&view=log
+
+The freedesktop.org Base Directory specification provides a way for
+applications to locate shared data and configuration:
+
+ http://standards.freedesktop.org/basedir-spec/
+
+(based on version 0.6)
+
+This module can be used to load and save from and to these directories.
+
+Typical usage:
+
+ from rox import basedir
+
+ for dir in basedir.load_config_paths('mydomain.org', 'MyProg', 'Options'):
+ print "Load settings from", dir
+
+ dir = basedir.save_config_path('mydomain.org', 'MyProg')
+ print >>file(os.path.join(dir, 'Options'), 'w'), "foo=2"
+
+Note: see the rox.Options module for a higher-level API for managing options.
+"""
+
+from __future__ import generators
+import os
+
+_home = os.environ.get('HOME', '/')
+xdg_data_home = os.environ.get('XDG_DATA_HOME',
+ os.path.join(_home, '.local', 'share'))
+
+xdg_data_dirs = [xdg_data_home] + \
+ os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
+
+xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
+ os.path.join(_home, '.config'))
+
+xdg_config_dirs = [xdg_config_home] + \
+ os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':')
+
+xdg_cache_home = os.environ.get('XDG_CACHE_HOME',
+ os.path.join(_home, '.cache'))
+
+xdg_data_dirs = filter(lambda x: x, xdg_data_dirs)
+xdg_config_dirs = filter(lambda x: x, xdg_config_dirs)
+
+def save_config_path(*resource):
+ """Ensure $XDG_CONFIG_HOME/<resource>/ exists, and return its path.
+ 'resource' should normally be the name of your application. Use this
+ when SAVING configuration settings. Use the xdg_config_dirs variable
+ for loading."""
+ resource = os.path.join(*resource)
+ assert not resource.startswith('/')
+ path = os.path.join(xdg_config_home, resource)
+ if not os.path.isdir(path):
+ os.makedirs(path, 0700)
+ return path
+
+def save_data_path(*resource):
+ """Ensure $XDG_DATA_HOME/<resource>/ exists, and return its path.
+ 'resource' is the name of some shared resource. Use this when updating
+ a shared (between programs) database. Use the xdg_data_dirs variable
+ for loading."""
+ resource = os.path.join(*resource)
+ assert not resource.startswith('/')
+ path = os.path.join(xdg_data_home, resource)
+ if not os.path.isdir(path):
+ os.makedirs(path)
+ return path
+
+def load_config_paths(*resource):
+ """Returns an iterator which gives each directory named 'resource' in the
+ configuration search path. Information provided by earlier directories should
+ take precedence over later ones (ie, the user's config dir comes first)."""
+ resource = os.path.join(*resource)
+ for config_dir in xdg_config_dirs:
+ path = os.path.join(config_dir, resource)
+ if os.path.exists(path): yield path
+
+def load_first_config(*resource):
+ """Returns the first result from load_config_paths, or None if there is nothing
+ to load."""
+ for x in load_config_paths(*resource):
+ return x
+ return None
+
+def load_data_paths(*resource):
+ """Returns an iterator which gives each directory named 'resource' in the
+ shared data search path. Information provided by earlier directories should
+ take precedence over later ones."""
+ resource = os.path.join(*resource)
+ for data_dir in xdg_data_dirs:
+ path = os.path.join(data_dir, resource)
+ if os.path.exists(path): yield path
--- /dev/null
+"""
+Functions to configure Basic Settings
+"""
+
+language = "C"
+windowmanager = None
+icon_theme = "highcolor"
+icon_size = 48
+cache_time = 5
+root_mode = False
+
+def setWindowManager(wm):
+ global windowmanager
+ windowmanager = wm
+
+def setIconTheme(theme):
+ global icon_theme
+ icon_theme = theme
+ import xdg.IconTheme
+ xdg.IconTheme.themes = []
+
+def setIconSize(size):
+ global icon_size
+ icon_size = size
+
+def setCacheTime(time):
+ global cache_time
+ cache_time = time
+
+def setLocale(lang):
+ import locale
+ lang = locale.normalize(lang)
+ locale.setlocale(locale.LC_ALL, lang)
+ import xdg.Locale
+ xdg.Locale.update(lang)
+
+def setRootMode(boolean):
+ global root_mode
+ root_mode = boolean
--- /dev/null
+"""
+Complete implementation of the XDG Desktop Entry Specification Version 0.9.4
+http://standards.freedesktop.org/desktop-entry-spec/
+
+Not supported:
+- Encoding: Legacy Mixed
+- Does not check exec parameters
+- Does not check URL's
+- Does not completly validate deprecated/kde items
+- Does not completly check categories
+"""
+
+from xdg.IniFile import *
+from xdg.BaseDirectory import *
+import os.path
+
+class DesktopEntry(IniFile):
+ "Class to parse and validate DesktopEntries"
+
+ defaultGroup = 'Desktop Entry'
+
+ def __init__(self, filename=None):
+ self.content = dict()
+ if filename and os.path.exists(filename):
+ self.parse(filename)
+ elif filename:
+ self.new(filename)
+
+ def __str__(self):
+ return self.getName()
+
+ def parse(self, file):
+ IniFile.parse(self, file, ["Desktop Entry", "KDE Desktop Entry"])
+
+ # start standard keys
+ def getType(self):
+ return self.get('Type')
+ """ @deprecated, use getVersionString instead """
+ def getVersion(self):
+ return self.get('Version', type="numeric")
+ def getVersionString(self):
+ return self.get('Version')
+ def getName(self):
+ return self.get('Name', locale=True)
+ def getGenericName(self):
+ return self.get('GenericName', locale=True)
+ def getNoDisplay(self):
+ return self.get('NoDisplay', type="boolean")
+ def getComment(self):
+ return self.get('Comment', locale=True)
+ def getIcon(self):
+ return self.get('Icon', locale=True)
+ def getHidden(self):
+ return self.get('Hidden', type="boolean")
+ def getOnlyShowIn(self):
+ return self.get('OnlyShowIn', list=True)
+ def getNotShowIn(self):
+ return self.get('NotShowIn', list=True)
+ def getTryExec(self):
+ return self.get('TryExec')
+ def getExec(self):
+ return self.get('Exec')
+ def getPath(self):
+ return self.get('Path')
+ def getTerminal(self):
+ return self.get('Terminal', type="boolean")
+ """ @deprecated, use getMimeTypes instead """
+ def getMimeType(self):
+ return self.get('MimeType', list=True, type="regex")
+ def getMimeTypes(self):
+ return self.get('MimeType', list=True)
+ def getCategories(self):
+ return self.get('Categories', list=True)
+ def getStartupNotify(self):
+ return self.get('StartupNotify', type="boolean")
+ def getStartupWMClass(self):
+ return self.get('StartupWMClass')
+ def getURL(self):
+ return self.get('URL')
+ # end standard keys
+
+ # start kde keys
+ def getServiceTypes(self):
+ return self.get('ServiceTypes', list=True)
+ def getDocPath(self):
+ return self.get('DocPath')
+ def getKeywords(self):
+ return self.get('Keywords', list=True, locale=True)
+ def getInitialPreference(self):
+ return self.get('InitialPreference')
+ def getDev(self):
+ return self.get('Dev')
+ def getFSType(self):
+ return self.get('FSType')
+ def getMountPoint(self):
+ return self.get('MountPoint')
+ def getReadonly(self):
+ return self.get('ReadOnly', type="boolean")
+ def getUnmountIcon(self):
+ return self.get('UnmountIcon', locale=True)
+ # end kde keys
+
+ # start deprecated keys
+ def getMiniIcon(self):
+ return self.get('MiniIcon', locale=True)
+ def getTerminalOptions(self):
+ return self.get('TerminalOptions')
+ def getDefaultApp(self):
+ return self.get('DefaultApp')
+ def getProtocols(self):
+ return self.get('Protocols', list=True)
+ def getExtensions(self):
+ return self.get('Extensions', list=True)
+ def getBinaryPattern(self):
+ return self.get('BinaryPattern')
+ def getMapNotify(self):
+ return self.get('MapNotify')
+ def getEncoding(self):
+ return self.get('Encoding')
+ def getSwallowTitle(self):
+ return self.get('SwallowTitle', locale=True)
+ def getSwallowExec(self):
+ return self.get('SwallowExec')
+ def getSortOrder(self):
+ return self.get('SortOrder', list=True)
+ def getFilePattern(self):
+ return self.get('FilePattern', type="regex")
+ def getActions(self):
+ return self.get('Actions', list=True)
+ # end deprecated keys
+
+ # desktop entry edit stuff
+ def new(self, filename):
+ if os.path.splitext(filename)[1] == ".desktop":
+ type = "Application"
+ elif os.path.splitext(filename)[1] == ".directory":
+ type = "Directory"
+ else:
+ raise ParsingError("Unknown extension", filename)
+
+ self.content = dict()
+ self.addGroup(self.defaultGroup)
+ self.set("Type", type)
+ self.filename = filename
+ # end desktop entry edit stuff
+
+ # validation stuff
+ def checkExtras(self):
+ # header
+ if self.defaultGroup == "KDE Desktop Entry":
+ self.warnings.append('[KDE Desktop Entry]-Header is deprecated')
+
+ # file extension
+ if self.fileExtension == ".kdelnk":
+ self.warnings.append("File extension .kdelnk is deprecated")
+ elif self.fileExtension != ".desktop" and self.fileExtension != ".directory":
+ self.warnings.append('Unknown File extension')
+
+ # Type
+ try:
+ self.type = self.content[self.defaultGroup]["Type"]
+ except KeyError:
+ self.errors.append("Key 'Type' is missing")
+
+ # Name
+ try:
+ self.name = self.content[self.defaultGroup]["Name"]
+ except KeyError:
+ self.errors.append("Key 'Name' is missing")
+
+ def checkGroup(self, group):
+ # check if group header is valid
+ if not (group == self.defaultGroup \
+ or re.match("^\Desktop Action [a-zA-Z]+\$", group) \
+ or (re.match("^\X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group)):
+ self.errors.append("Invalid Group name: %s" % group)
+ else:
+ #OnlyShowIn and NotShowIn
+ if self.content[group].has_key("OnlyShowIn") and self.content[group].has_key("NotShowIn"):
+ self.errors.append("Group may either have OnlyShowIn or NotShowIn, but not both")
+
+ def checkKey(self, key, value, group):
+ # standard keys
+ if key == "Type":
+ if value == "ServiceType" or value == "Service" or value == "FSDevice":
+ self.warnings.append("Type=%s is a KDE extension" % key)
+ elif value == "MimeType":
+ self.warnings.append("Type=MimeType is deprecated")
+ elif not (value == "Application" or value == "Link" or value == "Directory"):
+ self.errors.append("Value of key 'Type' must be Application, Link or Directory, but is '%s'" % value)
+
+ if self.fileExtension == ".directory" and not value == "Directory":
+ self.warnings.append("File extension is .directory, but Type is '%s'" % value)
+ elif self.fileExtension == ".desktop" and value == "Directory":
+ self.warnings.append("Files with Type=Directory should have the extension .directory")
+
+ if value == "Application":
+ if not self.content[group].has_key("Exec"):
+ self.warnings.append("Type=Application needs 'Exec' key")
+ if value == "Link":
+ if not self.content[group].has_key("URL"):
+ self.warnings.append("Type=Application needs 'Exec' key")
+
+ elif key == "Version":
+ self.checkValue(key, value)
+
+ elif re.match("^Name"+xdg.Locale.regex+"$", key):
+ pass # locale string
+
+ elif re.match("^GenericName"+xdg.Locale.regex+"$", key):
+ pass # locale string
+
+ elif key == "NoDisplay":
+ self.checkValue(key, value, type="boolean")
+
+ elif re.match("^Comment"+xdg.Locale.regex+"$", key):
+ pass # locale string
+
+ elif re.match("^Icon"+xdg.Locale.regex+"$", key):
+ self.checkValue(key, value)
+
+ elif key == "Hidden":
+ self.checkValue(key, value, type="boolean")
+
+ elif key == "OnlyShowIn":
+ self.checkValue(key, value, list=True)
+ self.checkOnlyShowIn(value)
+
+ elif key == "NotShowIn":
+ self.checkValue(key, value, list=True)
+ self.checkOnlyShowIn(value)
+
+ elif key == "TryExec":
+ self.checkValue(key, value)
+ self.checkType(key, "Application")
+
+ elif key == "Exec":
+ self.checkValue(key, value)
+ self.checkType(key, "Application")
+
+ elif key == "Path":
+ self.checkValue(key, value)
+ self.checkType(key, "Application")
+
+ elif key == "Terminal":
+ self.checkValue(key, value, type="boolean")
+ self.checkType(key, "Application")
+
+ elif key == "MimeType":
+ self.checkValue(key, value, list=True)
+ self.checkType(key, "Application")
+
+ elif key == "Categories":
+ self.checkValue(key, value)
+ self.checkType(key, "Application")
+ self.checkCategorie(value)
+
+ elif key == "StartupNotify":
+ self.checkValue(key, value, type="boolean")
+ self.checkType(key, "Application")
+
+ elif key == "StartupWMClass":
+ self.checkType(key, "Application")
+
+ elif key == "URL":
+ self.checkValue(key, value)
+ self.checkType(key, "URL")
+
+ # kde extensions
+ elif key == "ServiceTypes":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "DocPath":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif re.match("^Keywords"+xdg.Locale.regex+"$", key):
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "InitialPreference":
+ self.checkValue(key, value, type="numeric")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "Dev":
+ self.checkValue(key, value)
+ self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "FSType":
+ self.checkValue(key, value)
+ self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "MountPoint":
+ self.checkValue(key, value)
+ self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif key == "ReadOnly":
+ self.checkValue(key, value, type="boolean")
+ self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ elif re.match("^UnmountIcon"+xdg.Locale.regex+"$", key):
+ self.checkValue(key, value)
+ self.checkType(key, "FSDevice")
+ self.warnings.append("Key '%s' is a KDE extension" % key)
+
+ # deprecated keys
+ elif key == "Encoding":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif re.match("^MiniIcon"+xdg.Locale.regex+"$", key):
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "TerminalOptions":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "DefaultApp":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "Protocols":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "Extensions":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "BinaryPattern":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "MapNotify":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif re.match("^SwallowTitle"+xdg.Locale.regex+"$", key):
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "SwallowExec":
+ self.checkValue(key, value)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "FilePattern":
+ self.checkValue(key, value, type="regex", list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "SortOrder":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ elif key == "Actions":
+ self.checkValue(key, value, list=True)
+ self.warnings.append("Key '%s' is deprecated" % key)
+
+ # "X-" extensions
+ elif re.match("^X-[a-zA-Z0-9-]+", key):
+ pass
+
+ else:
+ self.errors.append("Invalid key: %s" % key)
+
+ def checkType(self, key, type):
+ if not self.getType() == type:
+ self.errors.append("Key '%s' only allowed in Type=%s" % (key, type))
+
+ def checkOnlyShowIn(self, value):
+ values = self.getList(value)
+ valid = ["GNOME", "KDE", "ROX", "XFCE", "Old", "LXDE"]
+ for item in values:
+ if item not in valid and item[0:2] != "X-":
+ self.errors.append("'%s' is not a registered OnlyShowIn value" % item);
+
+ def checkCategorie(self, value):
+ values = self.getList(value)
+
+ main = ["AudioVideo", "Audio", "Video", "Development", "Education", "Game", "Graphics", "Network", "Office", "Settings", "System", "Utility"]
+ hasmain = False
+ for item in values:
+ if item in main:
+ hasmain = True
+ if hasmain == False:
+ self.errors.append("Missing main category")
+
+ additional = ["Building", "Debugger", "IDE", "GUIDesigner", "Profiling", "RevisionControl", "Translation", "Calendar", "ContactManagement", "Database", "Dictionary", "Chart", "Email", "Finance", "FlowChart", "PDA", "ProjectManagement", "Presentation", "Spreadsheet", "WordProcessor", "2DGraphics", "VectorGraphics", "3DGraphics", "RasterGraphics", "Scanning", "OCR", "Photography", "Publishing", "Viewer", "TextTools", "DesktopSettings", "HardwareSettings", "Printing", "PackageManager", "Dialup", "InstantMessaging", "Chat", "IRCClient", "FileTransfer", "HamRadio", "News", "P2P", "RemoteAccess", "Telephony", "TelephonyTools", "VideoConference", "WebBrowser", "WebDevelopment", "Midi", "Mixer", "Sequencer", "Tuner", "TV", "AudioVideoEditing", "Player", "Recorder", "DiscBurning", "ActionGame", "AdventureGame", "ArcadeGame", "BoardGame", "BlocksGame", "CardGame", "KidsGame", "LogicGame", "RolePlaying", "Simulation", "SportsGame", "StrategyGame", "Art", "Construction", "Music", "Languages", "Science", "ArtificialIntelligence", "Astronomy", "Biology", "Chemistry", "ComputerScience", "DataVisualization", "Economy", "Electricity", "Geography", "Geology", "Geoscience", "History", "ImageProcessing", "Literature", "Math", "NumericalAnalysis", "MedicalSoftware", "Physics", "Robotics", "Sports", "ParallelComputing", "Amusement", "Archiving", "Compression", "Electronics", "Emulator", "Engineering", "FileTools", "FileManager", "TerminalEmulator", "Filesystem", "Monitor", "Security", "Accessibility", "Calculator", "Clock", "TextEditor", "Documentation", "Core", "KDE", "GNOME", "GTK", "Qt", "Motif", "Java", "ConsoleOnly", "Screensaver", "TrayIcon", "Applet", "Shell"]
+
+ for item in values:
+ if item not in additional + main and item[0:2] != "X-":
+ self.errors.append("'%s' is not a registered Category" % item);
+
--- /dev/null
+"""
+Exception Classes for the xdg package
+"""
+
+debug = False
+
+class Error(Exception):
+ def __init__(self, msg):
+ self.msg = msg
+ Exception.__init__(self, msg)
+ def __str__(self):
+ return self.msg
+
+class ValidationError(Error):
+ def __init__(self, msg, file):
+ self.msg = msg
+ self.file = file
+ Error.__init__(self, "ValidationError in file '%s': %s " % (file, msg))
+
+class ParsingError(Error):
+ def __init__(self, msg, file):
+ self.msg = msg
+ self.file = file
+ Error.__init__(self, "ParsingError in file '%s', %s" % (file, msg))
+
+class NoKeyError(Error):
+ def __init__(self, key, group, file):
+ Error.__init__(self, "No key '%s' in group %s of file %s" % (key, group, file))
+ self.key = key
+ self.group = group
+
+class DuplicateKeyError(Error):
+ def __init__(self, key, group, file):
+ Error.__init__(self, "Duplicate key '%s' in group %s of file %s" % (key, group, file))
+ self.key = key
+ self.group = group
+
+class NoGroupError(Error):
+ def __init__(self, group, file):
+ Error.__init__(self, "No group: %s in file %s" % (group, file))
+ self.group = group
+
+class DuplicateGroupError(Error):
+ def __init__(self, group, file):
+ Error.__init__(self, "Duplicate group: %s in file %s" % (group, file))
+ self.group = group
+
+class NoThemeError(Error):
+ def __init__(self, theme):
+ Error.__init__(self, "No such icon-theme: %s" % theme)
+ self.theme = theme
--- /dev/null
+"""
+Complete implementation of the XDG Icon Spec Version 0.8
+http://standards.freedesktop.org/icon-theme-spec/
+"""
+
+import os, sys, time
+
+from xdg.IniFile import *
+from xdg.BaseDirectory import *
+from xdg.Exceptions import *
+
+import xdg.Config
+
+class IconTheme(IniFile):
+ "Class to parse and validate IconThemes"
+ def __init__(self):
+ IniFile.__init__(self)
+
+ def __repr__(self):
+ return self.name
+
+ def parse(self, file):
+ IniFile.parse(self, file, ["Icon Theme", "KDE Icon Theme"])
+ self.dir = os.path.dirname(file)
+ (nil, self.name) = os.path.split(self.dir)
+
+ def getDir(self):
+ return self.dir
+
+ # Standard Keys
+ def getName(self):
+ return self.get('Name', locale=True)
+ def getComment(self):
+ return self.get('Comment', locale=True)
+ def getInherits(self):
+ return self.get('Inherits', list=True)
+ def getDirectories(self):
+ return self.get('Directories', list=True)
+ def getHidden(self):
+ return self.get('Hidden', type="boolean")
+ def getExample(self):
+ return self.get('Example')
+
+ # Per Directory Keys
+ def getSize(self, directory):
+ return self.get('Size', type="integer", group=directory)
+ def getContext(self, directory):
+ return self.get('Context', group=directory)
+ def getType(self, directory):
+ value = self.get('Type', group=directory)
+ if value:
+ return value
+ else:
+ return "Threshold"
+ def getMaxSize(self, directory):
+ value = self.get('MaxSize', type="integer", group=directory)
+ if value or value == 0:
+ return value
+ else:
+ return self.getSize(directory)
+ def getMinSize(self, directory):
+ value = self.get('MinSize', type="integer", group=directory)
+ if value or value == 0:
+ return value
+ else:
+ return self.getSize(directory)
+ def getThreshold(self, directory):
+ value = self.get('Threshold', type="integer", group=directory)
+ if value or value == 0:
+ return value
+ else:
+ return 2
+
+ # validation stuff
+ def checkExtras(self):
+ # header
+ if self.defaultGroup == "KDE Icon Theme":
+ self.warnings.append('[KDE Icon Theme]-Header is deprecated')
+
+ # file extension
+ if self.fileExtension == ".theme":
+ pass
+ elif self.fileExtension == ".desktop":
+ self.warnings.append('.desktop fileExtension is deprecated')
+ else:
+ self.warnings.append('Unknown File extension')
+
+ # Check required keys
+ # Name
+ try:
+ self.name = self.content[self.defaultGroup]["Name"]
+ except KeyError:
+ self.errors.append("Key 'Name' is missing")
+
+ # Comment
+ try:
+ self.comment = self.content[self.defaultGroup]["Comment"]
+ except KeyError:
+ self.errors.append("Key 'Comment' is missing")
+
+ # Directories
+ try:
+ self.directories = self.content[self.defaultGroup]["Directories"]
+ except KeyError:
+ self.errors.append("Key 'Directories' is missing")
+
+ def checkGroup(self, group):
+ # check if group header is valid
+ if group == self.defaultGroup:
+ pass
+ elif group in self.getDirectories():
+ try:
+ self.type = self.content[group]["Type"]
+ except KeyError:
+ self.type = "Threshold"
+ try:
+ self.name = self.content[group]["Name"]
+ except KeyError:
+ self.errors.append("Key 'Name' in Group '%s' is missing" % group)
+ elif not (re.match("^\[X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group):
+ self.errors.append("Invalid Group name: %s" % group)
+
+ def checkKey(self, key, value, group):
+ # standard keys
+ if group == self.defaultGroup:
+ if re.match("^Name"+xdg.Locale.regex+"$", key):
+ pass
+ elif re.match("^Comment"+xdg.Locale.regex+"$", key):
+ pass
+ elif key == "Inherits":
+ self.checkValue(key, value, list=True)
+ elif key == "Directories":
+ self.checkValue(key, value, list=True)
+ elif key == "Hidden":
+ self.checkValue(key, value, type="boolean")
+ elif key == "Example":
+ self.checkValue(key, value)
+ elif re.match("^X-[a-zA-Z0-9-]+", key):
+ pass
+ else:
+ self.errors.append("Invalid key: %s" % key)
+ elif group in self.getDirectories():
+ if key == "Size":
+ self.checkValue(key, value, type="integer")
+ elif key == "Context":
+ self.checkValue(key, value)
+ elif key == "Type":
+ self.checkValue(key, value)
+ if value not in ["Fixed", "Scalable", "Threshold"]:
+ self.errors.append("Key 'Type' must be one out of 'Fixed','Scalable','Threshold', but is %s" % value)
+ elif key == "MaxSize":
+ self.checkValue(key, value, type="integer")
+ if self.type != "Scalable":
+ self.errors.append("Key 'MaxSize' give, but Type is %s" % self.type)
+ elif key == "MinSize":
+ self.checkValue(key, value, type="integer")
+ if self.type != "Scalable":
+ self.errors.append("Key 'MinSize' give, but Type is %s" % self.type)
+ elif key == "Threshold":
+ self.checkValue(key, value, type="integer")
+ if self.type != "Threshold":
+ self.errors.append("Key 'Threshold' give, but Type is %s" % self.type)
+ elif re.match("^X-[a-zA-Z0-9-]+", key):
+ pass
+ else:
+ self.errors.append("Invalid key: %s" % key)
+
+
+class IconData(IniFile):
+ "Class to parse and validate IconData Files"
+ def __init__(self):
+ IniFile.__init__(self)
+
+ def __repr__(self):
+ return self.getDisplayName()
+
+ def parse(self, file):
+ IniFile.parse(self, file, ["Icon Data"])
+
+ # Standard Keys
+ def getDisplayName(self):
+ return self.get('DisplayName', locale=True)
+ def getEmbeddedTextRectangle(self):
+ return self.get('EmbeddedTextRectangle', list=True)
+ def getAttachPoints(self):
+ return self.get('AttachPoints', type="point", list=True)
+
+ # validation stuff
+ def checkExtras(self):
+ # file extension
+ if self.fileExtension != ".icon":
+ self.warnings.append('Unknown File extension')
+
+ def checkGroup(self, group):
+ # check if group header is valid
+ if not (group == self.defaultGroup \
+ or (re.match("^\[X-", group) and group.encode("ascii", 'ignore') == group)):
+ self.errors.append("Invalid Group name: %s" % group.encode("ascii", "replace"))
+
+ def checkKey(self, key, value, group):
+ # standard keys
+ if re.match("^DisplayName"+xdg.Locale.regex+"$", key):
+ pass
+ elif key == "EmbeddedTextRectangle":
+ self.checkValue(key, value, type="integer", list=True)
+ elif key == "AttachPoints":
+ self.checkValue(key, value, type="point", list=True)
+ elif re.match("^X-[a-zA-Z0-9-]+", key):
+ pass
+ else:
+ self.errors.append("Invalid key: %s" % key)
+
+
+
+icondirs = []
+for basedir in xdg_data_dirs:
+ icondirs.append(os.path.join(basedir, "icons"))
+ icondirs.append(os.path.join(basedir, "pixmaps"))
+icondirs.append(os.path.expanduser("~/.icons"))
+
+# just cache variables, they give a 10x speed improvement
+themes = []
+cache = dict()
+dache = dict()
+eache = dict()
+
+def getIconPath(iconname, size = None, theme = None, extensions = ["png", "svg", "xpm"]):
+ global themes
+
+ if size == None:
+ size = xdg.Config.icon_size
+ if theme == None:
+ theme = xdg.Config.icon_theme
+
+ # if we have an absolute path, just return it
+ if os.path.isabs(iconname):
+ return iconname
+
+ # check if it has an extension and strip it
+ if os.path.splitext(iconname)[1][1:] in extensions:
+ iconname = os.path.splitext(iconname)[0]
+
+ # parse theme files
+ try:
+ if themes[0].name != theme:
+ themes = []
+ __addTheme(theme)
+ except IndexError:
+ __addTheme(theme)
+
+ # more caching (icon looked up in the last 5 seconds?)
+ tmp = "".join([iconname, str(size), theme, "".join(extensions)])
+ if eache.has_key(tmp):
+ if int(time.time() - eache[tmp][0]) >= xdg.Config.cache_time:
+ del eache[tmp]
+ else:
+ return eache[tmp][1]
+
+ for thme in themes:
+ icon = LookupIcon(iconname, size, thme, extensions)
+ if icon:
+ eache[tmp] = [time.time(), icon]
+ return icon
+
+ # cache stuff again (directories lookuped up in the last 5 seconds?)
+ for directory in icondirs:
+ if (not dache.has_key(directory) \
+ or (int(time.time() - dache[directory][1]) >= xdg.Config.cache_time \
+ and dache[directory][2] < os.path.getmtime(directory))) \
+ and os.path.isdir(directory):
+ dache[directory] = [os.listdir(directory), time.time(), os.path.getmtime(directory)]
+
+ for dir, values in dache.items():
+ for extension in extensions:
+ try:
+ if iconname + "." + extension in values[0]:
+ icon = os.path.join(dir, iconname + "." + extension)
+ eache[tmp] = [time.time(), icon]
+ return icon
+ except UnicodeDecodeError, e:
+ if debug:
+ raise e
+ else:
+ pass
+
+ # we haven't found anything? "hicolor" is our fallback
+ if theme != "hicolor":
+ icon = getIconPath(iconname, size, "hicolor")
+ eache[tmp] = [time.time(), icon]
+ return icon
+
+def getIconData(path):
+ if os.path.isfile(path):
+ dirname = os.path.dirname(path)
+ basename = os.path.basename(path)
+ if os.path.isfile(os.path.join(dirname, basename + ".icon")):
+ data = IconData()
+ data.parse(os.path.join(dirname, basename + ".icon"))
+ return data
+
+def __addTheme(theme):
+ for dir in icondirs:
+ if os.path.isfile(os.path.join(dir, theme, "index.theme")):
+ __parseTheme(os.path.join(dir,theme, "index.theme"))
+ break
+ elif os.path.isfile(os.path.join(dir, theme, "index.desktop")):
+ __parseTheme(os.path.join(dir,theme, "index.desktop"))
+ break
+ else:
+ if debug:
+ raise NoThemeError(theme)
+
+def __parseTheme(file):
+ theme = IconTheme()
+ theme.parse(file)
+ themes.append(theme)
+ for subtheme in theme.getInherits():
+ __addTheme(subtheme)
+
+def LookupIcon(iconname, size, theme, extensions):
+ # look for the cache
+ if not cache.has_key(theme.name):
+ cache[theme.name] = []
+ cache[theme.name].append(time.time() - (xdg.Config.cache_time + 1)) # [0] last time of lookup
+ cache[theme.name].append(0) # [1] mtime
+ cache[theme.name].append(dict()) # [2] dir: [subdir, [items]]
+
+ # cache stuff (directory lookuped up the in the last 5 seconds?)
+ if int(time.time() - cache[theme.name][0]) >= xdg.Config.cache_time:
+ cache[theme.name][0] = time.time()
+ for subdir in theme.getDirectories():
+ for directory in icondirs:
+ dir = os.path.join(directory,theme.name,subdir)
+ if (not cache[theme.name][2].has_key(dir) \
+ or cache[theme.name][1] < os.path.getmtime(os.path.join(directory,theme.name))) \
+ and subdir != "" \
+ and os.path.isdir(dir):
+ cache[theme.name][2][dir] = [subdir, os.listdir(dir)]
+ cache[theme.name][1] = os.path.getmtime(os.path.join(directory,theme.name))
+
+ for dir, values in cache[theme.name][2].items():
+ if DirectoryMatchesSize(values[0], size, theme):
+ for extension in extensions:
+ if iconname + "." + extension in values[1]:
+ return os.path.join(dir, iconname + "." + extension)
+
+ minimal_size = sys.maxint
+ closest_filename = ""
+ for dir, values in cache[theme.name][2].items():
+ distance = DirectorySizeDistance(values[0], size, theme)
+ if distance < minimal_size:
+ for extension in extensions:
+ if iconname + "." + extension in values[1]:
+ closest_filename = os.path.join(dir, iconname + "." + extension)
+ minimal_size = distance
+
+ return closest_filename
+
+def DirectoryMatchesSize(subdir, iconsize, theme):
+ Type = theme.getType(subdir)
+ Size = theme.getSize(subdir)
+ Threshold = theme.getThreshold(subdir)
+ MinSize = theme.getMinSize(subdir)
+ MaxSize = theme.getMaxSize(subdir)
+ if Type == "Fixed":
+ return Size == iconsize
+ elif Type == "Scaleable":
+ return MinSize <= iconsize <= MaxSize
+ elif Type == "Threshold":
+ return Size - Threshold <= iconsize <= Size + Threshold
+
+def DirectorySizeDistance(subdir, iconsize, theme):
+ Type = theme.getType(subdir)
+ Size = theme.getSize(subdir)
+ Threshold = theme.getThreshold(subdir)
+ MinSize = theme.getMinSize(subdir)
+ MaxSize = theme.getMaxSize(subdir)
+ if Type == "Fixed":
+ return abs(Size - iconsize)
+ elif Type == "Scalable":
+ if iconsize < MinSize:
+ return MinSize - iconsize
+ elif iconsize > MaxSize:
+ return MaxSize - iconsize
+ return 0
+ elif Type == "Threshold":
+ if iconsize < Size - Threshold:
+ return MinSize - iconsize
+ elif iconsize > Size + Threshold:
+ return iconsize - MaxSize
+ return 0
--- /dev/null
+"""
+Base Class for DesktopEntry, IconTheme and IconData
+"""
+
+import re, os, stat, codecs
+from Exceptions import *
+import xdg.Locale
+
+class IniFile:
+ defaultGroup = ''
+ fileExtension = ''
+
+ filename = ''
+
+ tainted = False
+
+ def __init__(self, filename=None):
+ self.content = dict()
+ if filename:
+ self.parse(filename)
+
+ def __cmp__(self, other):
+ return cmp(self.content, other.content)
+
+ def parse(self, filename, headers=None):
+ # for performance reasons
+ content = self.content
+
+ if not os.path.isfile(filename):
+ raise ParsingError("File not found", filename)
+
+ try:
+ fd = file(filename, 'r')
+ except IOError, e:
+ if debug:
+ raise e
+ else:
+ return
+
+ # parse file
+ for line in fd:
+ line = line.strip()
+ # empty line
+ if not line:
+ continue
+ # comment
+ elif line[0] == '#':
+ continue
+ # new group
+ elif line[0] == '[':
+ currentGroup = line.lstrip("[").rstrip("]")
+ if debug and self.hasGroup(currentGroup):
+ raise DuplicateGroupError(currentGroup, filename)
+ else:
+ content[currentGroup] = {}
+ # key
+ else:
+ index = line.find("=")
+ key = line[0:index].strip()
+ value = line[index+1:].strip()
+ try:
+ if debug and self.hasKey(key, currentGroup):
+ raise DuplicateKeyError(key, currentGroup, filename)
+ else:
+ content[currentGroup][key] = value
+ except (IndexError, UnboundLocalError):
+ raise ParsingError("Parsing error on key, group missing", filename)
+
+ fd.close()
+
+ self.filename = filename
+ self.tainted = False
+
+ # check header
+ if headers:
+ for header in headers:
+ if content.has_key(header):
+ self.defaultGroup = header
+ break
+ else:
+ raise ParsingError("[%s]-Header missing" % headers[0], filename)
+
+ # start stuff to access the keys
+ def get(self, key, group=None, locale=False, type="string", list=False):
+ # set default group
+ if not group:
+ group = self.defaultGroup
+
+ # return key (with locale)
+ if self.content.has_key(group) and self.content[group].has_key(key):
+ if locale:
+ value = self.content[group][self.__addLocale(key, group)]
+ else:
+ value = self.content[group][key]
+ else:
+ if debug:
+ if not self.content.has_key(group):
+ raise NoGroupError(group, self.filename)
+ elif not self.content[group].has_key(key):
+ raise NoKeyError(key, group, self.filename)
+ else:
+ value = ""
+
+ if list == True:
+ values = self.getList(value)
+ result = []
+ else:
+ values = [value]
+
+ for value in values:
+ if type == "string" and locale == True:
+ value = value.decode("utf-8", "ignore")
+ elif type == "boolean":
+ value = self.__getBoolean(value)
+ elif type == "integer":
+ try:
+ value = int(value)
+ except ValueError:
+ value = 0
+ elif type == "numeric":
+ try:
+ value = float(value)
+ except ValueError:
+ value = 0.0
+ elif type == "regex":
+ value = re.compile(value)
+ elif type == "point":
+ value = value.split(",")
+
+ if list == True:
+ result.append(value)
+ else:
+ result = value
+
+ return result
+ # end stuff to access the keys
+
+ # start subget
+ def getList(self, string):
+ if re.search(r"(?<!\\)\;", string):
+ list = re.split(r"(?<!\\);", string)
+ elif re.search(r"(?<!\\)\|", string):
+ list = re.split(r"(?<!\\)\|", string)
+ elif re.search(r"(?<!\\),", string):
+ list = re.split(r"(?<!\\),", string)
+ else:
+ list = [string]
+ if list[-1] == "":
+ list.pop()
+ return list
+
+ def __getBoolean(self, boolean):
+ if boolean == 1 or boolean == "true" or boolean == "True":
+ return True
+ elif boolean == 0 or boolean == "false" or boolean == "False":
+ return False
+ return False
+ # end subget
+
+ def __addLocale(self, key, group=None):
+ "add locale to key according the current lc_messages"
+ # set default group
+ if not group:
+ group = self.defaultGroup
+
+ for lang in xdg.Locale.langs:
+ if self.content[group].has_key(key+'['+lang+']'):
+ return key+'['+lang+']'
+
+ return key
+
+ # start validation stuff
+ def validate(self, report="All"):
+ "validate ... report = All / Warnings / Errors"
+
+ self.warnings = []
+ self.errors = []
+
+ # get file extension
+ self.fileExtension = os.path.splitext(self.filename)[1]
+
+ # overwrite this for own checkings
+ self.checkExtras()
+
+ # check all keys
+ for group in self.content:
+ self.checkGroup(group)
+ for key in self.content[group]:
+ self.checkKey(key, self.content[group][key], group)
+ # check if value is empty
+ if self.content[group][key] == "":
+ self.warnings.append("Value of Key '%s' is empty" % key)
+
+ # raise Warnings / Errors
+ msg = ""
+
+ if report == "All" or report == "Warnings":
+ for line in self.warnings:
+ msg += "\n- " + line
+
+ if report == "All" or report == "Errors":
+ for line in self.errors:
+ msg += "\n- " + line
+
+ if msg:
+ raise ValidationError(msg, self.filename)
+
+ # check if group header is valid
+ def checkGroup(self, group):
+ pass
+
+ # check if key is valid
+ def checkKey(self, key, value, group):
+ pass
+
+ # check random stuff
+ def checkValue(self, key, value, type="string", list=False):
+ if list == True:
+ values = self.getList(value)
+ else:
+ values = [value]
+
+ for value in values:
+ if type == "string":
+ code = self.checkString(value)
+ elif type == "boolean":
+ code = self.checkBoolean(value)
+ elif type == "numeric":
+ code = self.checkNumber(value)
+ elif type == "integer":
+ code = self.checkInteger(value)
+ elif type == "regex":
+ code = self.checkRegex(value)
+ elif type == "point":
+ code = self.checkPoint(value)
+ if code == 1:
+ self.errors.append("'%s' is not a valid %s" % (value, type))
+ elif code == 2:
+ self.warnings.append("Value of key '%s' is deprecated" % key)
+
+ def checkExtras(self):
+ pass
+
+ def checkBoolean(self, value):
+ # 1 or 0 : deprecated
+ if (value == "1" or value == "0"):
+ return 2
+ # true or false: ok
+ elif not (value == "true" or value == "false"):
+ return 1
+
+ def checkNumber(self, value):
+ # float() ValueError
+ try:
+ float(value)
+ except:
+ return 1
+
+ def checkInteger(self, value):
+ # int() ValueError
+ try:
+ int(value)
+ except:
+ return 1
+
+ def checkPoint(self, value):
+ if not re.match("^[0-9]+,[0-9]+$", value):
+ return 1
+
+ def checkString(self, value):
+ # convert to ascii
+ if not value.decode("utf-8", "ignore").encode("ascii", 'ignore') == value:
+ return 1
+
+ def checkRegex(self, value):
+ try:
+ re.compile(value)
+ except:
+ return 1
+
+ # write support
+ def write(self, filename=None, trusted=False):
+ if not filename and not self.filename:
+ raise ParsingError("File not found", "")
+
+ if filename:
+ self.filename = filename
+ else:
+ filename = self.filename
+
+ if os.path.dirname(filename) and not os.path.isdir(os.path.dirname(filename)):
+ os.makedirs(os.path.dirname(filename))
+
+ fp = codecs.open(filename, 'w')
+
+ # An executable bit signifies that the desktop file is
+ # trusted, but then the file can be executed. Add hashbang to
+ # make sure that the file is opened by something that
+ # understands desktop files.
+ if trusted:
+ fp.write("#!/usr/bin/env xdg-open\n")
+
+ if self.defaultGroup:
+ fp.write("[%s]\n" % self.defaultGroup)
+ for (key, value) in self.content[self.defaultGroup].items():
+ fp.write("%s=%s\n" % (key, value))
+ fp.write("\n")
+ for (name, group) in self.content.items():
+ if name != self.defaultGroup:
+ fp.write("[%s]\n" % name)
+ for (key, value) in group.items():
+ fp.write("%s=%s\n" % (key, value))
+ fp.write("\n")
+
+ # Add executable bits to the file to show that it's trusted.
+ if trusted:
+ oldmode = os.stat(filename).st_mode
+ mode = oldmode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
+ os.chmod(filename, mode)
+
+ self.tainted = False
+
+ def set(self, key, value, group=None, locale=False):
+ # set default group
+ if not group:
+ group = self.defaultGroup
+
+ if locale == True and len(xdg.Locale.langs) > 0:
+ key = key + "[" + xdg.Locale.langs[0] + "]"
+
+ try:
+ if isinstance(value, unicode):
+ self.content[group][key] = value.encode("utf-8", "ignore")
+ else:
+ self.content[group][key] = value
+ except KeyError:
+ raise NoGroupError(group, self.filename)
+
+ self.tainted = (value == self.get(key, group))
+
+ def addGroup(self, group):
+ if self.hasGroup(group):
+ if debug:
+ raise DuplicateGroupError(group, self.filename)
+ else:
+ pass
+ else:
+ self.content[group] = {}
+ self.tainted = True
+
+ def removeGroup(self, group):
+ existed = group in self.content
+ if existed:
+ del self.content[group]
+ self.tainted = True
+ else:
+ if debug:
+ raise NoGroupError(group, self.filename)
+ return existed
+
+ def removeKey(self, key, group=None, locales=True):
+ # set default group
+ if not group:
+ group = self.defaultGroup
+
+ try:
+ if locales:
+ for (name, value) in self.content[group].items():
+ if re.match("^" + key + xdg.Locale.regex + "$", name) and name != key:
+ value = self.content[group][name]
+ del self.content[group][name]
+ value = self.content[group][key]
+ del self.content[group][key]
+ self.tainted = True
+ return value
+ except KeyError, e:
+ if debug:
+ if e == group:
+ raise NoGroupError(group, self.filename)
+ else:
+ raise NoKeyError(key, group, self.filename)
+ else:
+ return ""
+
+ # misc
+ def groups(self):
+ return self.content.keys()
+
+ def hasGroup(self, group):
+ if self.content.has_key(group):
+ return True
+ else:
+ return False
+
+ def hasKey(self, key, group=None):
+ # set default group
+ if not group:
+ group = self.defaultGroup
+
+ if self.content[group].has_key(key):
+ return True
+ else:
+ return False
+
+ def getFileName(self):
+ return self.filename
--- /dev/null
+"""
+Helper Module for Locale settings
+
+This module is based on a ROX module (LGPL):
+
+http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/i18n.py?rev=1.3&view=log
+"""
+
+import os
+from locale import normalize
+
+regex = "(\[([a-zA-Z]+)(_[a-zA-Z]+)?(\.[a-zA-Z\-0-9]+)?(@[a-zA-Z]+)?\])?"
+
+def _expand_lang(locale):
+ locale = normalize(locale)
+ COMPONENT_CODESET = 1 << 0
+ COMPONENT_MODIFIER = 1 << 1
+ COMPONENT_TERRITORY = 1 << 2
+ # split up the locale into its base components
+ mask = 0
+ pos = locale.find('@')
+ if pos >= 0:
+ modifier = locale[pos:]
+ locale = locale[:pos]
+ mask |= COMPONENT_MODIFIER
+ else:
+ modifier = ''
+ pos = locale.find('.')
+ codeset = ''
+ if pos >= 0:
+ locale = locale[:pos]
+ pos = locale.find('_')
+ if pos >= 0:
+ territory = locale[pos:]
+ locale = locale[:pos]
+ mask |= COMPONENT_TERRITORY
+ else:
+ territory = ''
+ language = locale
+ ret = []
+ for i in range(mask+1):
+ if not (i & ~mask): # if all components for this combo exist ...
+ val = language
+ if i & COMPONENT_TERRITORY: val += territory
+ if i & COMPONENT_CODESET: val += codeset
+ if i & COMPONENT_MODIFIER: val += modifier
+ ret.append(val)
+ ret.reverse()
+ return ret
+
+def expand_languages(languages=None):
+ # Get some reasonable defaults for arguments that were not supplied
+ if languages is None:
+ languages = []
+ for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
+ val = os.environ.get(envar)
+ if val:
+ languages = val.split(':')
+ break
+ #if 'C' not in languages:
+ # languages.append('C')
+
+ # now normalize and expand the languages
+ nelangs = []
+ for lang in languages:
+ for nelang in _expand_lang(lang):
+ if nelang not in nelangs:
+ nelangs.append(nelang)
+ return nelangs
+
+def update(language=None):
+ global langs
+ if language:
+ langs = expand_languages([language])
+ else:
+ langs = expand_languages()
+
+langs = []
+update()
--- /dev/null
+"""
+Implementation of the XDG Menu Specification Version 1.0.draft-1
+http://standards.freedesktop.org/menu-spec/
+"""
+
+from __future__ import generators
+import locale, os, xml.dom.minidom
+
+from xdg.BaseDirectory import *
+from xdg.DesktopEntry import *
+from xdg.Exceptions import *
+
+import xdg.Locale
+import xdg.Config
+
+ELEMENT_NODE = xml.dom.Node.ELEMENT_NODE
+
+# for python <= 2.3
+try:
+ reversed = reversed
+except NameError:
+ def reversed(x):
+ return x[::-1]
+
+class Menu:
+ def __init__(self):
+ # Public stuff
+ self.Name = ""
+ self.Directory = None
+ self.Entries = []
+ self.Doc = ""
+ self.Filename = ""
+ self.Depth = 0
+ self.Parent = None
+ self.NotInXml = False
+
+ # Can be one of Deleted/NoDisplay/Hidden/Empty/NotShowIn or True
+ self.Show = True
+ self.Visible = 0
+
+ # Private stuff, only needed for parsing
+ self.AppDirs = []
+ self.DefaultLayout = None
+ self.Deleted = "notset"
+ self.Directories = []
+ self.DirectoryDirs = []
+ self.Layout = None
+ self.MenuEntries = []
+ self.Moves = []
+ self.OnlyUnallocated = "notset"
+ self.Rules = []
+ self.Submenus = []
+
+ def __str__(self):
+ return self.Name
+
+ def __add__(self, other):
+ for dir in other.AppDirs:
+ self.AppDirs.append(dir)
+
+ for dir in other.DirectoryDirs:
+ self.DirectoryDirs.append(dir)
+
+ for directory in other.Directories:
+ self.Directories.append(directory)
+
+ if other.Deleted != "notset":
+ self.Deleted = other.Deleted
+
+ if other.OnlyUnallocated != "notset":
+ self.OnlyUnallocated = other.OnlyUnallocated
+
+ if other.Layout:
+ self.Layout = other.Layout
+
+ if other.DefaultLayout:
+ self.DefaultLayout = other.DefaultLayout
+
+ for rule in other.Rules:
+ self.Rules.append(rule)
+
+ for move in other.Moves:
+ self.Moves.append(move)
+
+ for submenu in other.Submenus:
+ self.addSubmenu(submenu)
+
+ return self
+
+ # FIXME: Performance: cache getName()
+ def __cmp__(self, other):
+ return locale.strcoll(self.getName(), other.getName())
+
+ def __eq__(self, other):
+ if self.Name == str(other):
+ return True
+ else:
+ return False
+
+ """ PUBLIC STUFF """
+ def getEntries(self, hidden=False):
+ for entry in self.Entries:
+ if hidden == True:
+ yield entry
+ elif entry.Show == True:
+ yield entry
+
+ # FIXME: Add searchEntry/seaqrchMenu function
+ # search for name/comment/genericname/desktopfileide
+ # return multiple items
+
+ def getMenuEntry(self, desktopfileid, deep = False):
+ for menuentry in self.MenuEntries:
+ if menuentry.DesktopFileID == desktopfileid:
+ return menuentry
+ if deep == True:
+ for submenu in self.Submenus:
+ submenu.getMenuEntry(desktopfileid, deep)
+
+ def getMenu(self, path):
+ array = path.split("/", 1)
+ for submenu in self.Submenus:
+ if submenu.Name == array[0]:
+ if len(array) > 1:
+ return submenu.getMenu(array[1])
+ else:
+ return submenu
+
+ def getPath(self, org=False, toplevel=False):
+ parent = self
+ names=[]
+ while 1:
+ if org:
+ names.append(parent.Name)
+ else:
+ names.append(parent.getName())
+ if parent.Depth > 0:
+ parent = parent.Parent
+ else:
+ break
+ names.reverse()
+ path = ""
+ if toplevel == False:
+ names.pop(0)
+ for name in names:
+ path = os.path.join(path, name)
+ return path
+
+ def getName(self):
+ try:
+ return self.Directory.DesktopEntry.getName()
+ except AttributeError:
+ return self.Name
+
+ def getGenericName(self):
+ try:
+ return self.Directory.DesktopEntry.getGenericName()
+ except AttributeError:
+ return ""
+
+ def getComment(self):
+ try:
+ return self.Directory.DesktopEntry.getComment()
+ except AttributeError:
+ return ""
+
+ def getIcon(self):
+ try:
+ return self.Directory.DesktopEntry.getIcon()
+ except AttributeError:
+ return ""
+
+ """ PRIVATE STUFF """
+ def addSubmenu(self, newmenu):
+ for submenu in self.Submenus:
+ if submenu == newmenu:
+ submenu += newmenu
+ break
+ else:
+ self.Submenus.append(newmenu)
+ newmenu.Parent = self
+ newmenu.Depth = self.Depth + 1
+
+class Move:
+ "A move operation"
+ def __init__(self, node=None):
+ if node:
+ self.parseNode(node)
+ else:
+ self.Old = ""
+ self.New = ""
+
+ def __cmp__(self, other):
+ return cmp(self.Old, other.Old)
+
+ def parseNode(self, node):
+ for child in node.childNodes:
+ if child.nodeType == ELEMENT_NODE:
+ if child.tagName == "Old":
+ try:
+ self.parseOld(child.childNodes[0].nodeValue)
+ except IndexError:
+ raise ValidationError('Old cannot be empty', '??')
+ elif child.tagName == "New":
+ try:
+ self.parseNew(child.childNodes[0].nodeValue)
+ except IndexError:
+ raise ValidationError('New cannot be empty', '??')
+
+ def parseOld(self, value):
+ self.Old = value
+ def parseNew(self, value):
+ self.New = value
+
+
+class Layout:
+ "Menu Layout class"
+ def __init__(self, node=None):
+ self.order = []
+ if node:
+ self.show_empty = node.getAttribute("show_empty") or "false"
+ self.inline = node.getAttribute("inline") or "false"
+ self.inline_limit = node.getAttribute("inline_limit") or 4
+ self.inline_header = node.getAttribute("inline_header") or "true"
+ self.inline_alias = node.getAttribute("inline_alias") or "false"
+ self.inline_limit = int(self.inline_limit)
+ self.parseNode(node)
+ else:
+ self.show_empty = "false"
+ self.inline = "false"
+ self.inline_limit = 4
+ self.inline_header = "true"
+ self.inline_alias = "false"
+ self.order.append(["Merge", "menus"])
+ self.order.append(["Merge", "files"])
+
+ def parseNode(self, node):
+ for child in node.childNodes:
+ if child.nodeType == ELEMENT_NODE:
+ if child.tagName == "Menuname":
+ try:
+ self.parseMenuname(
+ child.childNodes[0].nodeValue,
+ child.getAttribute("show_empty") or "false",
+ child.getAttribute("inline") or "false",
+ child.getAttribute("inline_limit") or 4,
+ child.getAttribute("inline_header") or "true",
+ child.getAttribute("inline_alias") or "false" )
+ except IndexError:
+ raise ValidationError('Menuname cannot be empty', "")
+ elif child.tagName == "Separator":
+ self.parseSeparator()
+ elif child.tagName == "Filename":
+ try:
+ self.parseFilename(child.childNodes[0].nodeValue)
+ except IndexError:
+ raise ValidationError('Filename cannot be empty', "")
+ elif child.tagName == "Merge":
+ self.parseMerge(child.getAttribute("type") or "all")
+
+ def parseMenuname(self, value, empty="false", inline="false", inline_limit=4, inline_header="true", inline_alias="false"):
+ self.order.append(["Menuname", value, empty, inline, inline_limit, inline_header, inline_alias])
+ self.order[-1][4] = int(self.order[-1][4])
+
+ def parseSeparator(self):
+ self.order.append(["Separator"])
+
+ def parseFilename(self, value):
+ self.order.append(["Filename", value])
+
+ def parseMerge(self, type="all"):
+ self.order.append(["Merge", type])
+
+
+class Rule:
+ "Inlcude / Exclude Rules Class"
+ def __init__(self, type, node=None):
+ # Type is Include or Exclude
+ self.Type = type
+ # Rule is a python expression
+ self.Rule = ""
+
+ # Private attributes, only needed for parsing
+ self.Depth = 0
+ self.Expr = [ "or" ]
+ self.New = True
+
+ # Begin parsing
+ if node:
+ self.parseNode(node)
+ self.compile()
+
+ def __str__(self):
+ return self.Rule
+
+ def compile(self):
+ exec("""
+def do(menuentries, type, run):
+ for menuentry in menuentries:
+ if run == 2 and ( menuentry.MatchedInclude == True \
+ or menuentry.Allocated == True ):
+ continue
+ elif %s:
+ if type == "Include":
+ menuentry.Add = True
+ menuentry.MatchedInclude = True
+ else:
+ menuentry.Add = False
+ return menuentries
+""" % self.Rule) in self.__dict__
+
+ def parseNode(self, node):
+ for child in node.childNodes:
+ if child.nodeType == ELEMENT_NODE:
+ if child.tagName == 'Filename':
+ try:
+ self.parseFilename(child.childNodes[0].nodeValue)
+ except IndexError:
+ raise ValidationError('Filename cannot be empty', "???")
+ elif child.tagName == 'Category':
+ try:
+ self.parseCategory(child.childNodes[0].nodeValue)
+ except IndexError:
+ raise ValidationError('Category cannot be empty', "???")
+ elif child.tagName == 'All':
+ self.parseAll()
+ elif child.tagName == 'And':
+ self.parseAnd(child)
+ elif child.tagName == 'Or':
+ self.parseOr(child)
+ elif child.tagName == 'Not':
+ self.parseNot(child)
+
+ def parseNew(self, set=True):
+ if not self.New:
+ self.Rule += " " + self.Expr[self.Depth] + " "
+ if not set:
+ self.New = True
+ elif set:
+ self.New = False
+
+ def parseFilename(self, value):
+ self.parseNew()
+ self.Rule += "menuentry.DesktopFileID == '%s'" % value.strip().replace("\\", r"\\").replace("'", r"\'")
+
+ def parseCategory(self, value):
+ self.parseNew()
+ self.Rule += "'%s' in menuentry.Categories" % value.strip()
+
+ def parseAll(self):
+ self.parseNew()
+ self.Rule += "True"
+
+ def parseAnd(self, node):
+ self.parseNew(False)
+ self.Rule += "("
+ self.Depth += 1
+ self.Expr.append("and")
+ self.parseNode(node)
+ self.Depth -= 1
+ self.Expr.pop()
+ self.Rule += ")"
+
+ def parseOr(self, node):
+ self.parseNew(False)
+ self.Rule += "("
+ self.Depth += 1
+ self.Expr.append("or")
+ self.parseNode(node)
+ self.Depth -= 1
+ self.Expr.pop()
+ self.Rule += ")"
+
+ def parseNot(self, node):
+ self.parseNew(False)
+ self.Rule += "not ("
+ self.Depth += 1
+ self.Expr.append("or")
+ self.parseNode(node)
+ self.Depth -= 1
+ self.Expr.pop()
+ self.Rule += ")"
+
+
+class MenuEntry:
+ "Wrapper for 'Menu Style' Desktop Entries"
+ def __init__(self, filename, dir="", prefix=""):
+ # Create entry
+ self.DesktopEntry = DesktopEntry(os.path.join(dir,filename))
+ self.setAttributes(filename, dir, prefix)
+
+ # Can be one of Deleted/Hidden/Empty/NotShowIn/NoExec or True
+ self.Show = True
+
+ # Semi-Private
+ self.Original = None
+ self.Parents = []
+
+ # Private Stuff
+ self.Allocated = False
+ self.Add = False
+ self.MatchedInclude = False
+
+ # Caching
+ self.Categories = self.DesktopEntry.getCategories()
+
+ def save(self):
+ if self.DesktopEntry.tainted == True:
+ self.DesktopEntry.write()
+
+ def getDir(self):
+ return self.DesktopEntry.filename.replace(self.Filename, '')
+
+ def getType(self):
+ # Can be one of System/User/Both
+ if xdg.Config.root_mode == False:
+ if self.Original:
+ return "Both"
+ elif xdg_data_dirs[0] in self.DesktopEntry.filename:
+ return "User"
+ else:
+ return "System"
+ else:
+ return "User"
+
+ def setAttributes(self, filename, dir="", prefix=""):
+ self.Filename = filename
+ self.Prefix = prefix
+ self.DesktopFileID = os.path.join(prefix,filename).replace("/", "-")
+
+ if not os.path.isabs(self.DesktopEntry.filename):
+ self.__setFilename()
+
+ def updateAttributes(self):
+ if self.getType() == "System":
+ self.Original = MenuEntry(self.Filename, self.getDir(), self.Prefix)
+ self.__setFilename()
+
+ def __setFilename(self):
+ if xdg.Config.root_mode == False:
+ path = xdg_data_dirs[0]
+ else:
+ path= xdg_data_dirs[1]
+
+ if self.DesktopEntry.getType() == "Application":
+ dir = os.path.join(path, "applications")
+ else:
+ dir = os.path.join(path, "desktop-directories")
+
+ self.DesktopEntry.filename = os.path.join(dir, self.Filename)
+
+ def __cmp__(self, other):
+ return locale.strcoll(self.DesktopEntry.getName(), other.DesktopEntry.getName())
+
+ def __eq__(self, other):
+ if self.DesktopFileID == str(other):
+ return True
+ else:
+ return False
+
+ def __repr__(self):
+ return self.DesktopFileID
+
+
+class Separator:
+ "Just a dummy class for Separators"
+ def __init__(self, parent):
+ self.Parent = parent
+ self.Show = True
+
+
+class Header:
+ "Class for Inline Headers"
+ def __init__(self, name, generic_name, comment):
+ self.Name = name
+ self.GenericName = generic_name
+ self.Comment = comment
+
+ def __str__(self):
+ return self.Name
+
+
+tmp = {}
+
+def __getFileName(filename):
+ dirs = xdg_config_dirs[:]
+ if xdg.Config.root_mode == True:
+ dirs.pop(0)
+
+ for dir in dirs:
+ menuname = os.path.join (dir, "menus" , filename)
+ if os.path.isdir(dir) and os.path.isfile(menuname):
+ return menuname
+
+def parse(filename=None):
+ # conver to absolute path
+ if filename and not os.path.isabs(filename):
+ filename = __getFileName(filename)
+
+ # use default if no filename given
+ if not filename:
+ candidate = os.environ.get('XDG_MENU_PREFIX', '') + "applications.menu"
+ filename = __getFileName(candidate)
+
+ if not filename:
+ raise ParsingError('File not found', "/etc/xdg/menus/%s" % candidate)
+
+ # check if it is a .menu file
+ if not os.path.splitext(filename)[1] == ".menu":
+ raise ParsingError('Not a .menu file', filename)
+
+ # create xml parser
+ try:
+ doc = xml.dom.minidom.parse(filename)
+ except xml.parsers.expat.ExpatError:
+ raise ParsingError('Not a valid .menu file', filename)
+
+ # parse menufile
+ tmp["Root"] = ""
+ tmp["mergeFiles"] = []
+ tmp["DirectoryDirs"] = []
+ tmp["cache"] = MenuEntryCache()
+
+ __parse(doc, filename, tmp["Root"])
+ __parsemove(tmp["Root"])
+ __postparse(tmp["Root"])
+
+ tmp["Root"].Doc = doc
+ tmp["Root"].Filename = filename
+
+ # generate the menu
+ __genmenuNotOnlyAllocated(tmp["Root"])
+ __genmenuOnlyAllocated(tmp["Root"])
+
+ # and finally sort
+ sort(tmp["Root"])
+
+ return tmp["Root"]
+
+
+def __parse(node, filename, parent=None):
+ for child in node.childNodes:
+ if child.nodeType == ELEMENT_NODE:
+ if child.tagName == 'Menu':
+ __parseMenu(child, filename, parent)
+ elif child.tagName == 'AppDir':
+ try:
+ __parseAppDir(child.childNodes[0].nodeValue, filename, parent)
+ except IndexError:
+ raise ValidationError('AppDir cannot be empty', filename)
+ elif child.tagName == 'DefaultAppDirs':
+ __parseDefaultAppDir(filename, parent)
+ elif child.tagName == 'DirectoryDir':
+ try:
+ __parseDirectoryDir(child.childNodes[0].nodeValue, filename, parent)
+ except IndexError:
+ raise ValidationError('DirectoryDir cannot be empty', filename)
+ elif child.tagName == 'DefaultDirectoryDirs':
+ __parseDefaultDirectoryDir(filename, parent)
+ elif child.tagName == 'Name' :
+ try:
+ parent.Name = child.childNodes[0].nodeValue
+ except IndexError:
+ raise ValidationError('Name cannot be empty', filename)
+ elif child.tagName == 'Directory' :
+ try:
+ parent.Directories.append(child.childNodes[0].nodeValue)
+ except IndexError:
+ raise ValidationError('Directory cannot be empty', filename)
+ elif child.tagName == 'OnlyUnallocated':
+ parent.OnlyUnallocated = True
+ elif child.tagName == 'NotOnlyUnallocated':
+ parent.OnlyUnallocated = False
+ elif child.tagName == 'Deleted':
+ parent.Deleted = True
+ elif child.tagName == 'NotDeleted':
+ parent.Deleted = False
+ elif child.tagName == 'Include' or child.tagName == 'Exclude':
+ parent.Rules.append(Rule(child.tagName, child))
+ elif child.tagName == 'MergeFile':
+ try:
+ if child.getAttribute("type") == "parent":
+ __parseMergeFile("applications.menu", child, filename, parent)
+ else:
+ __parseMergeFile(child.childNodes[0].nodeValue, child, filename, parent)
+ except IndexError:
+ raise ValidationError('MergeFile cannot be empty', filename)
+ elif child.tagName == 'MergeDir':
+ try:
+ __parseMergeDir(child.childNodes[0].nodeValue, child, filename, parent)
+ except IndexError:
+ raise ValidationError('MergeDir cannot be empty', filename)
+ elif child.tagName == 'DefaultMergeDirs':
+ __parseDefaultMergeDirs(child, filename, parent)
+ elif child.tagName == 'Move':
+ parent.Moves.append(Move(child))
+ elif child.tagName == 'Layout':
+ if len(child.childNodes) > 1:
+ parent.Layout = Layout(child)
+ elif child.tagName == 'DefaultLayout':
+ if len(child.childNodes) > 1:
+ parent.DefaultLayout = Layout(child)
+ elif child.tagName == 'LegacyDir':
+ try:
+ __parseLegacyDir(child.childNodes[0].nodeValue, child.getAttribute("prefix"), filename, parent)
+ except IndexError:
+ raise ValidationError('LegacyDir cannot be empty', filename)
+ elif child.tagName == 'KDELegacyDirs':
+ __parseKDELegacyDirs(filename, parent)
+
+def __parsemove(menu):
+ for submenu in menu.Submenus:
+ __parsemove(submenu)
+
+ # parse move operations
+ for move in menu.Moves:
+ move_from_menu = menu.getMenu(move.Old)
+ if move_from_menu:
+ move_to_menu = menu.getMenu(move.New)
+
+ menus = move.New.split("/")
+ oldparent = None
+ while len(menus) > 0:
+ if not oldparent:
+ oldparent = menu
+ newmenu = oldparent.getMenu(menus[0])
+ if not newmenu:
+ newmenu = Menu()
+ newmenu.Name = menus[0]
+ if len(menus) > 1:
+ newmenu.NotInXml = True
+ oldparent.addSubmenu(newmenu)
+ oldparent = newmenu
+ menus.pop(0)
+
+ newmenu += move_from_menu
+ move_from_menu.Parent.Submenus.remove(move_from_menu)
+
+def __postparse(menu):
+ # unallocated / deleted
+ if menu.Deleted == "notset":
+ menu.Deleted = False
+ if menu.OnlyUnallocated == "notset":
+ menu.OnlyUnallocated = False
+
+ # Layout Tags
+ if not menu.Layout or not menu.DefaultLayout:
+ if menu.DefaultLayout:
+ menu.Layout = menu.DefaultLayout
+ elif menu.Layout:
+ if menu.Depth > 0:
+ menu.DefaultLayout = menu.Parent.DefaultLayout
+ else:
+ menu.DefaultLayout = Layout()
+ else:
+ if menu.Depth > 0:
+ menu.Layout = menu.Parent.DefaultLayout
+ menu.DefaultLayout = menu.Parent.DefaultLayout
+ else:
+ menu.Layout = Layout()
+ menu.DefaultLayout = Layout()
+
+ # add parent's app/directory dirs
+ if menu.Depth > 0:
+ menu.AppDirs = menu.Parent.AppDirs + menu.AppDirs
+ menu.DirectoryDirs = menu.Parent.DirectoryDirs + menu.DirectoryDirs
+
+ # remove duplicates
+ menu.Directories = __removeDuplicates(menu.Directories)
+ menu.DirectoryDirs = __removeDuplicates(menu.DirectoryDirs)
+ menu.AppDirs = __removeDuplicates(menu.AppDirs)
+
+ # go recursive through all menus
+ for submenu in menu.Submenus:
+ __postparse(submenu)
+
+ # reverse so handling is easier
+ menu.Directories.reverse()
+ menu.DirectoryDirs.reverse()
+ menu.AppDirs.reverse()
+
+ # get the valid .directory file out of the list
+ for directory in menu.Directories:
+ for dir in menu.DirectoryDirs:
+ if os.path.isfile(os.path.join(dir, directory)):
+ menuentry = MenuEntry(directory, dir)
+ if not menu.Directory:
+ menu.Directory = menuentry
+ elif menuentry.getType() == "System":
+ if menu.Directory.getType() == "User":
+ menu.Directory.Original = menuentry
+ if menu.Directory:
+ break
+
+
+# Menu parsing stuff
+def __parseMenu(child, filename, parent):
+ m = Menu()
+ __parse(child, filename, m)
+ if parent:
+ parent.addSubmenu(m)
+ else:
+ tmp["Root"] = m
+
+# helper function
+def __check(value, filename, type):
+ path = os.path.dirname(filename)
+
+ if not os.path.isabs(value):
+ value = os.path.join(path, value)
+
+ value = os.path.abspath(value)
+
+ if type == "dir" and os.path.exists(value) and os.path.isdir(value):
+ return value
+ elif type == "file" and os.path.exists(value) and os.path.isfile(value):
+ return value
+ else:
+ return False
+
+# App/Directory Dir Stuff
+def __parseAppDir(value, filename, parent):
+ value = __check(value, filename, "dir")
+ if value:
+ parent.AppDirs.append(value)
+
+def __parseDefaultAppDir(filename, parent):
+ for dir in reversed(xdg_data_dirs):
+ __parseAppDir(os.path.join(dir, "applications"), filename, parent)
+
+def __parseDirectoryDir(value, filename, parent):
+ value = __check(value, filename, "dir")
+ if value:
+ parent.DirectoryDirs.append(value)
+
+def __parseDefaultDirectoryDir(filename, parent):
+ for dir in reversed(xdg_data_dirs):
+ __parseDirectoryDir(os.path.join(dir, "desktop-directories"), filename, parent)
+
+# Merge Stuff
+def __parseMergeFile(value, child, filename, parent):
+ if child.getAttribute("type") == "parent":
+ for dir in xdg_config_dirs:
+ rel_file = filename.replace(dir, "").strip("/")
+ if rel_file != filename:
+ for p in xdg_config_dirs:
+ if dir == p:
+ continue
+ if os.path.isfile(os.path.join(p,rel_file)):
+ __mergeFile(os.path.join(p,rel_file),child,parent)
+ break
+ else:
+ value = __check(value, filename, "file")
+ if value:
+ __mergeFile(value, child, parent)
+
+def __parseMergeDir(value, child, filename, parent):
+ value = __check(value, filename, "dir")
+ if value:
+ for item in os.listdir(value):
+ try:
+ if os.path.splitext(item)[1] == ".menu":
+ __mergeFile(os.path.join(value, item), child, parent)
+ except UnicodeDecodeError:
+ continue
+
+def __parseDefaultMergeDirs(child, filename, parent):
+ basename = os.path.splitext(os.path.basename(filename))[0]
+ for dir in reversed(xdg_config_dirs):
+ __parseMergeDir(os.path.join(dir, "menus", basename + "-merged"), child, filename, parent)
+
+def __mergeFile(filename, child, parent):
+ # check for infinite loops
+ if filename in tmp["mergeFiles"]:
+ if debug:
+ raise ParsingError('Infinite MergeFile loop detected', filename)
+ else:
+ return
+
+ tmp["mergeFiles"].append(filename)
+
+ # load file
+ try:
+ doc = xml.dom.minidom.parse(filename)
+ except IOError:
+ if debug:
+ raise ParsingError('File not found', filename)
+ else:
+ return
+ except xml.parsers.expat.ExpatError:
+ if debug:
+ raise ParsingError('Not a valid .menu file', filename)
+ else:
+ return
+
+ # append file
+ for child in doc.childNodes:
+ if child.nodeType == ELEMENT_NODE:
+ __parse(child,filename,parent)
+ break
+
+# Legacy Dir Stuff
+def __parseLegacyDir(dir, prefix, filename, parent):
+ m = __mergeLegacyDir(dir,prefix,filename,parent)
+ if m:
+ parent += m
+
+def __mergeLegacyDir(dir, prefix, filename, parent):
+ dir = __check(dir,filename,"dir")
+ if dir and dir not in tmp["DirectoryDirs"]:
+ tmp["DirectoryDirs"].append(dir)
+
+ m = Menu()
+ m.AppDirs.append(dir)
+ m.DirectoryDirs.append(dir)
+ m.Name = os.path.basename(dir)
+ m.NotInXml = True
+
+ for item in os.listdir(dir):
+ try:
+ if item == ".directory":
+ m.Directories.append(item)
+ elif os.path.isdir(os.path.join(dir,item)):
+ m.addSubmenu(__mergeLegacyDir(os.path.join(dir,item), prefix, filename, parent))
+ except UnicodeDecodeError:
+ continue
+
+ tmp["cache"].addMenuEntries([dir],prefix, True)
+ menuentries = tmp["cache"].getMenuEntries([dir], False)
+
+ for menuentry in menuentries:
+ categories = menuentry.Categories
+ if len(categories) == 0:
+ r = Rule("Include")
+ r.parseFilename(menuentry.DesktopFileID)
+ r.compile()
+ m.Rules.append(r)
+ if not dir in parent.AppDirs:
+ categories.append("Legacy")
+ menuentry.Categories = categories
+
+ return m
+
+def __parseKDELegacyDirs(filename, parent):
+ f=os.popen3("kde-config --path apps")
+ output = f[1].readlines()
+ try:
+ for dir in output[0].split(":"):
+ __parseLegacyDir(dir,"kde", filename, parent)
+ except IndexError:
+ pass
+
+# remove duplicate entries from a list
+def __removeDuplicates(list):
+ set = {}
+ list.reverse()
+ list = [set.setdefault(e,e) for e in list if e not in set]
+ list.reverse()
+ return list
+
+# Finally generate the menu
+def __genmenuNotOnlyAllocated(menu):
+ for submenu in menu.Submenus:
+ __genmenuNotOnlyAllocated(submenu)
+
+ if menu.OnlyUnallocated == False:
+ tmp["cache"].addMenuEntries(menu.AppDirs)
+ menuentries = []
+ for rule in menu.Rules:
+ menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 1)
+ for menuentry in menuentries:
+ if menuentry.Add == True:
+ menuentry.Parents.append(menu)
+ menuentry.Add = False
+ menuentry.Allocated = True
+ menu.MenuEntries.append(menuentry)
+
+def __genmenuOnlyAllocated(menu):
+ for submenu in menu.Submenus:
+ __genmenuOnlyAllocated(submenu)
+
+ if menu.OnlyUnallocated == True:
+ tmp["cache"].addMenuEntries(menu.AppDirs)
+ menuentries = []
+ for rule in menu.Rules:
+ menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 2)
+ for menuentry in menuentries:
+ if menuentry.Add == True:
+ menuentry.Parents.append(menu)
+ # menuentry.Add = False
+ # menuentry.Allocated = True
+ menu.MenuEntries.append(menuentry)
+
+# And sorting ...
+def sort(menu):
+ menu.Entries = []
+ menu.Visible = 0
+
+ for submenu in menu.Submenus:
+ sort(submenu)
+
+ tmp_s = []
+ tmp_e = []
+
+ for order in menu.Layout.order:
+ if order[0] == "Filename":
+ tmp_e.append(order[1])
+ elif order[0] == "Menuname":
+ tmp_s.append(order[1])
+
+ for order in menu.Layout.order:
+ if order[0] == "Separator":
+ separator = Separator(menu)
+ if len(menu.Entries) > 0 and isinstance(menu.Entries[-1], Separator):
+ separator.Show = False
+ menu.Entries.append(separator)
+ elif order[0] == "Filename":
+ menuentry = menu.getMenuEntry(order[1])
+ if menuentry:
+ menu.Entries.append(menuentry)
+ elif order[0] == "Menuname":
+ submenu = menu.getMenu(order[1])
+ if submenu:
+ __parse_inline(submenu, menu)
+ elif order[0] == "Merge":
+ if order[1] == "files" or order[1] == "all":
+ menu.MenuEntries.sort()
+ for menuentry in menu.MenuEntries:
+ if menuentry not in tmp_e:
+ menu.Entries.append(menuentry)
+ elif order[1] == "menus" or order[1] == "all":
+ menu.Submenus.sort()
+ for submenu in menu.Submenus:
+ if submenu.Name not in tmp_s:
+ __parse_inline(submenu, menu)
+
+ # getHidden / NoDisplay / OnlyShowIn / NotOnlyShowIn / Deleted / NoExec
+ for entry in menu.Entries:
+ entry.Show = True
+ menu.Visible += 1
+ if isinstance(entry, Menu):
+ if entry.Deleted == True:
+ entry.Show = "Deleted"
+ menu.Visible -= 1
+ elif isinstance(entry.Directory, MenuEntry):
+ if entry.Directory.DesktopEntry.getNoDisplay() == True:
+ entry.Show = "NoDisplay"
+ menu.Visible -= 1
+ elif entry.Directory.DesktopEntry.getHidden() == True:
+ entry.Show = "Hidden"
+ menu.Visible -= 1
+ elif isinstance(entry, MenuEntry):
+ if entry.DesktopEntry.getNoDisplay() == True:
+ entry.Show = "NoDisplay"
+ menu.Visible -= 1
+ elif entry.DesktopEntry.getHidden() == True:
+ entry.Show = "Hidden"
+ menu.Visible -= 1
+ elif entry.DesktopEntry.getTryExec() and not __try_exec(entry.DesktopEntry.getTryExec()):
+ entry.Show = "NoExec"
+ menu.Visible -= 1
+ elif xdg.Config.windowmanager:
+ if ( entry.DesktopEntry.getOnlyShowIn() != [] and xdg.Config.windowmanager not in entry.DesktopEntry.getOnlyShowIn() ) \
+ or xdg.Config.windowmanager in entry.DesktopEntry.getNotShowIn():
+ entry.Show = "NotShowIn"
+ menu.Visible -= 1
+ elif isinstance(entry,Separator):
+ menu.Visible -= 1
+
+ # remove separators at the beginning and at the end
+ if len(menu.Entries) > 0:
+ if isinstance(menu.Entries[0], Separator):
+ menu.Entries[0].Show = False
+ if len(menu.Entries) > 1:
+ if isinstance(menu.Entries[-1], Separator):
+ menu.Entries[-1].Show = False
+
+ # show_empty tag
+ for entry in menu.Entries:
+ if isinstance(entry,Menu) and entry.Layout.show_empty == "false" and entry.Visible == 0:
+ entry.Show = "Empty"
+ menu.Visible -= 1
+ if entry.NotInXml == True:
+ menu.Entries.remove(entry)
+
+def __try_exec(executable):
+ paths = os.environ['PATH'].split(os.pathsep)
+ if not os.path.isfile(executable):
+ for p in paths:
+ f = os.path.join(p, executable)
+ if os.path.isfile(f):
+ if os.access(f, os.X_OK):
+ return True
+ else:
+ if os.access(executable, os.X_OK):
+ return True
+ return False
+
+# inline tags
+def __parse_inline(submenu, menu):
+ if submenu.Layout.inline == "true":
+ if len(submenu.Entries) == 1 and submenu.Layout.inline_alias == "true":
+ menuentry = submenu.Entries[0]
+ menuentry.DesktopEntry.set("Name", submenu.getName(), locale = True)
+ menuentry.DesktopEntry.set("GenericName", submenu.getGenericName(), locale = True)
+ menuentry.DesktopEntry.set("Comment", submenu.getComment(), locale = True)
+ menu.Entries.append(menuentry)
+ elif len(submenu.Entries) <= submenu.Layout.inline_limit or submenu.Layout.inline_limit == 0:
+ if submenu.Layout.inline_header == "true":
+ header = Header(submenu.getName(), submenu.getGenericName(), submenu.getComment())
+ menu.Entries.append(header)
+ for entry in submenu.Entries:
+ menu.Entries.append(entry)
+ else:
+ menu.Entries.append(submenu)
+ else:
+ menu.Entries.append(submenu)
+
+class MenuEntryCache:
+ "Class to cache Desktop Entries"
+ def __init__(self):
+ self.cacheEntries = {}
+ self.cacheEntries['legacy'] = []
+ self.cache = {}
+
+ def addMenuEntries(self, dirs, prefix="", legacy=False):
+ for dir in dirs:
+ if not self.cacheEntries.has_key(dir):
+ self.cacheEntries[dir] = []
+ self.__addFiles(dir, "", prefix, legacy)
+
+ def __addFiles(self, dir, subdir, prefix, legacy):
+ for item in os.listdir(os.path.join(dir,subdir)):
+ if os.path.splitext(item)[1] == ".desktop":
+ try:
+ menuentry = MenuEntry(os.path.join(subdir,item), dir, prefix)
+ except ParsingError:
+ continue
+
+ self.cacheEntries[dir].append(menuentry)
+ if legacy == True:
+ self.cacheEntries['legacy'].append(menuentry)
+ elif os.path.isdir(os.path.join(dir,subdir,item)) and legacy == False:
+ self.__addFiles(dir, os.path.join(subdir,item), prefix, legacy)
+
+ def getMenuEntries(self, dirs, legacy=True):
+ list = []
+ ids = []
+ # handle legacy items
+ appdirs = dirs[:]
+ if legacy == True:
+ appdirs.append("legacy")
+ # cache the results again
+ key = "".join(appdirs)
+ try:
+ return self.cache[key]
+ except KeyError:
+ pass
+ for dir in appdirs:
+ for menuentry in self.cacheEntries[dir]:
+ try:
+ if menuentry.DesktopFileID not in ids:
+ ids.append(menuentry.DesktopFileID)
+ list.append(menuentry)
+ elif menuentry.getType() == "System":
+ # FIXME: This is only 99% correct, but still...
+ i = list.index(menuentry)
+ e = list[i]
+ if e.getType() == "User":
+ e.Original = menuentry
+ except UnicodeDecodeError:
+ continue
+ self.cache[key] = list
+ return list
--- /dev/null
+""" CLass to edit XDG Menus """
+
+from xdg.Menu import *
+from xdg.BaseDirectory import *
+from xdg.Exceptions import *
+from xdg.DesktopEntry import *
+from xdg.Config import *
+
+import xml.dom.minidom
+import os
+import re
+
+# XML-Cleanups: Move / Exclude
+# FIXME: proper reverte/delete
+# FIXME: pass AppDirs/DirectoryDirs around in the edit/move functions
+# FIXME: catch Exceptions
+# FIXME: copy functions
+# FIXME: More Layout stuff
+# FIXME: unod/redo function / remove menu...
+# FIXME: Advanced MenuEditing Stuff: LegacyDir/MergeFile
+# Complex Rules/Deleted/OnlyAllocated/AppDirs/DirectoryDirs
+
+class MenuEditor:
+ def __init__(self, menu=None, filename=None, root=False):
+ self.menu = None
+ self.filename = None
+ self.doc = None
+ self.parse(menu, filename, root)
+
+ # fix for creating two menus with the same name on the fly
+ self.filenames = []
+
+ def parse(self, menu=None, filename=None, root=False):
+ if root == True:
+ setRootMode(True)
+
+ if isinstance(menu, Menu):
+ self.menu = menu
+ elif menu:
+ self.menu = parse(menu)
+ else:
+ self.menu = parse()
+
+ if root == True:
+ self.filename = self.menu.Filename
+ elif filename:
+ self.filename = filename
+ else:
+ self.filename = os.path.join(xdg_config_dirs[0], "menus", os.path.split(self.menu.Filename)[1])
+
+ try:
+ self.doc = xml.dom.minidom.parse(self.filename)
+ except IOError:
+ self.doc = xml.dom.minidom.parseString('<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://standards.freedesktop.org/menu-spec/menu-1.0.dtd"><Menu><Name>Applications</Name><MergeFile type="parent">'+self.menu.Filename+'</MergeFile></Menu>')
+ except xml.parsers.expat.ExpatError:
+ raise ParsingError('Not a valid .menu file', self.filename)
+
+ self.__remove_whilespace_nodes(self.doc)
+
+ def save(self):
+ self.__saveEntries(self.menu)
+ self.__saveMenu()
+
+ def createMenuEntry(self, parent, name, command=None, genericname=None, comment=None, icon=None, terminal=None, after=None, before=None):
+ menuentry = MenuEntry(self.__getFileName(name, ".desktop"))
+ menuentry = self.editMenuEntry(menuentry, name, genericname, comment, command, icon, terminal)
+
+ self.__addEntry(parent, menuentry, after, before)
+
+ sort(self.menu)
+
+ return menuentry
+
+ def createMenu(self, parent, name, genericname=None, comment=None, icon=None, after=None, before=None):
+ menu = Menu()
+
+ menu.Parent = parent
+ menu.Depth = parent.Depth + 1
+ menu.Layout = parent.DefaultLayout
+ menu.DefaultLayout = parent.DefaultLayout
+
+ menu = self.editMenu(menu, name, genericname, comment, icon)
+
+ self.__addEntry(parent, menu, after, before)
+
+ sort(self.menu)
+
+ return menu
+
+ def createSeparator(self, parent, after=None, before=None):
+ separator = Separator(parent)
+
+ self.__addEntry(parent, separator, after, before)
+
+ sort(self.menu)
+
+ return separator
+
+ def moveMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
+ self.__deleteEntry(oldparent, menuentry, after, before)
+ self.__addEntry(newparent, menuentry, after, before)
+
+ sort(self.menu)
+
+ return menuentry
+
+ def moveMenu(self, menu, oldparent, newparent, after=None, before=None):
+ self.__deleteEntry(oldparent, menu, after, before)
+ self.__addEntry(newparent, menu, after, before)
+
+ root_menu = self.__getXmlMenu(self.menu.Name)
+ if oldparent.getPath(True) != newparent.getPath(True):
+ self.__addXmlMove(root_menu, os.path.join(oldparent.getPath(True), menu.Name), os.path.join(newparent.getPath(True), menu.Name))
+
+ sort(self.menu)
+
+ return menu
+
+ def moveSeparator(self, separator, parent, after=None, before=None):
+ self.__deleteEntry(parent, separator, after, before)
+ self.__addEntry(parent, separator, after, before)
+
+ sort(self.menu)
+
+ return separator
+
+ def copyMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
+ self.__addEntry(newparent, menuentry, after, before)
+
+ sort(self.menu)
+
+ return menuentry
+
+ def editMenuEntry(self, menuentry, name=None, genericname=None, comment=None, command=None, icon=None, terminal=None, nodisplay=None, hidden=None):
+ deskentry = menuentry.DesktopEntry
+
+ if name:
+ if not deskentry.hasKey("Name"):
+ deskentry.set("Name", name)
+ deskentry.set("Name", name, locale = True)
+ if comment:
+ if not deskentry.hasKey("Comment"):
+ deskentry.set("Comment", comment)
+ deskentry.set("Comment", comment, locale = True)
+ if genericname:
+ if not deskentry.hasKey("GnericNe"):
+ deskentry.set("GenericName", genericname)
+ deskentry.set("GenericName", genericname, locale = True)
+ if command:
+ deskentry.set("Exec", command)
+ if icon:
+ deskentry.set("Icon", icon)
+
+ if terminal == True:
+ deskentry.set("Terminal", "true")
+ elif terminal == False:
+ deskentry.set("Terminal", "false")
+
+ if nodisplay == True:
+ deskentry.set("NoDisplay", "true")
+ elif nodisplay == False:
+ deskentry.set("NoDisplay", "false")
+
+ if hidden == True:
+ deskentry.set("Hidden", "true")
+ elif hidden == False:
+ deskentry.set("Hidden", "false")
+
+ menuentry.updateAttributes()
+
+ if len(menuentry.Parents) > 0:
+ sort(self.menu)
+
+ return menuentry
+
+ def editMenu(self, menu, name=None, genericname=None, comment=None, icon=None, nodisplay=None, hidden=None):
+ # Hack for legacy dirs
+ if isinstance(menu.Directory, MenuEntry) and menu.Directory.Filename == ".directory":
+ xml_menu = self.__getXmlMenu(menu.getPath(True, True))
+ self.__addXmlTextElement(xml_menu, 'Directory', menu.Name + ".directory")
+ menu.Directory.setAttributes(menu.Name + ".directory")
+ # Hack for New Entries
+ elif not isinstance(menu.Directory, MenuEntry):
+ if not name:
+ name = menu.Name
+ filename = self.__getFileName(name, ".directory").replace("/", "")
+ if not menu.Name:
+ menu.Name = filename.replace(".directory", "")
+ xml_menu = self.__getXmlMenu(menu.getPath(True, True))
+ self.__addXmlTextElement(xml_menu, 'Directory', filename)
+ menu.Directory = MenuEntry(filename)
+
+ deskentry = menu.Directory.DesktopEntry
+
+ if name:
+ if not deskentry.hasKey("Name"):
+ deskentry.set("Name", name)
+ deskentry.set("Name", name, locale = True)
+ if genericname:
+ if not deskentry.hasKey("GenericName"):
+ deskentry.set("GenericName", genericname)
+ deskentry.set("GenericName", genericname, locale = True)
+ if comment:
+ if not deskentry.hasKey("Comment"):
+ deskentry.set("Comment", comment)
+ deskentry.set("Comment", comment, locale = True)
+ if icon:
+ deskentry.set("Icon", icon)
+
+ if nodisplay == True:
+ deskentry.set("NoDisplay", "true")
+ elif nodisplay == False:
+ deskentry.set("NoDisplay", "false")
+
+ if hidden == True:
+ deskentry.set("Hidden", "true")
+ elif hidden == False:
+ deskentry.set("Hidden", "false")
+
+ menu.Directory.updateAttributes()
+
+ if isinstance(menu.Parent, Menu):
+ sort(self.menu)
+
+ return menu
+
+ def hideMenuEntry(self, menuentry):
+ self.editMenuEntry(menuentry, nodisplay = True)
+
+ def unhideMenuEntry(self, menuentry):
+ self.editMenuEntry(menuentry, nodisplay = False, hidden = False)
+
+ def hideMenu(self, menu):
+ self.editMenu(menu, nodisplay = True)
+
+ def unhideMenu(self, menu):
+ self.editMenu(menu, nodisplay = False, hidden = False)
+ xml_menu = self.__getXmlMenu(menu.getPath(True,True), False)
+ for node in self.__getXmlNodesByName(["Deleted", "NotDeleted"], xml_menu):
+ node.parentNode.removeChild(node)
+
+ def deleteMenuEntry(self, menuentry):
+ if self.getAction(menuentry) == "delete":
+ self.__deleteFile(menuentry.DesktopEntry.filename)
+ for parent in menuentry.Parents:
+ self.__deleteEntry(parent, menuentry)
+ sort(self.menu)
+ return menuentry
+
+ def revertMenuEntry(self, menuentry):
+ if self.getAction(menuentry) == "revert":
+ self.__deleteFile(menuentry.DesktopEntry.filename)
+ menuentry.Original.Parents = []
+ for parent in menuentry.Parents:
+ index = parent.Entries.index(menuentry)
+ parent.Entries[index] = menuentry.Original
+ index = parent.MenuEntries.index(menuentry)
+ parent.MenuEntries[index] = menuentry.Original
+ menuentry.Original.Parents.append(parent)
+ sort(self.menu)
+ return menuentry
+
+ def deleteMenu(self, menu):
+ if self.getAction(menu) == "delete":
+ self.__deleteFile(menu.Directory.DesktopEntry.filename)
+ self.__deleteEntry(menu.Parent, menu)
+ xml_menu = self.__getXmlMenu(menu.getPath(True, True))
+ xml_menu.parentNode.removeChild(xml_menu)
+ sort(self.menu)
+ return menu
+
+ def revertMenu(self, menu):
+ if self.getAction(menu) == "revert":
+ self.__deleteFile(menu.Directory.DesktopEntry.filename)
+ menu.Directory = menu.Directory.Original
+ sort(self.menu)
+ return menu
+
+ def deleteSeparator(self, separator):
+ self.__deleteEntry(separator.Parent, separator, after=True)
+
+ sort(self.menu)
+
+ return separator
+
+ """ Private Stuff """
+ def getAction(self, entry):
+ if isinstance(entry, Menu):
+ if not isinstance(entry.Directory, MenuEntry):
+ return "none"
+ elif entry.Directory.getType() == "Both":
+ return "revert"
+ elif entry.Directory.getType() == "User" \
+ and (len(entry.Submenus) + len(entry.MenuEntries)) == 0:
+ return "delete"
+
+ elif isinstance(entry, MenuEntry):
+ if entry.getType() == "Both":
+ return "revert"
+ elif entry.getType() == "User":
+ return "delete"
+ else:
+ return "none"
+
+ return "none"
+
+ def __saveEntries(self, menu):
+ if not menu:
+ menu = self.menu
+ if isinstance(menu.Directory, MenuEntry):
+ menu.Directory.save()
+ for entry in menu.getEntries(hidden=True):
+ if isinstance(entry, MenuEntry):
+ entry.save()
+ elif isinstance(entry, Menu):
+ self.__saveEntries(entry)
+
+ def __saveMenu(self):
+ if not os.path.isdir(os.path.dirname(self.filename)):
+ os.makedirs(os.path.dirname(self.filename))
+ fd = open(self.filename, 'w')
+ fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*</", "\\1</", self.doc.toprettyxml().replace('<?xml version="1.0" ?>\n', '')))
+ fd.close()
+
+ def __getFileName(self, name, extension):
+ postfix = 0
+ while 1:
+ if postfix == 0:
+ filename = name + extension
+ else:
+ filename = name + "-" + str(postfix) + extension
+ if extension == ".desktop":
+ dir = "applications"
+ elif extension == ".directory":
+ dir = "desktop-directories"
+ if not filename in self.filenames and not \
+ os.path.isfile(os.path.join(xdg_data_dirs[0], dir, filename)):
+ self.filenames.append(filename)
+ break
+ else:
+ postfix += 1
+
+ return filename
+
+ def __getXmlMenu(self, path, create=True, element=None):
+ if not element:
+ element = self.doc
+
+ if "/" in path:
+ (name, path) = path.split("/", 1)
+ else:
+ name = path
+ path = ""
+
+ found = None
+ for node in self.__getXmlNodesByName("Menu", element):
+ for child in self.__getXmlNodesByName("Name", node):
+ if child.childNodes[0].nodeValue == name:
+ if path:
+ found = self.__getXmlMenu(path, create, node)
+ else:
+ found = node
+ break
+ if found:
+ break
+ if not found and create == True:
+ node = self.__addXmlMenuElement(element, name)
+ if path:
+ found = self.__getXmlMenu(path, create, node)
+ else:
+ found = node
+
+ return found
+
+ def __addXmlMenuElement(self, element, name):
+ node = self.doc.createElement('Menu')
+ self.__addXmlTextElement(node, 'Name', name)
+ return element.appendChild(node)
+
+ def __addXmlTextElement(self, element, name, text):
+ node = self.doc.createElement(name)
+ text = self.doc.createTextNode(text)
+ node.appendChild(text)
+ return element.appendChild(node)
+
+ def __addXmlFilename(self, element, filename, type = "Include"):
+ # remove old filenames
+ for node in self.__getXmlNodesByName(["Include", "Exclude"], element):
+ if node.childNodes[0].nodeName == "Filename" and node.childNodes[0].childNodes[0].nodeValue == filename:
+ element.removeChild(node)
+
+ # add new filename
+ node = self.doc.createElement(type)
+ node.appendChild(self.__addXmlTextElement(node, 'Filename', filename))
+ return element.appendChild(node)
+
+ def __addXmlMove(self, element, old, new):
+ node = self.doc.createElement("Move")
+ node.appendChild(self.__addXmlTextElement(node, 'Old', old))
+ node.appendChild(self.__addXmlTextElement(node, 'New', new))
+ return element.appendChild(node)
+
+ def __addXmlLayout(self, element, layout):
+ # remove old layout
+ for node in self.__getXmlNodesByName("Layout", element):
+ element.removeChild(node)
+
+ # add new layout
+ node = self.doc.createElement("Layout")
+ for order in layout.order:
+ if order[0] == "Separator":
+ child = self.doc.createElement("Separator")
+ node.appendChild(child)
+ elif order[0] == "Filename":
+ child = self.__addXmlTextElement(node, "Filename", order[1])
+ elif order[0] == "Menuname":
+ child = self.__addXmlTextElement(node, "Menuname", order[1])
+ elif order[0] == "Merge":
+ child = self.doc.createElement("Merge")
+ child.setAttribute("type", order[1])
+ node.appendChild(child)
+ return element.appendChild(node)
+
+ def __getXmlNodesByName(self, name, element):
+ for child in element.childNodes:
+ if child.nodeType == xml.dom.Node.ELEMENT_NODE and child.nodeName in name:
+ yield child
+
+ def __addLayout(self, parent):
+ layout = Layout()
+ layout.order = []
+ layout.show_empty = parent.Layout.show_empty
+ layout.inline = parent.Layout.inline
+ layout.inline_header = parent.Layout.inline_header
+ layout.inline_alias = parent.Layout.inline_alias
+ layout.inline_limit = parent.Layout.inline_limit
+
+ layout.order.append(["Merge", "menus"])
+ for entry in parent.Entries:
+ if isinstance(entry, Menu):
+ layout.parseMenuname(entry.Name)
+ elif isinstance(entry, MenuEntry):
+ layout.parseFilename(entry.DesktopFileID)
+ elif isinstance(entry, Separator):
+ layout.parseSeparator()
+ layout.order.append(["Merge", "files"])
+
+ parent.Layout = layout
+
+ return layout
+
+ def __addEntry(self, parent, entry, after=None, before=None):
+ if after or before:
+ if after:
+ index = parent.Entries.index(after) + 1
+ elif before:
+ index = parent.Entries.index(before)
+ parent.Entries.insert(index, entry)
+ else:
+ parent.Entries.append(entry)
+
+ xml_parent = self.__getXmlMenu(parent.getPath(True, True))
+
+ if isinstance(entry, MenuEntry):
+ parent.MenuEntries.append(entry)
+ entry.Parents.append(parent)
+ self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Include")
+ elif isinstance(entry, Menu):
+ parent.addSubmenu(entry)
+
+ if after or before:
+ self.__addLayout(parent)
+ self.__addXmlLayout(xml_parent, parent.Layout)
+
+ def __deleteEntry(self, parent, entry, after=None, before=None):
+ parent.Entries.remove(entry)
+
+ xml_parent = self.__getXmlMenu(parent.getPath(True, True))
+
+ if isinstance(entry, MenuEntry):
+ entry.Parents.remove(parent)
+ parent.MenuEntries.remove(entry)
+ self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Exclude")
+ elif isinstance(entry, Menu):
+ parent.Submenus.remove(entry)
+
+ if after or before:
+ self.__addLayout(parent)
+ self.__addXmlLayout(xml_parent, parent.Layout)
+
+ def __deleteFile(self, filename):
+ try:
+ os.remove(filename)
+ except OSError:
+ pass
+ try:
+ self.filenames.remove(filename)
+ except ValueError:
+ pass
+
+ def __remove_whilespace_nodes(self, node):
+ remove_list = []
+ for child in node.childNodes:
+ if child.nodeType == xml.dom.minidom.Node.TEXT_NODE:
+ child.data = child.data.strip()
+ if not child.data.strip():
+ remove_list.append(child)
+ elif child.hasChildNodes():
+ self.__remove_whilespace_nodes(child)
+ for node in remove_list:
+ node.parentNode.removeChild(node)
--- /dev/null
+"""
+This module is based on a rox module (LGPL):
+
+http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/mime.py?rev=1.21&view=log
+
+This module provides access to the shared MIME database.
+
+types is a dictionary of all known MIME types, indexed by the type name, e.g.
+types['application/x-python']
+
+Applications can install information about MIME types by storing an
+XML file as <MIME>/packages/<application>.xml and running the
+update-mime-database command, which is provided by the freedesktop.org
+shared mime database package.
+
+See http://www.freedesktop.org/standards/shared-mime-info-spec/ for
+information about the format of these files.
+
+(based on version 0.13)
+"""
+
+import os
+import stat
+import fnmatch
+
+import xdg.BaseDirectory
+import xdg.Locale
+
+from xml.dom import Node, minidom, XML_NAMESPACE
+
+FREE_NS = 'http://www.freedesktop.org/standards/shared-mime-info'
+
+types = {} # Maps MIME names to type objects
+
+exts = None # Maps extensions to types
+globs = None # List of (glob, type) pairs
+literals = None # Maps liternal names to types
+magic = None
+
+def _get_node_data(node):
+ """Get text of XML node"""
+ return ''.join([n.nodeValue for n in node.childNodes]).strip()
+
+def lookup(media, subtype = None):
+ "Get the MIMEtype object for this type, creating a new one if needed."
+ if subtype is None and '/' in media:
+ media, subtype = media.split('/', 1)
+ if (media, subtype) not in types:
+ types[(media, subtype)] = MIMEtype(media, subtype)
+ return types[(media, subtype)]
+
+class MIMEtype:
+ """Type holding data about a MIME type"""
+ def __init__(self, media, subtype):
+ "Don't use this constructor directly; use mime.lookup() instead."
+ assert media and '/' not in media
+ assert subtype and '/' not in subtype
+ assert (media, subtype) not in types
+
+ self.media = media
+ self.subtype = subtype
+ self._comment = None
+
+ def _load(self):
+ "Loads comment for current language. Use get_comment() instead."
+ resource = os.path.join('mime', self.media, self.subtype + '.xml')
+ for path in xdg.BaseDirectory.load_data_paths(resource):
+ doc = minidom.parse(path)
+ if doc is None:
+ continue
+ for comment in doc.documentElement.getElementsByTagNameNS(FREE_NS, 'comment'):
+ lang = comment.getAttributeNS(XML_NAMESPACE, 'lang') or 'en'
+ goodness = 1 + (lang in xdg.Locale.langs)
+ if goodness > self._comment[0]:
+ self._comment = (goodness, _get_node_data(comment))
+ if goodness == 2: return
+
+ # FIXME: add get_icon method
+ def get_comment(self):
+ """Returns comment for current language, loading it if needed."""
+ # Should we ever reload?
+ if self._comment is None:
+ self._comment = (0, str(self))
+ self._load()
+ return self._comment[1]
+
+ def __str__(self):
+ return self.media + '/' + self.subtype
+
+ def __repr__(self):
+ return '[%s: %s]' % (self, self._comment or '(comment not loaded)')
+
+class MagicRule:
+ def __init__(self, f):
+ self.next=None
+ self.prev=None
+
+ #print line
+ ind=''
+ while True:
+ c=f.read(1)
+ if c=='>':
+ break
+ ind+=c
+ if not ind:
+ self.nest=0
+ else:
+ self.nest=int(ind)
+
+ start=''
+ while True:
+ c=f.read(1)
+ if c=='=':
+ break
+ start+=c
+ self.start=int(start)
+
+ hb=f.read(1)
+ lb=f.read(1)
+ self.lenvalue=ord(lb)+(ord(hb)<<8)
+
+ self.value=f.read(self.lenvalue)
+
+ c=f.read(1)
+ if c=='&':
+ self.mask=f.read(self.lenvalue)
+ c=f.read(1)
+ else:
+ self.mask=None
+
+ if c=='~':
+ w=''
+ while c!='+' and c!='\n':
+ c=f.read(1)
+ if c=='+' or c=='\n':
+ break
+ w+=c
+
+ self.word=int(w)
+ else:
+ self.word=1
+
+ if c=='+':
+ r=''
+ while c!='\n':
+ c=f.read(1)
+ if c=='\n':
+ break
+ r+=c
+ #print r
+ self.range=int(r)
+ else:
+ self.range=1
+
+ if c!='\n':
+ raise 'Malformed MIME magic line'
+
+ def getLength(self):
+ return self.start+self.lenvalue+self.range
+
+ def appendRule(self, rule):
+ if self.nest<rule.nest:
+ self.next=rule
+ rule.prev=self
+
+ elif self.prev:
+ self.prev.appendRule(rule)
+
+ def match(self, buffer):
+ if self.match0(buffer):
+ if self.next:
+ return self.next.match(buffer)
+ return True
+
+ def match0(self, buffer):
+ l=len(buffer)
+ for o in range(self.range):
+ s=self.start+o
+ e=s+self.lenvalue
+ if l<e:
+ return False
+ if self.mask:
+ test=''
+ for i in range(self.lenvalue):
+ c=ord(buffer[s+i]) & ord(self.mask[i])
+ test+=chr(c)
+ else:
+ test=buffer[s:e]
+
+ if test==self.value:
+ return True
+
+ def __repr__(self):
+ return '<MagicRule %d>%d=[%d]%s&%s~%d+%d>' % (self.nest,
+ self.start,
+ self.lenvalue,
+ `self.value`,
+ `self.mask`,
+ self.word,
+ self.range)
+
+class MagicType:
+ def __init__(self, mtype):
+ self.mtype=mtype
+ self.top_rules=[]
+ self.last_rule=None
+
+ def getLine(self, f):
+ nrule=MagicRule(f)
+
+ if nrule.nest and self.last_rule:
+ self.last_rule.appendRule(nrule)
+ else:
+ self.top_rules.append(nrule)
+
+ self.last_rule=nrule
+
+ return nrule
+
+ def match(self, buffer):
+ for rule in self.top_rules:
+ if rule.match(buffer):
+ return self.mtype
+
+ def __repr__(self):
+ return '<MagicType %s>' % self.mtype
+
+class MagicDB:
+ def __init__(self):
+ self.types={} # Indexed by priority, each entry is a list of type rules
+ self.maxlen=0
+
+ def mergeFile(self, fname):
+ f=file(fname, 'r')
+ line=f.readline()
+ if line!='MIME-Magic\0\n':
+ raise 'Not a MIME magic file'
+
+ while True:
+ shead=f.readline()
+ #print shead
+ if not shead:
+ break
+ if shead[0]!='[' or shead[-2:]!=']\n':
+ raise 'Malformed section heading'
+ pri, tname=shead[1:-2].split(':')
+ #print shead[1:-2]
+ pri=int(pri)
+ mtype=lookup(tname)
+
+ try:
+ ents=self.types[pri]
+ except:
+ ents=[]
+ self.types[pri]=ents
+
+ magictype=MagicType(mtype)
+ #print tname
+
+ #rline=f.readline()
+ c=f.read(1)
+ f.seek(-1, 1)
+ while c and c!='[':
+ rule=magictype.getLine(f)
+ #print rule
+ if rule and rule.getLength()>self.maxlen:
+ self.maxlen=rule.getLength()
+
+ c=f.read(1)
+ f.seek(-1, 1)
+
+ ents.append(magictype)
+ #self.types[pri]=ents
+ if not c:
+ break
+
+ def match_data(self, data, max_pri=100, min_pri=0):
+ pris=self.types.keys()
+ pris.sort(lambda a, b: -cmp(a, b))
+ for pri in pris:
+ #print pri, max_pri, min_pri
+ if pri>max_pri:
+ continue
+ if pri<min_pri:
+ break
+ for type in self.types[pri]:
+ m=type.match(data)
+ if m:
+ return m
+
+
+ def match(self, path, max_pri=100, min_pri=0):
+ try:
+ buf=file(path, 'r').read(self.maxlen)
+ return self.match_data(buf, max_pri, min_pri)
+ except:
+ pass
+
+ return None
+
+ def __repr__(self):
+ return '<MagicDB %s>' % self.types
+
+
+# Some well-known types
+text = lookup('text', 'plain')
+inode_block = lookup('inode', 'blockdevice')
+inode_char = lookup('inode', 'chardevice')
+inode_dir = lookup('inode', 'directory')
+inode_fifo = lookup('inode', 'fifo')
+inode_socket = lookup('inode', 'socket')
+inode_symlink = lookup('inode', 'symlink')
+inode_door = lookup('inode', 'door')
+app_exe = lookup('application', 'executable')
+
+_cache_uptodate = False
+
+def _cache_database():
+ global exts, globs, literals, magic, _cache_uptodate
+
+ _cache_uptodate = True
+
+ exts = {} # Maps extensions to types
+ globs = [] # List of (glob, type) pairs
+ literals = {} # Maps liternal names to types
+ magic = MagicDB()
+
+ def _import_glob_file(path):
+ """Loads name matching information from a MIME directory."""
+ for line in file(path):
+ if line.startswith('#'): continue
+ line = line[:-1]
+
+ type_name, pattern = line.split(':', 1)
+ mtype = lookup(type_name)
+
+ if pattern.startswith('*.'):
+ rest = pattern[2:]
+ if not ('*' in rest or '[' in rest or '?' in rest):
+ exts[rest] = mtype
+ continue
+ if '*' in pattern or '[' in pattern or '?' in pattern:
+ globs.append((pattern, mtype))
+ else:
+ literals[pattern] = mtype
+
+ for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'globs')):
+ _import_glob_file(path)
+ for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'magic')):
+ magic.mergeFile(path)
+
+ # Sort globs by length
+ globs.sort(lambda a, b: cmp(len(b[0]), len(a[0])))
+
+def get_type_by_name(path):
+ """Returns type of file by its name, or None if not known"""
+ if not _cache_uptodate:
+ _cache_database()
+
+ leaf = os.path.basename(path)
+ if leaf in literals:
+ return literals[leaf]
+
+ lleaf = leaf.lower()
+ if lleaf in literals:
+ return literals[lleaf]
+
+ ext = leaf
+ while 1:
+ p = ext.find('.')
+ if p < 0: break
+ ext = ext[p + 1:]
+ if ext in exts:
+ return exts[ext]
+ ext = lleaf
+ while 1:
+ p = ext.find('.')
+ if p < 0: break
+ ext = ext[p+1:]
+ if ext in exts:
+ return exts[ext]
+ for (glob, mime_type) in globs:
+ if fnmatch.fnmatch(leaf, glob):
+ return mime_type
+ if fnmatch.fnmatch(lleaf, glob):
+ return mime_type
+ return None
+
+def get_type_by_contents(path, max_pri=100, min_pri=0):
+ """Returns type of file by its contents, or None if not known"""
+ if not _cache_uptodate:
+ _cache_database()
+
+ return magic.match(path, max_pri, min_pri)
+
+def get_type_by_data(data, max_pri=100, min_pri=0):
+ """Returns type of the data"""
+ if not _cache_uptodate:
+ _cache_database()
+
+ return magic.match_data(data, max_pri, min_pri)
+
+def get_type(path, follow=1, name_pri=100):
+ """Returns type of file indicated by path.
+ path - pathname to check (need not exist)
+ follow - when reading file, follow symbolic links
+ name_pri - Priority to do name matches. 100=override magic"""
+ if not _cache_uptodate:
+ _cache_database()
+
+ try:
+ if follow:
+ st = os.stat(path)
+ else:
+ st = os.lstat(path)
+ except:
+ t = get_type_by_name(path)
+ return t or text
+
+ if stat.S_ISREG(st.st_mode):
+ t = get_type_by_contents(path, min_pri=name_pri)
+ if not t: t = get_type_by_name(path)
+ if not t: t = get_type_by_contents(path, max_pri=name_pri)
+ if t is None:
+ if stat.S_IMODE(st.st_mode) & 0111:
+ return app_exe
+ else:
+ return text
+ return t
+ elif stat.S_ISDIR(st.st_mode): return inode_dir
+ elif stat.S_ISCHR(st.st_mode): return inode_char
+ elif stat.S_ISBLK(st.st_mode): return inode_block
+ elif stat.S_ISFIFO(st.st_mode): return inode_fifo
+ elif stat.S_ISLNK(st.st_mode): return inode_symlink
+ elif stat.S_ISSOCK(st.st_mode): return inode_socket
+ return inode_door
+
+def install_mime_info(application, package_file):
+ """Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml.
+ If package_file is None, install <app_dir>/<application>.xml.
+ If already installed, does nothing. May overwrite an existing
+ file with the same name (if the contents are different)"""
+ application += '.xml'
+
+ new_data = file(package_file).read()
+
+ # See if the file is already installed
+ package_dir = os.path.join('mime', 'packages')
+ resource = os.path.join(package_dir, application)
+ for x in xdg.BaseDirectory.load_data_paths(resource):
+ try:
+ old_data = file(x).read()
+ except:
+ continue
+ if old_data == new_data:
+ return # Already installed
+
+ global _cache_uptodate
+ _cache_uptodate = False
+
+ # Not already installed; add a new copy
+ # Create the directory structure...
+ new_file = os.path.join(xdg.BaseDirectory.save_data_path(package_dir), application)
+
+ # Write the file...
+ file(new_file, 'w').write(new_data)
+
+ # Update the database...
+ command = 'update-mime-database'
+ if os.spawnlp(os.P_WAIT, command, command, xdg.BaseDirectory.save_data_path('mime')):
+ os.unlink(new_file)
+ raise Exception("The '%s' command returned an error code!\n" \
+ "Make sure you have the freedesktop.org shared MIME package:\n" \
+ "http://standards.freedesktop.org/shared-mime-info/") % command
--- /dev/null
+"""
+Implementation of the XDG Recent File Storage Specification Version 0.2
+http://standards.freedesktop.org/recent-file-spec
+"""
+
+import xml.dom.minidom, xml.sax.saxutils
+import os, time, fcntl
+from xdg.Exceptions import *
+
+class RecentFiles:
+ def __init__(self):
+ self.RecentFiles = []
+ self.filename = ""
+
+ def parse(self, filename=None):
+ if not filename:
+ filename = os.path.join(os.getenv("HOME"), ".recently-used")
+
+ try:
+ doc = xml.dom.minidom.parse(filename)
+ except IOError:
+ raise ParsingError('File not found', filename)
+ except xml.parsers.expat.ExpatError:
+ raise ParsingError('Not a valid .menu file', filename)
+
+ self.filename = filename
+
+ for child in doc.childNodes:
+ if child.nodeType == xml.dom.Node.ELEMENT_NODE:
+ if child.tagName == "RecentFiles":
+ for recent in child.childNodes:
+ if recent.nodeType == xml.dom.Node.ELEMENT_NODE:
+ if recent.tagName == "RecentItem":
+ self.__parseRecentItem(recent)
+
+ self.sort()
+
+ def __parseRecentItem(self, item):
+ recent = RecentFile()
+ self.RecentFiles.append(recent)
+
+ for attribute in item.childNodes:
+ if attribute.nodeType == xml.dom.Node.ELEMENT_NODE:
+ if attribute.tagName == "URI":
+ recent.URI = attribute.childNodes[0].nodeValue
+ elif attribute.tagName == "Mime-Type":
+ recent.MimeType = attribute.childNodes[0].nodeValue
+ elif attribute.tagName == "Timestamp":
+ recent.Timestamp = attribute.childNodes[0].nodeValue
+ elif attribute.tagName == "Private":
+ recent.Prviate = True
+ elif attribute.tagName == "Groups":
+
+ for group in attribute.childNodes:
+ if group.nodeType == xml.dom.Node.ELEMENT_NODE:
+ if group.tagName == "Group":
+ recent.Groups.append(group.childNodes[0].nodeValue)
+
+ def write(self, filename=None):
+ if not filename and not self.filename:
+ raise ParsingError('File not found', filename)
+ elif not filename:
+ filename = self.filename
+
+ f = open(filename, "w")
+ fcntl.lockf(f, fcntl.LOCK_EX)
+ f.write('<?xml version="1.0"?>\n')
+ f.write("<RecentFiles>\n")
+
+ for r in self.RecentFiles:
+ f.write(" <RecentItem>\n")
+ f.write(" <URI>%s</URI>\n" % xml.sax.saxutils.escape(r.URI))
+ f.write(" <Mime-Type>%s</Mime-Type>\n" % r.MimeType)
+ f.write(" <Timestamp>%s</Timestamp>\n" % r.Timestamp)
+ if r.Private == True:
+ f.write(" <Private/>\n")
+ if len(r.Groups) > 0:
+ f.write(" <Groups>\n")
+ for group in r.Groups:
+ f.write(" <Group>%s</Group>\n" % group)
+ f.write(" </Groups>\n")
+ f.write(" </RecentItem>\n")
+
+ f.write("</RecentFiles>\n")
+ fcntl.lockf(f, fcntl.LOCK_UN)
+ f.close()
+
+ def getFiles(self, mimetypes=None, groups=None, limit=0):
+ tmp = []
+ i = 0
+ for item in self.RecentFiles:
+ if groups:
+ for group in groups:
+ if group in item.Groups:
+ tmp.append(item)
+ i += 1
+ elif mimetypes:
+ for mimetype in mimetypes:
+ if mimetype == item.MimeType:
+ tmp.append(item)
+ i += 1
+ else:
+ if item.Private == False:
+ tmp.append(item)
+ i += 1
+ if limit != 0 and i == limit:
+ break
+
+ return tmp
+
+ def addFile(self, item, mimetype, groups=None, private=False):
+ # check if entry already there
+ if item in self.RecentFiles:
+ index = self.RecentFiles.index(item)
+ recent = self.RecentFiles[index]
+ else:
+ # delete if more then 500 files
+ if len(self.RecentFiles) == 500:
+ self.RecentFiles.pop()
+ # add entry
+ recent = RecentFile()
+ self.RecentFiles.append(recent)
+
+ recent.URI = item
+ recent.MimeType = mimetype
+ recent.Timestamp = int(time.time())
+ recent.Private = private
+ recent.Groups = groups
+
+ self.sort()
+
+ def deleteFile(self, item):
+ if item in self.RecentFiles:
+ self.RecentFiles.remove(item)
+
+ def sort(self):
+ self.RecentFiles.sort()
+ self.RecentFiles.reverse()
+
+
+class RecentFile:
+ def __init__(self):
+ self.URI = ""
+ self.MimeType = ""
+ self.Timestamp = ""
+ self.Private = False
+ self.Groups = []
+
+ def __cmp__(self, other):
+ return cmp(self.Timestamp, other.Timestamp)
+
+ def __eq__(self, other):
+ if self.URI == str(other):
+ return True
+ else:
+ return False
+
+ def __str__(self):
+ return self.URI
--- /dev/null
+__all__ = [ "BaseDirectory", "DesktopEntry", "Menu", "Exceptions", "IniFile", "IconTheme", "Locale", "Config", "Mime", "RecentFiles", "MenuEditor" ]
+++ /dev/null
-begin-base64 644 drlaunch-48.png
-iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABHNCSVQICAgI
-fAhkiAAAAAlwSFlzAAADOgAAAzoBWYNI0gAAABl0RVh0U29mdHdhcmUAd3d3
-Lmlua3NjYXBlLm9yZ5vuPBoAABTMSURBVGiBfZp7jCTXdd5/dep2dfW7e3pm
-Z2aH+9ZyuVy+SVEiRdGULJMSxTCBEtiyHEmUIzkRJASxDdiKg4Q0HCGWYSCw
-HQUGpBgCIcs0JAc2YjhKINA0RSzlXZErkibXpLxe7mN2dnYePf2qrr5961T+
-uD2zXJHIHTR6pqv71r3nfuf7vnN6AuAJAMRgCkUMGQCqyg3HbqSrdZbPvoKx
-I/wQdGYRXT8HCKaxBBMLmnL9/iVe6wmufZAHbtuHXDnHMxe3uO/wYbaimF0h
-PPP0X7Nn9y5WxsqDh+aw6gCIQ7AqkAsiMJgoiVNEoG5Ac4PNQAS0vZ+4FLPp
-ILjjY5/PT794kv3VIaViCWNiUIdTxySMkaiASpFS2oNcARgFhnY1ZmNrk1IY
-QRhBrmgAzev2c7ZjubBuufezv85zf/o1vvTlJ3jx61/hwsYKtWKEjJVd7RYS
-CHaSEBmhFIKIIcCQ5UDuGKlDFaIALqURCyXh8hgGLiW64R4+9HOfI5DQ5Efv
-uhutzOJWzmLiGoz7OAIKcZnRqEurVmE0dFSaMwxTpVKBja2E9kyV4UYP4jqV
-SOmuLdNot1lYuo4zKwPOrFvu++mHeeHk07yrsEXuLJ0E4nwCi4fYt7TE+X6P
-exarKBAHhrhguJLCxjChXRbqIXQcOCI0F9YtmEmPpLOKu+0Rwpl264nFhQWG
-E4VQ0MEGqhPIHFpsImEROymg4hiPEmgvkK5foVE2hHZCYh1qu4wHW5AL47FC
-YIgmA85fXuHCUKlvvklMSlCcIZuM0IVDHG7XOHfpMlGjzWQyoVUqkmSQqYdQ
-IwrZmAT0HJTCkHEWUC/kDGyOhIZ64DC1GYxgEMAkPdyoCyI4pxgMZrgGOdA+
-QDqyyHiE664TT0bEUkbqTbSQUJuk9HsbCIrJHN3NLUoRNO58hP7JP0da89Tq
-ZTr9ASaIaGRDtpIc01+mWYuwCBcGXcpG6OXqT0OEOIDNrR7tw0fRsSMWQ90o
-NjBcmRQYbVkMOR7D1SakQ5xLAQMh04xRSDY5tLSLyAiCQd0M5GDKZZYvniNe
-XEICS7/ThUIE4yFEFfp//XXYew/ajKARQ2cTXMb773o/t992O71BD0E8aaBI
-Lmjg80xyIa7EnDx5kiQHQsEIGCNc6jtmIoNWDAaAScK+Vp0zlx1gEANkDooN
-MAZ6Xb715F+RDBOsddjUMRr758e/8gTrm5v0t7oYiTwjBX5J99/7Ps50HenK
-afpuEdwYAkOr1WJsxzz5zSeRTNgeWZ6hU6IggH1L+zh48CDJcELNCAaoCVQL
-EAfCKy//AFEDk8mE18+dh6hAHEVEhQoEQpp0sYHBitKo1CATojAmimOKpYhC
-KSLpp4y31ljcfysaGtKoSBpAHpV55pnvcuHUM0TlNm7cI81SUjdAjPDQQw/x
-6MOPMrTDnYfLHDL90UxJJyljHaN5ykIJJIQJymLsuJz0aR+5DUN11u94tEFr
-YR/9YcIX/gW8+1iJI3sCim6IHUUA9HpbxMUqmKtRi4wipRqDceLDVvCUyngI
-xJBbdNwBMRDGyDTCUSHiEz//Cf7yf/8lw2TIOw2fDRAJLA+VSkGoGKFroVws
-sjpKkSh3OwLVubIMoz4AexZiTp9J33FigOHQ39T2B+igw3iQQjZChhugDkwR
-jj0ACIwGIJEXwbjt7xYIe/bu4f7334+IvOM9tvNjY6QUpznQncDAOrrDlEY5
-QuzEUWnOeS4KDNpcRNUxPyMstiPOrVjGFpwqmisOhwQhF8+fRxBcoLiohusv
-017YR2NmDuMygjCCv/seBA4NDJI7TGiYLynkV6P7i5/6RW46dhNGfDqqKqrq
-gRQIhaDAfCWiarxaz8ewpxpzQ7vKexaqiGSKm1hMqQHqKGUJqhMkz1mcLbOx
-pVxed37iXFEcEoBMI3Tk8FHsYAOnysbyOUZjxRgDdgCzRyCPUHWICMZZuhtd
-v4Hczzc/P89jn3qM+dl5j/3pBgBE/Cai0G/OBLBlIc0MayPlhU2HME5Q56C6
-CynW6HbWkDDaOcY7jsYMR37CmfYM5bgMwO7FPQA89tgv8bGPfRLnLFRblHa9
-y38wEKR/EUQ9jADiGMKrcNk+hX179/HIP3mEYlS8FkO5fxo4iEJwCqsjy0wR
-yoUIsWOEQImrdRisI6YMcQvN9FosTm9aqVaIpjdpNJsAfOPJ/87rp3+IKUQw
-7DA6d8prhxh0PMKvcUqtqfXXtjcwPQWnjjvvvJM777pzSsHTMf09FkjVy9Ji
-HNGbQCWEcrWCcZU6/Y0V0tEmRgyxCMbUcBKiOmSUCE4t7oXPkskenj014m/e
-UDRzEEaceP44susIc8fup/Pas6SZA4RYDFKI0ElCVFnAZQ4rgjrr4SCGiIie
-65FOUjLN+NDPfIjNziZvvP4GTh02s4x1zGBi6VjDUsUwE8NLmynxJGVfyWEY
-bML8DD5UCtU24HaC8NI/Drjj6BGi3t8h6Yucednxve+D6nShJoL1H9PfOAuF
-Coz7/vN2iLoUWksw7kFcRp1FAtmJrObThHbKOBsjgfC+e99Hd6vL8sryDguV
-C9AsGGzuV3agDr0urKYgIG85NoFBB4pVAF57M+XI/hgmK1Ba9G9xDuIGjb23
-+g0XKhC3ICpCNvaUaKY5JAI3PTCdX67CIvD4VvULV6cwATuxRCbijtvuoFar
-7QRxy/qprELifC7kwFwM0jxwjFBCTGAgV0+Xoy02NhwhhlbdIIHi3ADiRc9C
-oz7DC69iggiXTXDqcKEBVSQ3mEwJCiVQC8/+Cc6lSOBhI+U25OBy55NY/UZs
-ZknHKYN0AEC9UYfQ60WzYLbxQTWEKDBUQkOraDCDzR6Li2VMbnD1Gq7X5W9f
-HnN5dcSuVsT3TiTgDG68CnmNU1fmMdEYMiBzuCxFd+1BL5/H5HgKnQDOAjE0
-W96oBYLZfRjSFDLY6mzR6/ZI+gmjoU/2uBgzPztPuVqm2W5SLpV58+ybrCcj
-zASqRXA5DJwhzw27CgZTrtY9ZgM8sLAcP+U4ERhMOM2F3GFzg+Z9yJW41PLx
-yBwuc9DfQnTszzV3UDQQGq/IvQ1oe/Vl8yKNxb089e2nePr7z0BcJlIBm9Ka
-m2djfYVyo0FqyhyaqXJ2PcGNuizt3rMNdqzCrhg2Uw8rk3bWoNbyGwiNBxgC
-gUJYhPEIhB1xAeN9jil5TneAS71YBV5wyBQyC3uPwNYVxExfL87gTERUrnPm
-/GVqC/sZdzdhkmDWNkl7axBV2HPj7SwPoFYvMzNbRwDN/QNg3SpxCK/3QGbf
-+zDD0RANFdQS1+aJjSCmRGr7pFhStcRGiEs1YiOkuSWd9EkJiItl5mZ3ezaq
-1rCZhYLQsQbiNsZaKJSJyhGyaw76V+hvrnHs5jvZu7hIunUBOx6SJAMQw1x7
-lpWXf4C6hDh0vLTew+UpIpBmUA6hHFh66YB7bj1MIA98Jm+/9l12X7dAqVj0
-nkR80kzGV11isVCcMokynox3ip1Wa55ksOVpsVBmknZJkpR/vHCRsVVoHIKD
-N3Bo/SSLC4uMAkPRelqZn5/n/MU3vbgFIeVikaBYQpyilSpGHUSGmbhEVDC4
-DPJACSXjyA238K+++KsEBJKTC1FsqNUqCBEUykhrHrv6+lRVBVOsg00AwQV4
-fKvzRUwgUCijuYOCoXvpAhqVUNsluu52dO1NKMRUdu9DSruJLr+AjhMQg8vs
-TrdDAoOU6pibH+DxTz3CN/++wydumud81/JTS2U6I3ABLDbLzNxyH9fXIACT
-i5kqZAgStVDb9UmaG8i9TkihgFavg86P/c0KJXQyRMptNOn4nCGCwMHMHsyw
-h0uHPrFFMSZCKaLNJtFggAsN2l1FoirkGepGSGDQ625k7tCtbMzs4ff/w68g
-ofKdly7x+IPv4kC1RBxCO4LhBH7vDTCId3+lco1ypeytbGsJ0tQnX65QruMG
-PWS2jQZbGNRHPa+iUYTGAjkIBrnuMJ3E4jaW/emFCkGEG1vaMyXEpdCsA4ru
-vZ9osIrLUphMdSFZRjt12udP8Ox/6/DBjzxK/fv/l83WgyQGrAOJqzz80/fy
-6QMQEJp8adc8e/bu8aYt5+oQMy3SEyjVPWzGiW8CZJa3jcDb3yQZ8PqPzzA0
-MfTWAMOxG2+gUa9eO3+AL3TU+oCoUm826XW99SBzNBtNorhGbBwh0Fcom5g7
-bj7KF/7dLxOUKrX85puO0u/1cc75ifAKqGEIeeYL/EoLojJsre5gdtsSAFCq
-QOK7EUWxjDXn3MUBrjpH213gwIF9dDY6UKyBHSFRETUxphTjuhtQrPlGVnuB
-C92EohiG3XWo1Ln5yGHmSxFGIMkgEstga8A9H34UiYsROTnpxIuZEcGIoLnD
-2RFu4tuMpjaLcSkmwFsH9SK2/X4zHkGgOIHxJKNajHCjLfTyyxgR+v0+Lld0
-0se05hFyTNrFbizjCNC0T2djjQtn36SGMlw7T61WRkplTp0+zXLiOL2WIDmQ
-KxIqp//hDGa7tsLINV59mzJBfYFz5exP4EV52wgM1Gdwgx4wQd0Ir3Rm56Q0
-U0i6/lTLLSqR0O/2qNVrDPsDxI3pr54DdXRXhlCdY+ngERIHi40yum0GA1iM
-HeJhoFcV+JoxbTplUzsIsHDo2uth8eq13MHqGWTUnV7fzpNtez7d9CT1qh/G
-9NdXmJtt0h2kSJD7OaajtbBEqz3D8sVlqpMBG6nvCbn8aqyNCYRCoUAcGFyu
-pM5i5m4lmrubKAcNBNyYdNSHDHQyQXYd8rZaHT1rUbtMHFzCRCXiAIgiAuxO
-5F2mvlgKY8ghdWACxY22iJduZv+eOf71Zz/IT933bmxqWV5d4alv/zmvnDpJ
-f+RozR7kpdOvMnfgCGfPd7j90AE0j9E83m6MRlBr+FogN5iF9xJd/xmsdWBH
-4FJIBmDHqLWMbQo2RW2KlRGkzxO7M1DIvK8adKFSRIKphwqmTiyfPgKHmzgE
-5dd/6ZN89MH7cdYxTL3yt1stPvfYL/D6XXfxld//KsN/OAlz+5ht1tnorKGZ
-o2bA5CBeaCJIh2/B/f9/FIOExfn47Rdy9YvPPRw10ylTOb9wZWoaS4Bw77vv
-5p8/8iAvn4cvPTXioSe2+JnfuMDvfXsNgIPXH+RXvvBZnBHiyHD+4gqlWpug
-4AsmMWA0VzTt+a4CPpclMGgAmk8AX+RIINhkk48/PMt9H7ifgVP++JuvcfyZ
-s4iYaRr4At1vYCpiChIaVH1PiekXFlpu8Pjjv8mLF+B/PKc8eEz4j/+sibNV
-/uDPLvDHf7VGkvT4+IMHeOD++3jub55Ddx/m6Pwso8wRq2M4cb7pkWWZvzEe
-qxIIGsBg9Qy//U97/J//vMTP3j+DcVs8+uEb/bcxOczOV6ZdNUHEAOLpNVfy
-PAedagqy0xRzKBLAIx98H/uuW+S/Pi382keUL/+F5WO/vcWvfX2NZ3+Usm8+
-5gcvbZBax4c+8ADSaNOotkjTlALK2thhnUOuQubt0Ln7ENx95wEAPvPRFoPB
-tT3MUslc/axOo16sscNm4tsrb2U3CQREOHTIs9m5DZhvQKsMX/lknffeUObA
-oqHdMBzZW2WjbylXy+ggIWqUOdAo0xn7IOTBNbxp+MnxwzNw4gXP/1/7Xx2i
-6Nr3jEaTq39sNwbG/bc0CWJvBnFeIwIz7URcDVZt2ss6PC/88IzjnhtLHL4u
-5kdvJFxaS6ltB8mNcJfOsppYmkUhEhgrSFSa8TQa+kilzuHUYhSqCzfwpb9o
-8tH/tMJ3vr+OmTnMH/zh0yB+gs0rKeoUVYfLFHIhlog4MARBCLlFRHDq24Jx
-YIglIp0o3/rTp3A4/uV7hN/4M8Pv/kKFVlX57gtdFMeR/RH9kdKsxJx+5TQO
-ZW0wwE4s86WYmolZLMUYHfWAuWtN1tsEbfqqGJ5/o8nzv/wsszNlli9sveUA
-S6DZW/yRL0s1d95qb59K6BV/5fIaJ06+yM/fdQvPvwZf+MaImxemPJjBl79x
-jt/6nIfZ8ePHfcma9LgyStnTrGIVbKbItnkDpscN4HCjDm6wAoBmY9R5/Gu6
-wSQos7Kaounq1c+6ocd84NduCgYpVqYLn8IgZ6r4ioTCb/7WfwHgdz4OH77F
-kKRwqeOQEH733x7i8L4qp069xKunXyUqN1A3oTweEpupyckF0dx6issVAm/O
-JIj4/COL3F5/BUW5bSmhOXgOJORXP3kX6eXjuFz53M8dQ9MVb6NFfF9p2ut0
-E4umE2+88KzlcsUF6u8Rhiy/+WP+zRe/yIkTJ3j4duHzHy7z7392jk8/NEez
-kvKd7/xPvvqHX8UEBi1EmGKNlUS5nDgmucPlDqO5eBrNLSYw3om++jVWn+sy
-eP0F7Okn6F5/lGD5MnH6LeyxT1O69EfE3Zho+CnaW08SxzGYCKdeMwgj8jyb
-Jq/fnNcB31KLQzOFm3D8B8c5/qNXWKyXeeihj6DA+uVLfO/Zv8VmIyQUbABx
-OoFiEQKIjSMIHOXQEbTa7fxdhw6yvrmJacxhht6I+cW4nXyIjdkp5FOXsq3a
-sbmqyN5mexVu1quc+tEp7xp3LdFsNUmtRUJDFABRBbIx6TiB0hy4ASabYMJp
-hxDBhUUYd5CoSoRAY47KzG6O7auhowFm9xEM270cMZD03zF5dzRix26/c5Jj
-4mnhPx2BQUqNab2s7FjrQgnsVFMCQTQFN2JnLXENsUOoNJHI0C7FSGM3aTKA
-QoHlfspMAE///Zp3o8VCkRjBTRzpdMEmEOIw2mGn1Flv+ly64yoJIHXb9tdh
-AkNMBAEEQQAqaNrFxnNEUcz0/0hIR0OoVmAwJC5WYDL23+Sg2AxIh0ixQhzV
-oVpHspRBALNlYfn8q+y+5U7W+13ec+thpNPdIElSSpXSNFJTQxfoVXHO8ZFz
-Foh+4vXt0zC+ixFCu91mdW3KUGqwNqUUF32i6/REB33vnzSDxhyUK/5S4HtE
-YvvUCsCVs4gRxstvcGEkEEZcWl6mXjAk1SUCjMkrpQpHDh3ypgu/CZk2ZLd5
-3eVXoWTeQr1XXxck8F/OdXt9zi+fnyq1V94D+w4wN9uGaUlKYCD3p4aJ/P9m
-qC9rFRATIZUFmGxRLRq2OpsQlYniMtKe5z3vfT83f+BR/h9cGEWXakoA8AAA
-AABJRU5ErkJggg==
-====
+++ /dev/null
-[Desktop Entry]
-Name=DrLaunch
-Comment=Application launcer widget with portrait mode
-Type=python
-X-Path=drlaunch_widget.py
-X-Multiple=true
+++ /dev/null
-#convert drlaunch-64x64.png -scale 48x48 -depth 8 drlaunch-48x48.png
-uuencode -m drlaunch-48.png drlaunch-48.png > drlaunch-48.base64
+++ /dev/null
-#!/bin/sh
-
-VERSION=`cd drlaunch/src ; python2.5 -c "import config ;print config.version"`
-
-if [ -z "$VERSION" ] ; then
- echo "Version not found"
- exit 1
-fi
-
-D="drlaunch-$VERSION"
-
-rm -rf tmp
-mkdir tmp
-cp -pR drlaunch "tmp/$D"
-
-FN="drlaunch-$VERSION.tar.gz"
-
-echo "Creating $FN"
-tar -zcf $FN --exclude ".svn" --exclude ".*.swp" -C tmp $D
-
-rm -rf tmp
-
+++ /dev/null
-#!/bin/bash
-
-rm MANIFEST dist/*.tar.gz
-python2.5 setup.py sdist
-
-for i in dist/drlaunch-*.tar.gz ; do
- t=$(basename $i)
- ver=${t#drlaunch-}
- ver=${ver%.tar.gz}
- if ! [ -d "../$ver" ] ; then
- echo "New version: $ver"
- cp ../drlaunch/dist/$t ..
-# mkdir "../$ver"
-# cp ../drlaunch/dist/$t ../$ver/$t
-# ln -s $t ../$ver/drlaunch_$ver.orig.tar.gz
- fi
-done
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of maegirls.
-#
-# maegirls 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.
-#
-# maegirls 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 maegirls. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: setup.py 2266 2010-02-21 19:33:27Z v13 $
-
-from distutils.core import setup
-
-setup(
- name='drlaunch',
- version="1.0",
- description="DrLaunch",
- author="Stefanos Harhalakis",
- author_email="v13@v13.gr",
- url="not-available-yet",
- packages=['drlaunch', 'drlaunch.xdg'],
- package_dir={'drlaunch': 'src'},
-# scripts=["maegirls"],
-# data_files=[
-# ("share/maegirls/translations", i18n_qm_files)
-# ],
- long_description="DrLaunch - Application launcher widget with portrait mode"
- )
-
-# py_modules=['src/core.py', 'src/graph.py', 'src/wifi-su.py',
-# 'src/wifi.py', 'src/win.py'],
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-*.pyo
-*.pyc
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of DrLaunch.
-#
-# DrLaunch 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.
-#
-# DrLaunch 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 DrLaunch. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-from gtk import AboutDialog
-import config
-from about0 import HeAboutDialog
-from portrait import FremantleRotation
-
-class DlgAbout(HeAboutDialog, FremantleRotation):
- def __init__(self):
- HeAboutDialog.__init__(self)
- FremantleRotation.__init__(self, "DrlaunchPlugin",
- mode=FremantleRotation.AUTOMATIC)
-
- @classmethod
- def present2(cls, parent):
- _copyright="""\
-Copyright (C) 2010 Stefanos Harhalakis <v13@v13.gr>
-Copyright (C) 2005-2010 Thomas Perl and the gPodder Team (for about0.py
-and portrait.py)
-"""
- _description="""
-DrLaunch is a desktop launcher widget that
-supports portrait mode by rotating program
-icons. Whenever the device is rotated, icons
-are also rotated.
-
-Thanks to Thomas Perl and the gPodder Team.
-"""
-
- _license="""\
-Send bug reports and feature requests to maemo's bugzilla:
-https://bugs.maemo.org/enter_bug.cgi?product=DrLaunch (preferred)
-or directly to Stefanos Harhalakis <v13@v13.gr>
-
-DrLaunch 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.
-
-DrLaunch 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 DrLaunch. If not, see <http://www.gnu.org/licenses/>.
-
-DrLaunch uses a modified version of about.py from gpodder which is also
-distributed under the terms of the GPLv3 license. It also uses a
-modified version of portrait.py from gpodder which is also distributed
-under the terms of the GPLv3 license.
-"""
-
- args={
- "app_name": "DrLaunch",
- "version": config.version,
- "copyright": _copyright + "\n" + _license,
- "description": _description,
- "bugtracker_url": "https://bugs.maemo.org/enter_bug.cgi?product=DrLaunch",
- "parent": parent,
- }
-
- cls.present(**args)
-
- def set_icon_name(self, icon_name):
- pass
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# gPodder - A media aggregator and podcast client
-# Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
-#
-# gPodder 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.
-#
-# gPodder 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/>.
-#
-
-# Python implementation of HeAboutDialog from hildon-extras
-# Copyright (c) 2010-04-11 Thomas Perl <thp@thpinfo.com>
-
-import hildon
-import gtk
-import dbus
-
-_ = str
-
-class HeAboutDialog(gtk.Dialog):
- RESPONSE_WEBSITE, \
- RESPONSE_BUGTRACKER, \
- RESPONSE_DONATE = range(3)
-
- def __init__(self):
- gtk.Dialog.__init__(self)
-
- self.website_url = None
- self.bugtracker_url = None
- self.donate_url = None
-
- self.set_title(_('About'))
-
- self.image_icon = gtk.Image()
- self.label_app_name = gtk.Label()
- self.label_version = gtk.Label()
- self.label_description = gtk.Label()
- self.label_copyright = gtk.Label()
- self.table_layout = gtk.Table(3, 3, False)
-
- hildon.hildon_helper_set_logical_font(self.label_app_name, 'X-LargeSystemFont')
- hildon.hildon_helper_set_logical_font(self.label_version, 'LargeSystemFont')
- hildon.hildon_helper_set_logical_font(self.label_copyright, 'SmallSystemFont')
- hildon.hildon_helper_set_logical_color(self.label_copyright, gtk.RC_FG, gtk.STATE_NORMAL, 'SecondaryTextColor')
-
- self.label_app_name.set_alignment(0, 1)
- self.label_version.set_alignment(0, 1)
- self.label_description.set_alignment(0, 0)
- self.label_copyright.set_alignment(0, 1)
- self.label_version.set_padding(10, 0)
- self.label_copyright.set_padding(0, 5)
- self.image_icon.set_padding(5, 5)
-
- #content_area = self.get_content_area() # Starting with PyGTK 2.14
- self.set_size_request(800,800)
- pa=hildon.PannableArea()
- pa.set_property('mov-mode',
- hildon.MOVEMENT_MODE_HORIZ | hildon.MOVEMENT_MODE_VERT )
- vbox=gtk.VBox()
- self.vbox.add(pa)
- pa.add_with_viewport(vbox)
- content_area = vbox
-
- self.table_layout.attach(self.image_icon, 0, 1, 0, 2, 0, gtk.EXPAND, 0, 0)
- self.table_layout.attach(self.label_app_name, 1, 2, 0, 1, 0, gtk.EXPAND | gtk.FILL, 0, 0)
- self.table_layout.attach(self.label_version, 2, 3, 0, 1, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, 0, 0)
- self.table_layout.attach(self.label_description, 1, 3, 1, 2, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, 0, 0)
- self.table_layout.attach(self.label_copyright, 0, 3, 2, 3, gtk.EXPAND | gtk.FILL, gtk.EXPAND | gtk.FILL, 0, 0)
- content_area.add(self.table_layout)
- self.connect('response', self._on_response)
- self.show_all()
-
- def _on_response(self, dialog, response_id):
- if response_id == HeAboutDialog.RESPONSE_WEBSITE:
- self.open_webbrowser(self.website_url)
- elif response_id == HeAboutDialog.RESPONSE_BUGTRACKER:
- self.open_webbrowser(self.bugtracker_url)
- elif response_id == HeAboutDialog.RESPONSE_DONATE:
- self.open_webbrowser(self.donate_url)
-
- def set_app_name(self, app_name):
- self.label_app_name.set_text(app_name)
- self.set_title(_('About %s') % app_name)
-
- def set_icon_name(self, icon_name):
- self.image_icon.set_from_icon_name(icon_name, gtk.ICON_SIZE_DIALOG)
-
- def set_version(self, version):
- self.label_version.set_text(version)
-
- def set_description(self, description):
- self.label_description.set_text(description)
-
- def set_copyright(self, copyright):
- self.label_copyright.set_text(copyright)
-
- def set_website(self, url):
- if self.website_url is None:
- self.add_button(_('Visit website'), HeAboutDialog.RESPONSE_WEBSITE)
- self.website_url = url
-
- def set_bugtracker(self, url):
- if self.bugtracker_url is None:
- self.add_button(_('Report bug'), HeAboutDialog.RESPONSE_BUGTRACKER)
- self.bugtracker_url = url
-
- def set_donate_url(self, url):
- if self.donate_url is None:
- self.add_button(_('Donate'), HeAboutDialog.RESPONSE_DONATE)
- self.donate_url = url
-
- def open_webbrowser(self, url):
- bus = dbus.SessionBus()
- proxy = bus.get_object('com.nokia.osso_browser', '/com/nokia/osso_browser/request', 'com.nokia.osso_browser')
- proxy.load_url(url, dbus_interface='com.nokia.osso_browser')
-
- @classmethod
- def present(cls, parent=None, app_name=None, icon_name=None, \
- version=None, description=None, copyright=None, \
- website_url=None, bugtracker_url=None, donate_url=None):
- ad = cls()
-
- if parent is not None:
- ad.set_transient_for(parent)
- ad.set_destroy_with_parent(True)
-
- if app_name is not None:
- ad.set_app_name(app_name)
-
- ad.set_icon_name(icon_name)
- ad.set_version(version)
- ad.set_description(description)
- ad.set_copyright(copyright)
-
- if website_url is not None:
- ad.set_website(website_url)
-
- if bugtracker_url is not None:
- ad.set_bugtracker(bugtracker_url)
-
- if donate_url is not None:
- ad.set_donate_url(donate_url)
-
- ad.run()
- ad.destroy()
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-import os
-
-from gettext import translation
-#import locale
-
-#from xdg.IconTheme import getIconPath
-
-appdir="/usr/share/applications/hildon"
-
-apps={}
-
-def readOneFn(fn):
- global appdir
-
- ret={
- 'id': fn[:-8],
- 'name': None,
- 'exec': None,
- 'icon': None,
- 'iconpath': None,
- 'domain': None,
- 'type': None,
- }
-
- fn2=appdir + '/' + fn
-
- try:
- f=open(fn2, 'rt')
- except:
- return(None)
-
- inde=False
- for line in f:
- line=line.strip()
- if line=='[Desktop Entry]':
- inde=True
- continue
-
- if inde==False:
- continue
-
- # Reached another block
- if line.startswith('[') and inde:
- break
-
- elif line.startswith('Name='):
- l=line[5:]
- ret['name']=l
- elif line.startswith('Exec='):
- l=line[5:]
- ret['exec']=l
- elif line.startswith('Icon='):
- l=line[5:]
- ret['icon']=l
- # ret['iconpath']=getIconPath(l)
- elif line.startswith('X-Text-Domain='):
- l=line[14:]
- ret['domain']=l
- elif line.startswith('Type='):
- l=line[5:]
- ret['type']=l
-
- if ret['domain']!=None:
- try:
- c=translation(ret['domain'])
- except IOError, e:
- c=None
-
- if c!=None:
- ret['name0']=ret['name']
- ret['name']=c.gettext(ret['name0'])
-
- if ret['name']==None:
- ret['name']=ret['id']
-
- return(ret)
-
-def readOne(name):
- fn=name + ".desktop"
-
- ret=readOneFn(fn)
-
- return(ret)
-
-def scan():
- global appdir, apps
-
- files=os.listdir(appdir)
-
- ret={}
-
- for f in files:
- if not f.endswith('.desktop'):
- continue
- if f.startswith('catorise-'):
- continue
-
- dt=readOneFn(f)
-
- if dt==None:
- continue
- if dt['type']=='Daemon' or dt['type']=='daemon':
- continue
-
- t=f[:-8]
- ret[t]=dt
-
- apps=ret
-
- return(ret)
-
-def getLastScan():
- global apps
-
- return(apps)
-
-if __name__=="__main__":
- #locale.setlocale(locale.LC_ALL, '')
- print scan()
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-import os
-import pickle
-
-version = "1.0"
-
-try:
- from glib import get_user_config_dir
-except:
- def get_user_config_dir():
- home=os.environ['HOME']
- if home=='':
- home="/home/user"
- cfg="%s/.config" % (home)
-
- return(cfg)
-
-def ensure_dir():
- dir0=get_user_config_dir()
- dir=dir0+'/drlaunch'
- if not os.path.exists(dir):
- os.mkdir(dir)
- if not os.path.isdir(dir):
- raise Exception('Failed to ensure directory' + dir)
-
- return(dir)
-
-def get_config_fn():
- dir=ensure_dir()
- ret=dir + '/config'
-
- return(ret)
-
-class Config(object):
- def __init__(self, id):
- self.id=id
-
- self.size = (2,2)
- self.themebgsize = 96
- self.iconsize0 = 64
-### self.iconspace = 42 # For 4 icons (height)
-## self.iconspace = 36 # For 8 icons (width)
-
-# self.iconsize = 64
-# self.iconpadding = 12
-# self.iconmargin = 6
-
- self.apps=None
- self.indiv=True
- self.longpress=False
- self.animate=True
- self.nobg=False
- self.themebg=False
-
- self.setDefaultSizes()
-
- #self.maxsz=(8,4)
-
- def setSize(self, sz):
- maxsz=self.getMaxSize()
- sz2=(min(sz[0], maxsz[0]), min(sz[1], maxsz[1]))
- self.size=sz2
-
- def getSize(self):
- return(self.size)
-
- def setDefaultSizes(self):
- self.iconsize=64
- self.iconpadding=12
- self.iconmargin=6
-
- # Return the maximum grid size
- def getMaxSize(self):
- isf=self.getIconSizeFull()
- retx=int(800/isf)
- rety=int((480-60)/isf)
- maxsz=(retx, rety)
- return(maxsz)
-
- # Return the maxmimum icon size
- def getIconSizeRange(self):
- return((48,128))
-
- def getIconPaddingRange(self):
- return((0,32))
-
- def getIconMarginRange(self):
- return((0,16))
-
- def getIconSize(self):
- return(self.iconsize)
-
- def setIconSize(self, sz):
- self.iconsize=sz
-
- def getIconSpace(self):
- return((2*self.iconpadding) + (2*self.iconmargin))
-
- def getIconSizeFull(self):
- ret=self.iconsize + (2*self.iconpadding) + (2*self.iconmargin)
- return(ret)
-
- def getIconMargin(self):
- return(self.iconmargin)
-
- def setIconMargin(self, sz):
- self.iconmargin=sz
-
- def getIconPadding(self):
- return(self.iconpadding)
-
- def setIconPadding(self, sz):
- self.iconpadding=sz
-
- def setIndiv(self, indiv):
- self.indiv=indiv
-
- def getIndiv(self):
- return(self.indiv)
-
- def setLongpress(self, lp):
- self.longpress=lp
-
- def getLongpress(self):
- return(self.longpress)
-
- def setAnimate(self, ar):
- self.animate=ar
-
- def getAnimate(self):
- return(self.animate)
-
- def setNoBg(self, nobg):
- self.nobg=nobg
-
- def getNoBg(self):
- return(self.nobg)
-
- def setThemeBg(self, themebg):
- self.themebg=themebg
-
- def getThemeBg(self):
- return(self.themebg)
-
- def setApps(self, aps):
- """ apps is a dictionary of (x,y)=>appname """
- self.apps=aps
-
- def getApps(self):
- if self.apps==None:
- tmp={
- (0,0): 'rtcom-call-ui',
- (0,1): 'rtcom-messaging-ui',
- (1,0): 'browser',
- (1,1): 'osso-addressbook',
- }
- self.setApps(tmp)
-
- return(self.apps)
-
- def filterDefault(self, value, default):
- if value==default:
- return(-1)
- else:
- return(value)
-
- def provideDefault(self, value, default):
- if value==-1:
- return(default)
- else:
- return(value)
-
- def save(self):
- self.check_init()
-
- dt=self.load_all()
-
- if dt==None:
- dt={
- 'version': 7,
- 'data': {},
- }
-
- dt['data'][self.id]={
- 'size': self.getSize(),
- 'apps': self.getApps(),
- 'indiv': self.getIndiv(),
- 'longpress': self.getLongpress(),
- 'animate': self.getAnimate(),
- 'nobg': self.getNoBg(),
- 'themebg': self.getThemeBg(),
- 'iconsize': self.filterDefault(self.getIconSize(), 64),
- 'iconpadding': self.filterDefault(self.getIconPadding(), 12),
- 'iconmargin': self.filterDefault(self.getIconMargin(), 6),
- }
-
- fn=get_config_fn()
-
- st=pickle.dumps(dt)
- f=file(fn, 'w')
- f.write(st)
- f.close()
-
- def parse_v1(self, dt0):
- """ Convert a v1 config to v2 """
- ret={
- 'version': 2,
- 'data': {},
- }
-
- ret['data'][self.id]={
- 'size': dt0['size'],
- 'apps': dt0['apps'],
- }
-
- return(ret)
-
- def parse_v2(self, dt):
- # Perhaps copy dt?
-
- dt['version']=3
-
- for i in dt['data']:
- dt['data'][i]['indiv']=False
- dt['data'][i]['size']=(dt['data'][i]['size'], dt['data'][i]['size'])
- dt['data'][i]['longpress']=True
-
- return(dt)
-
- def parse_v3(self, dt):
- dt['version']=4
-
- for i in dt['data']:
- dt['data'][i]['animate']=True
-
- return(dt)
-
- def parse_v4(self, dt):
- dt['version']=5
-
- for i in dt['data']:
- dt['data'][i]['nobg']=False
-
- return(dt)
-
- def parse_v5(self, dt):
- dt['version']=6
-
- for i in dt['data']:
- dt['data'][i]['themebg']=False
-
- return(dt)
-
- def parse_v6(self, dt):
- dt['version']=7
-
- for i in dt['data']:
- dt['data'][i]['iconsize']=-1
- dt['data'][i]['iconpadding']=-1
- dt['data'][i]['iconmargin']=-1
-
- return(dt)
-
- def load_all(self):
- fn=get_config_fn()
-
- try:
- f=file(fn, 'r')
- st=f.read()
- f.close()
- ret=pickle.loads(st)
-
- if ret==None:
- print "failed to load config"
- ret=None
- else:
- if ret['version']==1:
- ret=self.parse_v1(ret)
-
- if ret['version']==2:
- ret=self.parse_v2(ret)
-
- if ret['version']==3:
- ret=self.parse_v3(ret)
-
- if ret['version']==4:
- ret=self.parse_v4(ret)
-
- if ret['version']==5:
- ret=self.parse_v5(ret)
-
- if ret['version']==6:
- ret=self.parse_v6(ret)
- except Exception, e:
- print "config error:", e
- ret=None
-
- return(ret)
-
- def load(self):
- self.check_init()
-
- fn=get_config_fn()
-
- dt0=self.load_all()
-
- if dt0==None or not dt0['data'].has_key(self.id):
- return
-
- dt=dt0['data'][self.id]
-
- self.setSize(dt['size'])
- self.setApps(dt['apps'])
- self.setIndiv(dt['indiv'])
- self.setLongpress(dt['longpress'])
- self.setAnimate(dt['animate'])
- self.setNoBg(dt['nobg'])
- self.setThemeBg(dt['themebg'])
- self.setIconSize(self.provideDefault(dt['iconsize'], 64))
- self.setIconPadding(self.provideDefault(dt['iconpadding'], 12))
- self.setIconMargin(self.provideDefault(dt['iconmargin'], 6))
-
- def check_init(self):
- if self.id==None:
- import sys
-
- print "config.init() not done"
- sys.exit(1)
-
-def dump(obj):
- attrs=[attr for attr in dir(obj) if not callable(getattr(obj,attr))]
-
- print "obj:", obj
- for attr in attrs:
- if attr=='__gdoc__' or attr=='__doc__':
- continue
- print " ", attr, ":", getattr(obj, attr)
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-import os.path
-from drlaunch import widget
-
-class DrlaunchPlugin(widget.DrlaunchPlugin):
- pass
-
-def redirect_err():
- import sys
- import time
-
- print "Opening /tmp/drlaunch.log"
- f=open('/tmp/drlaunch.log', 'at', buffering=1)
- sys.stdout=f
- sys.stderr=f
- print "Log open:", time.ctime()
-
-hd_plugin_type = DrlaunchPlugin
-
-if os.path.exists('/tmp/drlaunch.log'):
- redirect_err()
-
-if __name__=="__main__":
- import gobject
- import gtk
-
- gobject.type_register(hd_plugin_type)
- obj=gobject.new(hd_plugin_type, plugin_id="plugin_id")
- obj.show_all()
- gtk.main()
-
-
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-import gtk
-import gobject
-import hildon
-from hildondesktop import *
-from gtk import gdk
-from math import pi
-import cairo
-import time
-
-from portrait import FremantleRotation
-import launcher
-from xdg.IconTheme import getIconPath
-from sig import Disconnector
-
-#import config
-import apps
-
-# Background surface for icons
-iconbg=None
-sthemebg1=None
-sthemebg2=None
-
-# Load an icon
-# Fall-back to default/blue if not found or name==None
-def getIcon(name, iconsize):
- # Default icon
- idef='tasklaunch_default_application'
-
- # If name==None then use the default icon
- if name==None or name=='':
- iname=idef
- else:
- iname=name
-
- ico=getIconPath(iname, iconsize)
-
- # If not found then use the default icon
- if ico==None:
- ico=getIconPath(idef, iconsize)
-
- try:
- ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
- except:
- # On error use the default icon
- ico=getIconPath(idef, iconsize)
- ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
- print "Icon with unhandled format:", iname
-
- return(ret)
-
-class Icon(Disconnector, gobject.GObject):
-#class Icon(gtk.Widget, Disconnector):
-
- __v_t=(gobject.SIGNAL_RUN_FIRST | gobject.SIGNAL_ACTION,
- gobject.TYPE_NONE, ())
-
- gsignals={
- 'click': __v_t,
- 'double-click': __v_t,
- 'tripple-click': __v_t,
- 'long-press': __v_t,
- }
-
- __gsignals__=gsignals
-
- def __init__(self, isconfig, config):
-# self.__gobject_init__()
- gobject.GObject.__init__(self)
-# gtk.Widget.__init__(self)
- Disconnector.__init__(self)
-
- self.isconfig=isconfig
- self.config=config
-
- self.appname=None
- self.icon=None
- self.sicon=None
- self.lastpress=0
- self.ispressed=False
-
- self.x=0
- self.y=0
-
- self.presstime=0.25
-
- self.window_=None
-
- self.clickcount=0
-
- self.angle=0
-
- self.cached_icons={}
-
- self.draw_queued=False
-
- #print "icon-init"
-
- def __del__(self):
- #print "icon-del"
- pass
-
- def timePressed(self):
- """ return how much time a button is pressed """
- dt=time.time() - self.lastpress
-
- return(dt)
-
- def reload(self):
- self.clearAnimationCache()
- self.clearBgCache()
- self.invalidate()
-
- def setApp(self, dt):
- if dt==None:
- self.appname=None
- self.icon=None
- self.sicon=None
- else:
- self.appname=dt['id']
- self.icon=dt['icon2']
- self.sicon=None
- self.clearAnimationCache()
- self.clearBgCache()
- self.invalidate()
-
- def clearAnimationCache(self):
- self.cached_icons={}
-
- def clearBgCache(self):
- global iconbg, sthemebg1, sthemebg2
-
- iconbg=None
- sthemebg1=None
- sthemebg2=None
-
- def getSize(self):
- return(self.config.getIconSizeFull())
- # return(self.config.iconsize+self.config.iconspace)
-
- def setAngle(self, angle):
- """ Set the angle. Return True if the angle changed or False if it
- didn't. The caller should invalidate the icon """
-
- # The step in degrees
- step=9
-
- angle2=int(angle/step)*step
-
- if angle2==self.angle:
- return(False)
-
- self.angle=angle2
-
- # The caller should be responsible for redrawing.
- # If we call invalidate() here there is the risk of having
- # icons rotate individually using different angles
-# self.invalidate()
-
- return(True)
-
- def mkbg(self, t_pressed):
- """ Create the background of the icon and cache it as a global
- variable among all icons and widget instances """
- global iconbg
-
- if iconbg!=None and t_pressed<=0.001:
- return(iconbg)
-
- w=self.getSize()
- s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
- cr0=cairo.Context(s)
- cr=gtk.gdk.CairoContext(cr0)
-
- cr.set_source_rgba(0.1, 0.1, 0.1, 1)
- cr.set_line_width(5)
-
- #if self.ispressed:
- if t_pressed>0.001 and \
- (t_pressed <= self.presstime or self.ispressed):
- t=1.0 * min(t_pressed, self.presstime) / self.presstime
- g=0.3+0.5*t
- b=0.3+0.7*t
- cr.set_source_rgba(0, g, b, 0.7)
- else:
- cr.set_source_rgba(0.3, 0.3, 0.3, 0.7)
-
- x=0
- y=0
- x3=x + (self.config.iconmargin)
- y3=y + (self.config.iconmargin)
-
- r=10 # Radius
- w=self.config.iconsize+(self.config.iconpadding*2)
-
- cr.move_to(x3+r, y3)
- cr.arc(x3+w-r, y3+r, r, pi*1.5, pi*2)
- cr.arc(x3+w-r, y3+w-r, r, 0, pi*0.5)
- cr.arc(x3+r, y3+w-r, r, pi*0.5, pi)
- cr.arc(x3+r, y3+r, r, pi, pi*1.5)
-
- cr.stroke_preserve()
- cr.fill()
- cr.clip()
- cr.paint()
-# cr.restore()
-
- if t_pressed<0.001:
- iconbg=s
-
- return(s)
-
- def get_sthemebg(self, pressed):
- """ Return the theme's background icon as a surface. Cache it. """
- global sthemebg1, sthemebg2
-
- if not pressed and sthemebg1!=None:
- return(sthemebg1)
- if pressed and sthemebg2!=None:
- return(sthemebg2)
-
- fn="/etc/hildon/theme/images/"
- if pressed:
- fn+="ApplicationShortcutAppletPressed.png"
- else:
- fn+="ApplicationShortcutApplet.png"
-
- w=self.config.iconsize + (self.config.iconpadding*2)
- buf=gtk.gdk.pixbuf_new_from_file_at_size(fn, w, w)
- s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
- cr0=cairo.Context(s)
- cr=gtk.gdk.CairoContext(cr0)
-
- cr.set_source_pixbuf(buf, 0, 0)
- cr.paint()
-
- if not pressed:
- sthemebg1=s
- else:
- sthemebg2=s
-
- return(s)
-
- def get_sicon(self):
- """ Return the icon as a surface. Cache it. """
- if self.sicon!=None:
- return(self.sicon)
-
- w=self.config.iconsize
- s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
- cr0=cairo.Context(s)
- cr=gtk.gdk.CairoContext(cr0)
-
- cr.set_source_pixbuf(self.icon, 0, 0)
- cr.paint()
-
- self.sicon=s
-
- return(s)
-
- def get_paint_icon(self):
- """ Return the icon to paint as a surface. The icon is rotated
- as needed. The result is cached. """
- angle=self.angle
-
- if self.timePressed() <= self.presstime or self.ispressed:
- t=self.timePressed()
- pressed=True
- else:
- t=0
- pressed=False
-
- if not pressed and self.cached_icons.has_key(angle):
- return(self.cached_icons[angle])
-
- w=self.config.getIconSizeFull()
- s=cairo.ImageSurface(cairo.FORMAT_ARGB32, w, w)
- cr0=cairo.Context(s)
- cr=gtk.gdk.CairoContext(cr0)
-
- # Paint the background
- if self.config.getNoBg():
- pass
- elif self.config.getThemeBg(): # Use theme bg
- s2=self.get_sthemebg(pressed)
-
- # have in mind the size difference of iconsize+iconspace with
- # the fixed themebgsize
- #xy0=int((w-self.config.themebgsize)/2)
- #xy0=int((w-self.config.iconsize)/2)
- xy0=self.config.iconmargin
-
- cr.save()
- cr.set_source_surface(s2, xy0, xy0)
- cr.paint()
- cr.restore()
- else:
- s2=self.mkbg(t)
- cr.save()
- cr.set_source_surface(s2, 0, 0)
- cr.paint()
- cr.restore()
-
- # If there is no icon then don't do anything more
- if self.icon!=None:
- # Get the icon as a surface (get_sicon() will cache the surface)
- sicon=self.get_sicon()
-
- # Width is the iconsize plus the empty border around the icon
- #w=self.config.iconsize + self.config.iconspace
-
- # This is used to locate the center of the surface
- dx=int(w/2)
-
- # This is the delta from the center where icons are drawn
- dx2=int(self.config.iconsize/2)
-
-# cr.save()
-
- # A transformation matrix with dx/dy set to point to the center
- m=cairo.Matrix(1, 0, 0, 1, dx, dx)
- cr.set_matrix(m)
- # Transform degrees to rads
- rot=-1 * pi * 2 * self.angle / 360
- cr.rotate(rot)
- # Draw the icon
- cr.set_source_surface(sicon, -dx2, -dx2) # Faster than pixbuf
-# cr.set_source_pixbuf(icon2, -dx2, -dx2)
- cr.paint()
-
-# cr.restore()
-
- if not pressed:
- self.cached_icons[angle]=s
-
- return(s)
-
-
- def draw(self, cr, x, y):
- self.draw_queued=False
- self.x=x
- self.y=y
-
- if self.icon==None and not self.isconfig:
- return
-
- cr.save()
- s=self.get_paint_icon()
- cr.set_source_surface(s, x, y)
- cr.paint()
-
- cr.restore()
-
- return(False)
-
- def timerPressed(self):
-# if not self.ispressed:
-# return(False)
-
- self.invalidate()
-
- if self.timePressed()>self.presstime:
- ret=False
- else:
- ret=True
-
- return(ret)
-
- def doPress(self):
- # Double-time: time for pressed and time for not-pressed
- if time.time() - self.lastpress > self.presstime*2:
- self.clickcount=0
-
- self.lastpress=time.time()
- self.ispressed=True
- gobject.timeout_add(20, self.timerPressed)
-
- def doRelease(self):
- dt=time.time() - self.lastpress
- self.ispressed=False
- self.invalidate()
- if dt<=self.presstime:
- self.clickcount+=1
- if self.clickcount==1:
- self.emit('click')
-# print "emit click", self
- elif self.clickcount==2:
- self.emit('double-click')
- if self.clickcount==3:
- self.emit('tripple-click')
- self.clickcount=0
- elif dt>self.presstime and dt<2:
- self.emit('long-press')
-# print "Emit lp"
-
- def doCancel(self):
- self.ispressed=False
-
- def setWindow(self, window):
- self.window_=window
-
- def invalidate(self, window=None):
- if window==None:
- window=self.window_
- else:
- self.window_=window
-
- if window==None:
- return
-
- if self.draw_queued:
-# print "queued"
- return
-
- self.draw_queued=True
- w=self.getSize()
- rect=gdk.Rectangle(self.x, self.y, w, w)
- gdk.Window.invalidate_rect(window, rect, True)
-
-#gobject.type_register(Icon)
-#signals=['click', 'double-click', 'tripple-click', 'long-press']
-#for s in signals:
-# gobject.signal_new(s, Icon, gobject.SIGNAL_RUN_FIRST | \
-# gobject.SIGNAL_ACTION, gobject.TYPE_NONE, ())
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-import gtk
-import gobject
-import hildon
-from hildondesktop import *
-from gtk import gdk
-from math import pi
-import cairo
-import time
-
-from portrait import FremantleRotation
-#from xdg.IconTheme import getIconPath
-
-from config import dump
-import apps
-import icon
-from icon import Icon
-from icons import Icons
-
-#def getIcon(name, iconsize):
-# ico=getIconPath(name, iconsize)
-# ret=gtk.gdk.pixbuf_new_from_file_at_size(ico, iconsize, iconsize)
-#
-# return(ret)
-
-# IconGrid is the main class that implements tha drawing of the grid
-# However, since it will be used both by the desktop plugin and the
-# configuration window, it cannot derive either gtk.Widget or HomePluginItem.
-# It is created here in a way that will allow it to work on both cases
-# and it is inherited by appropriate classes (one for the plugin and one
-# for the config widget)
-
-#class IconGrid(gtk.Widget, FremantleRotation):
-class IconGrid: #(gobject.GObject):
- def __init__(self, isconfig=False):
-# self.__gobject_init__()
-# gtk.Widget.__init__(self)
-
- self.init_done=False
-
- self.size=(0,0)
-
- self.isconfig=isconfig
-
- self.icons=None
- self.lasticon=None # The last icon that got selected
-
- self.draw_pending=False
-
- self.mode=None
-
- # If this is False then animations are forcefully disabled
- self.do_animations=True
-
- self.angle_timer_start=0
-
- # Duration of the rotation effect
- self.rotation_time=0.8
-
-# print "ig-init"
-
-# def __del__(self):
-# print "ig-del"
-
- def do_realize(self, config):
-# print "ig-realize"
- self.config=config
-
- if self.icons!=None:
- print
- print
- print
- print "WTF??????????????????????"
- print
- print
- print
-
- self.icons=Icons(self.isconfig, self.config)
- #print "self:", self
- #self.icons.set_parent(self)
- self.setMode('l')
- self.setSize(config.getMaxSize())
- self.reloadIcons()
-
- def do_unrealize(self):
-# print "ig-unrealize"
- self.config=None
- self.icons.finish()
- self.icons=None
- self.lasticon=None
-
- def connect(self, what, *args):
- if what in Icon.gsignals.keys():
- ret=self.icons.connect(what, *args)
- else:
- ret=gobject.GObject.connect(self, what, *args)
- #ret=super(IconGrid, self).connect(what, *args)
-
- return(ret)
-
- def setSize(self, size):
- self.size=size
- self.icons.setSize(size)
-
- def getSize(self):
- ret=self.icons.getSize()
- return(ret)
-
- def setMode(self, mode):
- if self.mode==mode:
-# print "same mode"
- return
-
- self.mode=mode
- if not isinstance(self, gtk.Widget):
- return
-
- do_draw=False
-
- try:
- v=self.get_property('is-on-current-desktop')
- if v:
- do_draw=True
- else:
- self.draw_pending=True
- except TypeError:
- do_draw=True
-
- if do_draw and self.config.getAnimate() and self.do_animations:
- #self.queue_draw()
- # Don't start another timer
- # Instead adjust the time start to produce a nice effect ;-)
- if self.angle_timer_start==0:
- self.angle_timer_start=time.time()
- gobject.timeout_add(20, self.timerAngle)
- else:
- dt=time.time()-self.angle_timer_start
- da=90.0*dt/self.rotation_time
-
- da2=90.0-da
- dt2=da2*self.rotation_time/90.0
- self.angle_timer_start=time.time()-dt2
- else:
- if self.mode=='l':
- self.setAngle(0)
- else:
- self.setAngle(90)
-
- if do_draw:
- self.queue_draw()
-
- def disableAnimation(self):
- self.do_animations=False
-
- def enableAnimation(self):
- self.do_animations=True
-
- def setAnimationEnable(self, value):
- if value:
- self.enableAnimation()
- else:
- self.disableAnimation()
-
- def timerAngle(self):
- if self.angle_timer_start==0:
- self.angle_timer_start=time.time()-0.05
-
- dt=time.time()-self.angle_timer_start
-
- da=90.0*dt/self.rotation_time
-
- if self.mode=='l':
- angle=90-da
- else:
- angle=da
-
- if angle>=90:
- angle=90
- ret=False
- elif angle<0:
- angle=0
- ret=False
- else:
- ret=True
-
- if self.setAngle(angle):
- self.queue_draw()
-
- if ret==False:
- self.angle_timer_start=0
-
- return(ret)
-
- def iconAt(self, x, y):
- """ Get icon at coordinates x,y. X and Y are in pixels """
-
- w=self.config.getIconSizeFull()
-
- if self.mode=='l' or self.config.getIndiv():
- x2=int(x / w)
- y2=int(y / w)
- else:
- x2=self.size[1] - int(y/w) - 1
- y2=int(x/w)
-
- ret=self.get(x2,y2)
-
- return(ret)
-
- def get(self, x, y):
- ret=self.icons.get(x,y)
-
- return(ret)
-
- def _draw(self, cr, event):
- self.draw_pending=False
-
- w=self.config.getIconSizeFull()
- for x,y in self.icons:
- if self.mode=='l' or self.config.getIndiv():
- #x2=x * (self.config.iconsize + self.config.iconspace)
- #y2=y * (self.config.iconsize + self.config.iconspace)
- x2=x * self.config.getIconSizeFull()
- y2=y * self.config.getIconSizeFull()
- else:
- #x2=y * (self.config.iconsize + self.config.iconspace)
- #y2=(self.size[1]-x-1) * \
- # (self.config.iconsize + self.config.iconspace)
- x2=y * self.config.getIconSizeFull()
- y2=(self.size[1]-x-1) * self.config.getIconSizeFull()
-
- # Only repaint the needed icons
- rect=gdk.Rectangle(x2, y2, w, w)
- t=rect.intersect(event.area)
- if t.width==0 and t.height==0:
- continue
-
- ico=self.icons.get(x,y)
- ico.draw(cr, x2, y2)
-
- def setAngle(self, angle):
- """ Return True/False indicating that angle has changed """
- ret=False
- for x,y in self.icons:
- ic=self.icons.get(x,y)
- if ic.setAngle(angle):
- ret=True
-
- return(ret)
-
- def clearAnimationCache(self):
- """ Clear animation cache, freeing memory """
- for x,y in self.icons:
- ic=self.icons.get(x,y)
- ic.clearAnimationCache()
-
- def clearBgCache(self):
- """ Clear backgrounds cache """
- for x,y in self.icons:
- ic=self.icons.get(x,y)
- ic.clearBgCache()
-
- def do_expose_event(self, event):
- cr=self.window.cairo_create()
-
- cr.rectangle(event.area.x, event.area.y,
- event.area.width, event.area.height)
-
- cr.clip()
-
- if not self.init_done:
- self.icons.setWindow(self.window)
- self.init_done=True
-
- self._draw(cr, event)
-
- def setLastIcon(self, icon):
- if icon==self.lasticon:
- return
-
- if self.lasticon!=None:
- self.lasticon.doCancel()
- self.lasticon.invalidate(self.window)
- self.lasticon=icon
-
- def do_button_press_event(self, event):
- icon=self.iconAt(event.x, event.y)
- if icon==None:
- return
-# rect=gdk.Rectangle(event.x,event.y,1,1)
-# rect=gdk.Rectangle(0, 0, 100, 100)
- icon.doPress()
- icon.invalidate(self.window)
- self.setLastIcon(icon)
-
-# gdk.Window.invalidate_rect(self.window, rect, True)
-
- return(True)
-
- def do_button_release_event(self, event):
- if self.lasticon!=None:
- self.lasticon.invalidate(self.window)
- self.lasticon.doRelease()
-
- self.setLastIcon(None)
-
- return(True)
-
- def do_leave_notify_event(self, event):
- self.setLastIcon(None)
- return(True)
-
- def do_pproperty_notify_event(self, event):
- icon=self.iconAt(event.x, event.y)
- if icon==None:
- return
- icon.doCancel()
- icon.invalidate(self.window)
- return(True)
-
- def do_motion_notify_event(self, event):
- icon=self.iconAt(event.x, event.y)
- if self.lasticon==icon:
- return(True)
-
- self.setLastIcon(None)
- icon.doCancel()
- icon.invalidate(self.window)
- return(True)
-
- def do_button_press_event_old(self, event):
- if event.type==gdk.BUTTON_PRESS:
- if self.mode=='p':
- self.setMode('l')
- else:
- self.setMode('p')
- self.queue_draw()
- return True
-
- # For debugging
- def do_event1(self, event):
- print "event:", event, event.type
-
- def reloadIcons(self):
- self.icons.load()
- self.lasticon=None
-
-# def on_orientation_changed(self, orientation):
-# print "orch:", orientation
-# o=orientation[0]
-# self.setMode(o)
-
-class IconGridWidget(IconGrid, gtk.Widget):
- def __init__(self, isconfig, config, animation=True):
- IconGrid.__init__(self, isconfig)
- gtk.Widget.__init__(self)
-
- # This must be called before do_realize
- self.setAnimationEnable(animation)
-
- self.config=config
-
- IconGrid.do_realize(self, self.config)
-
- self.setSize(self.size)
-
-# print "igw-init"
-
-# def __del__(self):
-# print "igw-del"
-
- def setSize(self, size):
- IconGrid.setSize(self, size)
-
- w=self.size[0] * self.config.getIconSizeFull()
- h=self.size[1] * self.config.getIconSizeFull()
-
- self.set_size_request(w, h)
-
- def reconfig(self):
- self.clearBgCache()
- self.clearAnimationCache()
- self.reloadIcons()
- self.setSize(self.size)
- self.icons.resizeMax()
- self.queue_draw()
-
- def do_realize(self):
-# print "igw-realize"
- screen=self.get_screen()
- self.set_colormap(screen.get_rgba_colormap())
- self.set_app_paintable(True)
-
- self.set_flags(self.flags() | gtk.REALIZED)
-
- self.window=gdk.Window(
- self.get_parent_window(),
- width=self.allocation.width,
- height=self.allocation.height,
- window_type=gdk.WINDOW_CHILD,
- wclass=gdk.INPUT_OUTPUT,
- event_mask=self.get_events() | gdk.EXPOSURE_MASK
- | gdk.BUTTON_PRESS_MASK
- | gdk.BUTTON_RELEASE_MASK
- | gdk.BUTTON_MOTION_MASK
- | gdk.POINTER_MOTION_MASK
- | gdk.POINTER_MOTION_HINT_MASK
- | gdk.ENTER_NOTIFY_MASK
- | gdk.LEAVE_NOTIFY_MASK )
-
- self.window.set_user_data(self)
-
- # No matter what the pygtk widget demo says, this is NOT CORRECT!!!
- # Don't call style.attach(self.window) or else the program will crash
- # after some time! It seems that there is a style already.
- # If we want to use the style the we use get_style() instead.
- # This one was very hard to solve. Thanks to gnome2-globalmenu guys.
- # It was solved by looking the commit 2666:
- # see:
- # svn diff -r2665:2666 http://gnome2-globalmenu.googlecode.com/svn/trunk
- # which solved cse 490:
- # http://code.google.com/p/gnome2-globalmenu/issues/detail?id=490
-# self.style.attach(self.window)
- style=self.get_style()
-
-# style.set_background(self.window, gtk.STATE_NORMAL)
- self.window.move_resize(*self.allocation)
-
-# self.pixmap, mask = gtk.gdk.pixmap_create_from_xpm_d(
-# self.window, self.style.bg[gtk.STATE_NORMAL], STAR_PIXMAP)
-
-# self.gc = self.style.fg_gc[gtk.STATE_NORMAL]
-
- #HomePluginItem.do_realize(self)
-
-# screen=self.get_screen()
-# self.set_colormap(screen.get_rgba_colormap())
-# self.set_app_paintable(True)
-
- def do_unrealize(self):
-# print "igw-unrealize", self
- IconGrid.do_unrealize(self)
- self.window.destroy()
- self.window.set_user_data(None)
-
- def do_expose_event(self, event):
- cr=self.window.cairo_create()
-
- cr.rectangle(event.area.x, event.area.y,
- event.area.width, event.area.height)
- cr.clip()
-
- style=self.get_style()
- col=style.bg[gtk.STATE_NORMAL]
- cr.set_source_color(col)
- cr.paint()
-
- IconGrid.do_expose_event(self, event)
-
-#gobject.type_register(IconGrid)
-gobject.type_register(IconGridWidget)
-
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-#import config
-import apps
-import icon
-from icon import getIcon, Icon
-from sig import Disconnector
-
-import gobject
-import gtk
-
-class IconIter:
- def __init__(self, items):
- self.iter=items.__iter__()
-
- def __iter__(self):
- ret=self.iter.__iter__()
- return(ret)
-
- def next(self):
- ret=self.iter.next()
- return(ret)
-
-class Icons(gobject.GObject, Disconnector):
-#class Icons(gtk.Widget, Disconnector):
-
- #__gsignals__=Icon.gsignals
- def __init__(self, isconfig, config):
- self.__gobject_init__()
- #gobject.GObject.__init__(self)
- #gtk.Widget.__init__(self)
- Disconnector.__init__(self)
-
- self.icons={}
- self.allicons={}
- self.size=0
- self.isconfig=isconfig
- self.config=config
-
- # signal handlers
- self.handlers={}
-
- self.maxsz=(0,0)
-
- # setup allicons
- self.resizeMax()
-
-# print "icons-init"
-
- def finish(self):
- self.dis_finish()
- return
-# self.icons=None
-# self.allicons=None
-# print "icons-finish"
-
-# def __del__(self):
-# print "icons-del"
-
- def resizeMax(self):
- sz=self.maxsz
- maxsz=self.config.getMaxSize()
-
- # Create new entries in x
- if maxsz[0]>sz[0]:
- for x in xrange(maxsz[0]-sz[0]):
- for y in xrange(sz[1]):
- k=(x+sz[0],y)
- ico=Icon(self.isconfig, self.config)
- self.allicons[k]=ico
- self.connect_one(ico)
- sz=(maxsz[0], sz[1])
- elif maxsz[0]<sz[0]:
- for x in xrange(sz[0]-maxsz[0]):
- for y in xrange(sz[1]):
- k=(maxsz[0]+x,y)
- t=self.allicons.pop(k)
- self.disconnect_one(t)
- sz=(maxsz[0], sz[1])
-
- # Create new entries in y
- if maxsz[1]>sz[1]:
- for y in xrange(maxsz[1]-sz[1]):
- for x in xrange(sz[0]):
- k=(x,y+sz[1])
- ico=Icon(self.isconfig, self.config)
- self.allicons[k]=ico
- self.connect_one(ico)
- sz=(sz[0], maxsz[1])
- elif maxsz[1]<sz[1]:
- for y in xrange(sz[1]-maxsz[1]):
- for x in xrange(sz[0]):
- k=(x,y+maxsz[1])
- t=self.allicons.pop(k)
- self.disconnect_one(t)
- sz=(sz[0], maxsz[1])
-
- self.maxsz=sz
-
- @classmethod
- def register_signals(cls):
- signals=Icon.gsignals
- for s in signals:
- ss=signals[s]
- gobject.signal_new(s, cls, ss[0], ss[1], (Icon,))
-# gobject.SIGNAL_RUN_FIRST,
-# gobject.TYPE_NONE, (Icon,))
-
- def __iter__(self):
- return(IconIter(self.icons))
-
- def connect_one(self, which):
- self.handlers[which]={
- 'longpress': self.c(which, 'long-press', self.signalLongpress),
- 'click': self.c(which, 'click', self.signalClick),
- 'tripple': self.c(which, 'tripple-click',
- self.signalTrippleClick)
- }
-
- def disconnect_one(self, which):
- for i in self.handlers[which]:
- which.disconnect(self.handlers[which][i])
- self.handlers.pop(which)
-# which.disconnect(self.h[which]_longpress)
-# which.disconnect(self.h_click)
-# which.disconnect(self.h_tripple)
-
- def setSize(self, sz):
-# print "sz:", sz, self.size
-
- if sz==self.size:
- return
-
- old=self.icons
- self.icons={}
-
- for x in xrange(sz[0]):
- for y in xrange(sz[1]):
- k=(x,y)
- ico=self.allicons[k]
- self.icons[k]=ico
-# if old.has_key(k):
-# old.pop(k) # Re-used
-# else:
-# self.connect_one(ico)
-
- # Disconnect signals
-# for i in old:
-# self.disconnect_one(old[i])
-
- self.size=sz
-
- def getSize(self):
- return(self.size)
-
- def setWindow(self, win):
- """ Set the window for all icons """
-
- for i in self.icons:
- ic=self.icons[i]
- ic.setWindow(win)
-
- def signalLongpress(self, icon):
- #print "signalLongpress()", icon
- self.emit('long-press', icon)
-
- def signalClick(self, icon):
- #print "signalClick()", icon
- self.emit('click', icon)
-
- def signalTrippleClick(self, icon):
- #print "signalTrippleClick()", icon
- self.emit('tripple-click', icon)
-
- def get(self, x, y):
- k=(x,y)
- if self.icons.has_key(k):
- ret=self.icons[k]
- else:
- ret=None
-
- return(ret)
-
- def load(self):
-# x=0
-# y=0
-# fn=["maegirls", "wifieye", 'battery-eye', 'image-viewer',
-# 'tecnoballz', 'ncalc', 'rtcom-call-ui', 'rtcom-messaging-ui',
-# 'extcalllog', 'browser', 'modest', 'osso-addressbook']
-
- wapps=self.config.getApps()
- #sz=self.config.getSize()
- sz=self.getSize()
-
- for k in wapps:
- x,y=k
- if x>=sz or y>=sz:
- continue
-
- appname=wapps[k]
- if appname!=None:
- app=apps.readOne(appname)
- if app!=None:
- app['icon2']=getIcon(app['icon'], self.config.getIconSize())
- self.get(x,y).setApp(app)
- else:
- ic=self.get(x,y)
- ic.setApp(None)
-
- # Reload icons to make sure backgrounds, etc are valid
- for x in xrange(sz[0]):
- for y in xrange(sz[1]):
- ic=self.get(x,y)
- ic.reload()
-
-
-# for f in fn:
-# dt=apps.readOne(f)
-# dt['icon2']=getIcon(dt['icon'])
-# print x, y, dt
-# self.get(x,y).setApp(dt)
-# x+=1
-# if x>=config.getSize(getSize()):
-# x=0
-# y+=1
-## self.icons.append(p)
-
-gobject.type_register(Icons)
-Icons.register_signals()
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-from icon import Icon, getIcon
-import apps
-
-import gtk
-import cairo
-from gtk import gdk
-import gobject
-
-class IconWidget(gtk.Widget):
- def __init__(self, isconfig, config):
- gtk.Widget.__init__(self)
-
- self.config=config
- self.icon=Icon(isconfig, config)
-
- def getMaxIconSizeFull(self):
- c=self.config
- w=c.getIconSizeRange()[1] + 2*c.getIconPaddingRange()[1] + \
- 2*c.getIconMarginRange()[1]
-
- return(w)
-
- def resetSize(self):
- w=self.getMaxIconSizeFull()
- self.set_size_request(2*w, 2*w)
-
- def do_realize(self):
- screen=self.get_screen()
- self.set_colormap(screen.get_rgba_colormap())
- self.set_app_paintable(True)
-
- self.set_flags(self.flags() | gtk.REALIZED)
-
- self.window=gdk.Window(
- self.get_parent_window(),
- width=self.allocation.width,
- height=self.allocation.height,
- window_type=gdk.WINDOW_CHILD,
- wclass=gdk.INPUT_OUTPUT,
- event_mask=self.get_events() | gdk.EXPOSURE_MASK)
-
- self.window.set_user_data(self)
-
- self.window.move_resize(*self.allocation)
-
-# style=self.get_style()
-# style.set_background(self.window, gtk.STATE_NORMAL)
-# self.gc=style.bg_gc[gtk.STATE_NORMAL]
-
- self.icon.setWindow(self.window)
- self.resetSize()
-
- def do_unrealize(self):
- self.window.destroy()
- self.window.set_user_data(None)
-
- def do_expose_event(self, event):
- cr=self.window.cairo_create()
-
- cr.rectangle(event.area.x, event.area.y,
- event.area.width, event.area.height)
-
- cr.clip()
-
- cr.save()
- # Paint the background
- style=self.get_style()
- col=style.bg[gtk.STATE_NORMAL]
- cr.set_source_color(col)
- cr.paint()
-
- x2=self.config.getIconSizeFull()
-
-# col=style.fg[gtk.STATE_ACTIVE]
-# cr.set_source_color(col)
-# #cr.set_source_rgba(0,1,1,0.5)
-# cr.rectangle(1,1,x2+x2+2,x2+2)
-# cr.stroke()
-# cr.restore()
-
- self.icon.draw(cr, 0, 0)
- self.icon.draw(cr, x2, 0)
- self.icon.draw(cr, 0, x2)
- self.icon.draw(cr, x2, x2)
-
- def setApp(self, app):
- self.app=app
- self.refresh()
-
- def refresh(self):
- if self.app!=None:
- app=apps.readOne(self.app)
- app['icon2']=getIcon(app['icon'], self.config.getIconSize())
- self.icon.setApp(app)
-
- self.queue_draw()
-
-gobject.type_register(IconWidget)
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-import dbus
-
-bus=None
-proxy=None
-
-def init():
- global bus, proxy
-
- if bus==None:
- bus=dbus.SessionBus()
-
- if proxy==None:
- proxy=bus.get_object('com.nokia.HildonDesktop.AppMgr',
- '/com/nokia/HildonDesktop/AppMgr')
-
-def launch(prog):
- global bus, proxy
-
- proxy.LaunchApplication(prog,
- dbus_interface='com.nokia.HildonDesktop.AppMgr')
-
-if __name__=="__main__":
- init()
- launch('wifieye')
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# gPodder - A media aggregator and podcast client
-# Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
-#
-# gPodder 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.
-#
-# gPodder 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/>.
-#
-
-import dbus
-import dbus.glib
-from dbus.mainloop.glib import DBusGMainLoop
-
-import hildon
-import osso
-
-# Replace this with your own gettext() functionality
-_ = str
-
-
-class FremantleRotation(object):
- """thp's screen rotation for Maemo 5
-
- Simply instantiate an object of this class and let it auto-rotate
- your StackableWindows depending on the device orientation.
-
- If you need to relayout a window, connect to its "configure-event"
- signal and measure the ratio of width/height and relayout for that.
-
- You can set the mode for rotation to AUTOMATIC (default), NEVER or
- ALWAYS with the set_mode() method.
- """
- AUTOMATIC, NEVER, ALWAYS = range(3)
-
- # Human-readable captions for the above constants
- MODE_CAPTIONS = (_('Automatic'), _('Landscape'), _('Portrait'))
-
- # Privately-used constants
- _PORTRAIT, _LANDSCAPE = ('portrait', 'landscape')
- _ENABLE_ACCEL = 'req_accelerometer_enable'
- _DISABLE_ACCEL = 'req_accelerometer_disable'
-
- # Defined in mce/dbus-names.h
- _MCE_SERVICE = 'com.nokia.mce'
- _MCE_REQUEST_PATH = '/com/nokia/mce/request'
- _MCE_REQUEST_IF = 'com.nokia.mce.request'
-
- # sysfs device name for the keyboard slider switch
- KBD_SLIDER = '/sys/devices/platform/gpio-switch/slide/state'
- _KBD_OPEN = 'open'
- _KBD_CLOSED = 'closed'
-
- def __init__(self, app_name, main_window=None, version='1.0', mode=0,
- dontrotate=False):
- """Create a new rotation manager
-
- app_name ... The name of your application (for osso.Context)
- main_window ... The root window (optional, hildon.StackableWindow)
- version ... The version of your application (optional, string)
- mode ... Initial mode for this manager (default: AUTOMATIC)
- dontrotate ... Don't rotate the window. (def: False)
- """
- self.dontrotate = dontrotate # V13
- self._orientation = None
- self._main_window = main_window
- self._stack = hildon.WindowStack.get_default()
- self._mode = -1
- self._last_dbus_orientation = None
- self._keyboard_state = self._get_keyboard_state()
- app_id = '-'.join((app_name, self.__class__.__name__))
- self._osso_context = osso.Context(app_id, version, False)
- program = hildon.Program.get_instance()
- program.connect('notify::is-topmost', self._on_topmost_changed)
-
- # Hack for dbus. See:
- # https://garage.maemo.org/pipermail/pymaemo-developers/2010-April/001445.html
- # https://garage.maemo.org/pipermail/pymaemo-developers/2010-April/001454.html
- # https://garage.maemo.org/pipermail/pymaemo-developers/2010-April/thread.html
- # https://bugs.maemo.org/show_bug.cgi?id=8611
- #
- # If we use dbus.Bus.get_system() or dbus.SystemBus() then the
- # program fails whenever bluezwitch is installed. This could
- # also happen whenever another widget is using System Bus (sure?).
- #
- #V13 system_bus = dbus.Bus.get_system()
- dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
- busaddress='unix:path=/var/run/dbus/system_bus_socket'
- system_bus=dbus.bus.BusConnection(busaddress)
- self.system_bus=system_bus
-
- system_bus.add_signal_receiver(self._on_orientation_signal, \
- signal_name='sig_device_orientation_ind', \
- dbus_interface='com.nokia.mce.signal', \
- path='/com/nokia/mce/signal')
- system_bus.add_signal_receiver(self._on_keyboard_signal, \
- signal_name='Condition', \
- dbus_interface='org.freedesktop.Hal.Device', \
- path='/org/freedesktop/Hal/devices/platform_slide')
- self.set_mode(mode)
-
- def get_mode(self):
- """Get the currently-set rotation mode
-
- This will return one of three values: AUTOMATIC, ALWAYS or NEVER.
- """
- return self._mode
-
- def set_mode(self, new_mode):
- """Set the rotation mode
-
- You can set the rotation mode to AUTOMATIC (use hardware rotation
- info), ALWAYS (force portrait) and NEVER (force landscape).
- """
- if new_mode not in (self.AUTOMATIC, self.ALWAYS, self.NEVER):
- raise ValueError('Unknown rotation mode')
-
- if self._mode != new_mode:
- if self._mode == self.AUTOMATIC:
- # Remember the current "automatic" orientation for later
- self._last_dbus_orientation = self._orientation
- # Tell MCE that we don't need the accelerometer anymore
- self._send_mce_request(self._DISABLE_ACCEL)
-
- if new_mode == self.NEVER:
- self._orientation_changed(self._LANDSCAPE)
- elif new_mode == self.ALWAYS and \
- self._keyboard_state != self._KBD_OPEN:
- self._orientation_changed(self._PORTRAIT)
- elif new_mode == self.AUTOMATIC:
- # Restore the last-known "automatic" orientation
- self._orientation_changed(self._last_dbus_orientation)
- # Tell MCE that we need the accelerometer again
- self._send_mce_request(self._ENABLE_ACCEL)
-
- self._mode = new_mode
-
- def reset_mode(self):
- if self._mode==self.AUTOMATIC:
- self._send_mce_request(self._ENABLE_ACCEL)
- else:
- self._send_mce_request(self._DISABLE_ACCEL)
-
- def _send_mce_request(self, request):
- rpc = osso.Rpc(self._osso_context)
- rpc.rpc_run(self._MCE_SERVICE, \
- self._MCE_REQUEST_PATH, \
- self._MCE_REQUEST_IF, \
- request, \
- use_system_bus=True)
-
- def _on_topmost_changed(self, program, property_spec):
- # XXX: This seems to never get called on Fremantle(?)
- if self._mode == self.AUTOMATIC:
- if program.get_is_topmost():
- self._send_mce_request(self._ENABLE_ACCEL)
- else:
- self._send_mce_request(self._DISABLE_ACCEL)
-
- def _get_main_window(self):
- if self._main_window:
- # If we have gotten the main window as parameter, return it and
- # don't try "harder" to find another window using the stack
- return self._main_window
- else:
- # The main window is at the "bottom" of the window stack, and as
- # the list we get with get_windows() is sorted "topmost first", we
- # simply take the last item of the list to get our main window
- windows = self._stack.get_windows()
- if windows:
- return windows[-1]
- else:
- return None
-
- def _orientation_changed(self, orientation):
- if self._orientation == orientation:
- # Ignore repeated requests
- return
-
- flags = 0
-
- if orientation != self._LANDSCAPE:
- flags |= hildon.PORTRAIT_MODE_SUPPORT
-
- if orientation == self._PORTRAIT:
- flags |= hildon.PORTRAIT_MODE_REQUEST
-
- window = self._get_main_window()
- if window is not None and self.dontrotate==False:
- hildon.hildon_gtk_window_set_portrait_flags(window, flags)
-
- self._orientation = orientation
-
- self.on_orientation_changed(orientation)
-
- def on_orientation_changed(self, orientation):
- pass
-
- def _get_keyboard_state(self):
- # For sbox, if the device does not exist assume that it's closed
- try:
- return open(self.KBD_SLIDER).read().strip()
- except IOError:
- return self._KBD_CLOSED
-
- def _keyboard_state_changed(self):
- state = self._get_keyboard_state()
-
- if state == self._KBD_OPEN:
- self._orientation_changed(self._LANDSCAPE)
- elif state == self._KBD_CLOSED:
- if self._mode == self.AUTOMATIC:
- self._orientation_changed(self._last_dbus_orientation)
- elif self._mode == self.ALWAYS:
- self._orientation_changed(self._PORTRAIT)
-
- self._keyboard_state = state
-
- def _on_keyboard_signal(self, condition, button_name):
- if condition == 'ButtonPressed' and button_name == 'cover':
- self._keyboard_state_changed()
-
- def _on_orientation_signal(self, orientation, stand, face, x, y, z):
- if orientation in (self._PORTRAIT, self._LANDSCAPE):
- if self._mode == self.AUTOMATIC and \
- self._keyboard_state != self._KBD_OPEN:
- # Automatically set the rotation based on hardware orientation
- self._orientation_changed(orientation)
-
- # Save the current orientation for "automatic" mode later on
- self._last_dbus_orientation = orientation
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+++ /dev/null
-# -*- coding: utf-8 -*-
-#
-# gPodder - A media aggregator and podcast client
-# Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
-#
-# gPodder 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.
-#
-# gPodder 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/>.
-#
-
-import dbus
-import dbus.glib
-
-import hildon
-import osso
-
-# Replace this with your own gettext() functionality
-import gpodder
-_ = gpodder.gettext
-
-
-class FremantleRotation(object):
- """thp's screen rotation for Maemo 5
-
- Simply instantiate an object of this class and let it auto-rotate
- your StackableWindows depending on the device orientation.
-
- If you need to relayout a window, connect to its "configure-event"
- signal and measure the ratio of width/height and relayout for that.
-
- You can set the mode for rotation to AUTOMATIC (default), NEVER or
- ALWAYS with the set_mode() method.
- """
- AUTOMATIC, NEVER, ALWAYS = range(3)
-
- # Human-readable captions for the above constants
- MODE_CAPTIONS = (_('Automatic'), _('Landscape'), _('Portrait'))
-
- # Privately-used constants
- _PORTRAIT, _LANDSCAPE = ('portrait', 'landscape')
- _ENABLE_ACCEL = 'req_accelerometer_enable'
- _DISABLE_ACCEL = 'req_accelerometer_disable'
-
- # Defined in mce/dbus-names.h
- _MCE_SERVICE = 'com.nokia.mce'
- _MCE_REQUEST_PATH = '/com/nokia/mce/request'
- _MCE_REQUEST_IF = 'com.nokia.mce.request'
-
- # sysfs device name for the keyboard slider switch
- KBD_SLIDER = '/sys/devices/platform/gpio-switch/slide/state'
- _KBD_OPEN = 'open'
- _KBD_CLOSED = 'closed'
-
- def __init__(self, app_name, main_window=None, version='1.0', mode=0):
- """Create a new rotation manager
-
- app_name ... The name of your application (for osso.Context)
- main_window ... The root window (optional, hildon.StackableWindow)
- version ... The version of your application (optional, string)
- mode ... Initial mode for this manager (default: AUTOMATIC)
- """
- self._orientation = None
- self._main_window = main_window
- self._stack = hildon.WindowStack.get_default()
- self._mode = -1
- self._last_dbus_orientation = None
- self._keyboard_state = self._get_keyboard_state()
- app_id = '-'.join((app_name, self.__class__.__name__))
- self._osso_context = osso.Context(app_id, version, False)
- program = hildon.Program.get_instance()
- program.connect('notify::is-topmost', self._on_topmost_changed)
- system_bus = dbus.Bus.get_system()
- system_bus.add_signal_receiver(self._on_orientation_signal, \
- signal_name='sig_device_orientation_ind', \
- dbus_interface='com.nokia.mce.signal', \
- path='/com/nokia/mce/signal')
- system_bus.add_signal_receiver(self._on_keyboard_signal, \
- signal_name='Condition', \
- dbus_interface='org.freedesktop.Hal.Device', \
- path='/org/freedesktop/Hal/devices/platform_slide')
- self.set_mode(mode)
-
- def get_mode(self):
- """Get the currently-set rotation mode
-
- This will return one of three values: AUTOMATIC, ALWAYS or NEVER.
- """
- return self._mode
-
- def set_mode(self, new_mode):
- """Set the rotation mode
-
- You can set the rotation mode to AUTOMATIC (use hardware rotation
- info), ALWAYS (force portrait) and NEVER (force landscape).
- """
- if new_mode not in (self.AUTOMATIC, self.ALWAYS, self.NEVER):
- raise ValueError('Unknown rotation mode')
-
- if self._mode != new_mode:
- if self._mode == self.AUTOMATIC:
- # Remember the current "automatic" orientation for later
- self._last_dbus_orientation = self._orientation
- # Tell MCE that we don't need the accelerometer anymore
- self._send_mce_request(self._DISABLE_ACCEL)
-
- if new_mode == self.NEVER:
- self._orientation_changed(self._LANDSCAPE)
- elif new_mode == self.ALWAYS and \
- self._keyboard_state != self._KBD_OPEN:
- self._orientation_changed(self._PORTRAIT)
- elif new_mode == self.AUTOMATIC:
- # Restore the last-known "automatic" orientation
- self._orientation_changed(self._last_dbus_orientation)
- # Tell MCE that we need the accelerometer again
- self._send_mce_request(self._ENABLE_ACCEL)
-
- self._mode = new_mode
-
- def _send_mce_request(self, request):
- rpc = osso.Rpc(self._osso_context)
- rpc.rpc_run(self._MCE_SERVICE, \
- self._MCE_REQUEST_PATH, \
- self._MCE_REQUEST_IF, \
- request, \
- use_system_bus=True)
-
- def _on_topmost_changed(self, program, property_spec):
- # XXX: This seems to never get called on Fremantle(?)
- if self._mode == self.AUTOMATIC:
- if program.get_is_topmost():
- self._send_mce_request(self._ENABLE_ACCEL)
- else:
- self._send_mce_request(self._DISABLE_ACCEL)
-
- def _get_main_window(self):
- if self._main_window:
- # If we have gotten the main window as parameter, return it and
- # don't try "harder" to find another window using the stack
- return self._main_window
- else:
- # The main window is at the "bottom" of the window stack, and as
- # the list we get with get_windows() is sorted "topmost first", we
- # simply take the last item of the list to get our main window
- windows = self._stack.get_windows()
- if windows:
- return windows[-1]
- else:
- return None
-
- def _orientation_changed(self, orientation):
- if self._orientation == orientation:
- # Ignore repeated requests
- return
-
- flags = 0
-
- if orientation != self._LANDSCAPE:
- flags |= hildon.PORTRAIT_MODE_SUPPORT
-
- if orientation == self._PORTRAIT:
- flags |= hildon.PORTRAIT_MODE_REQUEST
-
- window = self._get_main_window()
- if window is not None:
- hildon.hildon_gtk_window_set_portrait_flags(window, flags)
-
- self._orientation = orientation
-
- def _get_keyboard_state(self):
- # For sbox, if the device does not exist assume that it's closed
- try:
- return open(self.KBD_SLIDER).read().strip()
- except IOError:
- return self._KBD_CLOSED
-
- def _keyboard_state_changed(self):
- state = self._get_keyboard_state()
-
- if state == self._KBD_OPEN:
- self._orientation_changed(self._LANDSCAPE)
- elif state == self._KBD_CLOSED:
- if self._mode == self.AUTOMATIC:
- self._orientation_changed(self._last_dbus_orientation)
- elif self._mode == self.ALWAYS:
- self._orientation_changed(self._PORTRAIT)
-
- self._keyboard_state = state
-
- def _on_keyboard_signal(self, condition, button_name):
- if condition == 'ButtonPressed' and button_name == 'cover':
- self._keyboard_state_changed()
-
- def _on_orientation_signal(self, orientation, stand, face, x, y, z):
- if orientation in (self._PORTRAIT, self._LANDSCAPE):
- if self._mode == self.AUTOMATIC and \
- self._keyboard_state != self._KBD_OPEN:
- # Automatically set the rotation based on hardware orientation
- self._orientation_changed(orientation)
-
- # Save the current orientation for "automatic" mode later on
- self._last_dbus_orientation = orientation
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-import gtk
-
-class Disconnector(object):
- def __init__(self):
- self.chandles=[]
- if isinstance(self, gtk.Widget):
- self.c(self, 'delete-event', self.slotDiscDeleteEvent)
-
- def c(self, obj, signal, slot, *args):
- h=obj.connect(signal, slot, *args)
- self.chandles.append((obj, h))
-
- return(h)
-
- def slotDiscDeleteEvent(self, sender, event):
-# print "sig-delete-event"
- self.dis_finish()
- return(False)
-
- def dis_finish(self, obj=None):
- """ Disconnect all signals. If obj is specified the only disconnect
- signals from that object """
- for i in self.chandles:
- if obj!=None and i[0]!=obj:
- continue
- #print i[0], i[1]
- i[0].disconnect(i[1])
-
- if obj==None:
- self.chandles=[]
- else:
- self.chandles=[a for a in self.chandles if a[0]!=obj]
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-# HACK
-# Add the current module's directory to sys.path to bypass
-# problems when running as widget.
-# Restore the path at the end of the imports
-import sys
-import os
-
-orig_path=sys.path[:]
-tmp_path=os.path.dirname( os.path.realpath( __file__ ) )
-sys.path.append(tmp_path)
-
-# End of hack
-
-import gtk
-import gobject
-import hildon
-from hildondesktop import *
-from gtk import gdk
-from math import pi
-import cairo
-import gconf
-import time
-
-from subprocess import Popen,PIPE
-
-from portrait import FremantleRotation
-import launcher
-from xdg.IconTheme import getIconPath
-from win_config import WinConfig
-
-import config
-import apps
-from icon import Icon
-from icongrid import IconGrid
-from sig import Disconnector
-
-# Restore path
-sys.path=orig_path
-
-# IconGrid must be before HomePluginItem for its connect()
-# and do_button_*() to override those of HomePluginItem
-class DrlaunchPlugin(IconGrid, HomePluginItem, FremantleRotation, Disconnector):
- def __init__(self):
- IconGrid.__init__(self)
- HomePluginItem.__init__(self)
- FremantleRotation.__init__(self, 'DrlaunchPlugin',
- mode=FremantleRotation.AUTOMATIC, dontrotate=True)
- Disconnector.__init__(self)
-
- self.winConfig=None
-
- self.gconf=gconf.client_get_default()
-
- self.set_settings(True)
-
- self.id=None
- self.config=None
-
- self.reset_mode()
-
- def get_id0(self):
- """If this is called from the constructor then the program
- core dumps """
- aid=self.get_applet_id()
-
- # Get desktop activity if D.A.M. is present
-
- act="/usr/bin/activity"
-
- if os.path.exists(act):
- r=Popen([act, "current"], stdout=PIPE).communicate()
- activity=r[0].strip()
- else:
- activity=""
-
- ret="%s-%s" % (aid, activity)
-
- return(ret)
-
- def get_id(self):
- if self.id==None:
- self.id=self.get_id0()
-
- return(self.id)
-
- def get_config(self):
- if self.config==None:
- id=self.get_id()
- self.config=config.Config(id)
-
- return(self.config)
-
- def get_desktop_orientation(self):
- """
- Return desktop orientation
-
- NOTE: This is the desktop orientation as it was introduced in CSSU.
- Not the device orientation.
-
- @return "portrait" or "landscape"
- """
-
- sw=gdk.screen_width()
- sh=gdk.screen_height()
-
- if sw>=sh:
- ret='landscape'
- else:
- ret='portrait'
-
- return(ret)
-
- def is_rotating_desktop(self):
- """
- Check whether the desktop will change to portrait mode, as
- added in CSSU.
-
- @return True/False
- """
-
- c=self.gconf
-
- # This returns False if the key doesn't exist
- ret=c.get_bool('/apps/osso/hildon-desktop/ui_can_rotate')
-
- return(ret)
-
- def do_realize(self):
- launcher.init()
- config=self.get_config()
- config.load()
-
- IconGrid.do_realize(self, config)
-
- self.setSize(config.getSize())
- self.reloadIcons()
-
- screen=self.get_screen()
- self.set_colormap(screen.get_rgba_colormap())
- self.set_app_paintable(True)
-
- self.c(self, 'show-settings', self.slot_show_settings)
- self.c(self, 'long-press', self.signalLongpress)
- self.c(self, 'click', self.signalClick)
- self.c(self, 'notify', self.signalNotify)
-
- HomePluginItem.do_realize(self)
-
- def on_orientation_changed(self, orientation):
- # Avoid bugs
- if orientation==None or len(orientation)==0:
- return
-
- # Get the first character of the string (l/p)
- o=orientation[0]
-
- # Get desktop orientation
- #do=self.get_desktop_orientation()
-
- # Is desktop rotation (per CSSU) enabled?
- rd=self.is_rotating_desktop()
-
- #print "desktop: %s / %s, device: %s" % (do, rd, o)
-
- # In case of a rotating desktop, force orientation to be
- # 'landscape'
- if rd:
- o='l'
-
- self.setMode(o)
-# self.queue_draw()
-
- def do_expose_event(self, event):
- IconGrid.do_expose_event(self, event)
- HomePluginItem.do_expose_event(self, event)
- self.reset_mode()
-
- def slot_show_settings(self, dt):
- if self.winConfig!=None:
- # Doesn't work
- # self.winConfig.show_all()
- return
-
- s=WinConfig(self.get_config())
- s.show_all()
- #s.c(s, 'delete-event', self.slotConfigDestroy)
- self.c(s, 'delete-event', self.slotConfigDestroy)
- #s.connect('destroy', self.slotConfigDestroy)
- self.winConfig=s
-
- def slotConfigDestroy(self, sender, event):
-# print "Sender:", sender
- dt=sender.getData()
-
- # Disconnect signals for that object in order to be deleted
- self.dis_finish(self.winConfig)
- #self.winConfig.finish()
- #self.winConfig.destroy()
-
- self.winConfig=None
-
- cfg=self.get_config()
-
- cfg.setSize(dt['size'])
- cfg.setApps(dt['apps'])
- cfg.setIndiv(dt['indiv'])
- cfg.setLongpress(dt['longpress'])
- cfg.setAnimate(dt['animate'])
- cfg.setNoBg(dt['nobg'])
- cfg.setThemeBg(dt['themebg'])
- cfg.setIconSize(dt['iconsize'])
- cfg.setIconPadding(dt['iconpadding'])
- cfg.setIconMargin(dt['iconmargin'])
- cfg.save()
-
- # Resize widget
- self.icons.resizeMax()
- self.setSize(dt['size'])
- self.reloadIcons()
-
- # Free memory that is used for animations if animations are disabled
- if not dt['animate']:
- self.clearAnimationCache()
-
- # Free memory of backgrounds in case they changed
- self.clearBgCache()
-
- self.queue_draw()
-
-# print "slot-config-destroy-end"
-
- return(False)
-
- def handle_click(self, sender, icon):
- """ common handler for longpress and click """
- if icon.appname!=None and icon.appname!='':
- launcher.launch(icon.appname)
-
- def signalLongpress(self, sender, icon):
- self.handle_click(sender, icon)
-
- def signalClick(self, sender, icon):
- config=self.get_config()
-
- if not config.getLongpress():
- self.handle_click(sender, icon)
-
- def signalNotify(self, sender, property):
- if property.name=='is-on-current-desktop':
- v=self.get_property(property.name)
- if v and self.draw_pending:
- self.queue_draw()
-
- def resize2(self):
- config=self.get_config()
-
- w=(self.size[0] * config.iconsize) + \
- (self.size[0] * config.getIconSpace())
- h=(self.size[1] * config.iconsize) + \
- (self.size[1] * config.getIconSpace())
- self.set_size_request(w, h)
- self.resize(w, h)
-
- def setSize(self, size):
- IconGrid.setSize(self, size)
- self.resize2()
-
-hd_plugin_type = DrlaunchPlugin
-
-if __name__=="__main__":
- gobject.type_register(hd_plugin_type)
- obj=gobject.new(hd_plugin_type, plugin_id="plugin_id")
- obj.show_all()
- gtk.main()
-
-
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-#!/usr/bin/env python
-# coding=UTF-8
-#
-# Copyright (C) 2010 Stefanos Harhalakis
-#
-# This file is part of wifieye.
-#
-# wifieye 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.
-#
-# wifieye 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 wifieye. If not, see <http://www.gnu.org/licenses/>.
-#
-# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
-
-__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
-
-import gtk
-import gobject
-import hildon
-import time
-import copy
-
-from hildon import StackableWindow
-#from portrait import FremantleRotation
-#from xdg.IconTheme import getIconPath
-
-import config
-import apps
-from icon import Icon, getIcon
-from iconw import IconWidget
-from icongrid import IconGridWidget
-from about import DlgAbout
-from portrait import FremantleRotation
-from sig import Disconnector
-
-class DialogIconSize(gtk.Dialog):
- def __init__(self, config):
- gtk.Dialog.__init__(self, "Adjust icon size")
-
- # Operate on copy of config
- self.config=copy.copy(config)
- # And keep another copy to be able to undo
- self.config0=copy.copy(config)
-
- vbox=self.vbox
-
- self.pa=hildon.PannableArea()
- vbox.add(self.pa)
- self.pa.set_property('mov-mode', hildon.MOVEMENT_MODE_VERT)
-
- vbox=gtk.VBox()
- self.pa.add_with_viewport(vbox)
- self.pa.set_size_request(-1, 1000)
-
- self.labelDim=gtk.Label("koko")
- vbox.add(self.labelDim)
-
- vbox.add(gtk.Label("Icon size:"))
- scale=gtk.HScale()
- scale.set_digits(0)
- scale.set_value_pos(gtk.POS_RIGHT)
- scale.set_range(*self.config.getIconSizeRange())
- scale.set_increments(8, 16)
- self.scaleIconSize=scale
- vbox.add(scale)
-
- hbox=gtk.HBox()
- vbox.add(hbox)
-
- vbox2=gtk.VBox()
- vbox2.add(gtk.Label("Padding:"))
- scale=gtk.HScale()
- scale.set_digits(0)
- scale.set_value_pos(gtk.POS_RIGHT)
- scale.set_range(*self.config.getIconPaddingRange())
- scale.set_increments(2,2)
- self.scaleIconPadding=scale
- vbox2.add(scale)
- hbox.add(vbox2)
-
- vbox2=gtk.VBox()
- vbox2.add(gtk.Label("Margin:"))
- scale=gtk.HScale()
- scale.set_digits(0)
- scale.set_value_pos(gtk.POS_RIGHT)
- scale.set_range(*self.config.getIconMarginRange())
- scale.set_increments(2,2)
- self.scaleIconMargin=scale
- vbox2.add(scale)
- hbox.add(vbox2)
-
- self.icon=IconWidget(False, self.config)
- self.icon.setApp('osso-addressbook')
- vbox.add(self.icon)
-
- self.scaleIconSize.set_value(self.config.getIconSize())
- self.scaleIconPadding.set_value(self.config.getIconPadding())
- self.scaleIconMargin.set_value(self.config.getIconMargin())
-
- # Set thos after settings initial values to avoid unwanted effects
- self.scaleIconSize.connect('value-changed', self.slotSzChangeSize)
- self.scaleIconPadding.connect('value-changed', self.slotSzChangePadding)
- self.scaleIconMargin.connect('value-changed', self.slotSzChangeMargin)
-
- hbox=gtk.HBox()
- vbox.add(hbox)
-
- but=hildon.Button(
- gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
- hildon.BUTTON_ARRANGEMENT_VERTICAL)
- but.set_title("OK")
- but.connect("clicked", self.slotButtonOK)
- #hbox.add(but)
- self.action_area.pack_start(but)
- self.buttonOK=but
-
- but=hildon.Button(
- gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
- hildon.BUTTON_ARRANGEMENT_VERTICAL)
- but.set_title("Undo")
- but.connect("clicked", self.slotButtonUndo)
- #hbox.add(but)
- self.action_area.pack_start(but)
- self.buttonUndo=but
-
- but=hildon.Button(
- gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
- hildon.BUTTON_ARRANGEMENT_VERTICAL)
- but.set_title("Restore defaults")
- but.connect("clicked", self.slotButtonDefaults)
- #hbox.add(but)
- self.action_area.pack_start(but)
- self.buttonDefaults=but
-
- self.show_all()
-
- self.recalc()
-
- def setWH(self, w, h):
- self.labelDim.set_text("Grid size: %d x %d icons" % (w, h))
- #self.labelWidth.set_text("Width: %d icons" % w)
- #self.labelHeight.set_text("Height: %d icons" % h)
-
- def recalc(self):
- maxsz=self.config.getMaxSize()
- self.setWH(maxsz[0], maxsz[1])
- self.icon.refresh()
-
- def getIconSize(self):
- return(self.scaleIconSize.get_value())
-
- def setIconSize(self, iconsize):
- self.scaleIconSize.set_value(iconsize)
-
- def getIconPadding(self):
- return(self.scaleIconPadding.get_value())
-
- def setIconPadding(self, pad):
- self.scaleIconPadding.set_value(pad)
-
- def getIconMargin(self):
- return(self.scaleIconMargin.get_value())
-
- def setIconMargin(self, margin):
- self.scaleIconMargin.set_value(margin)
-
- def slotSzChangeSize(self, sender):
- self.config.setIconSize(int(self.getIconSize()))
- self.recalc()
-
- def slotSzChangeMargin(self, sender):
- self.config.setIconMargin(int(self.getIconMargin()))
- self.recalc()
-
- def slotSzChangePadding(self, sender):
- self.config.setIconPadding(int(self.getIconPadding()))
- self.recalc()
-
- def slotButtonUndo(self, sender):
- self.config=copy.copy(self.config0)
- self.recalc()
- self.resetSliders()
-
- def slotButtonDefaults(self, sender):
- self.config.setDefaultSizes()
- self.recalc()
- self.resetSliders()
-
- def slotButtonOK(self, sender):
- self.response(gtk.RESPONSE_OK)
-
- def resetSliders(self):
- self.scaleIconSize.set_value(self.config.getIconSize())
- self.scaleIconPadding.set_value(self.config.getIconPadding())
- self.scaleIconMargin.set_value(self.config.getIconMargin())
-
- def getData(self):
- ret={
- 'size': self.config.getIconSize(),
- 'padding': self.config.getIconPadding(),
- 'margin': self.config.getIconMargin()
- }
-
- return(ret)
-
-
-class WinConfig(StackableWindow, Disconnector): #, FremantleRotation):
- def __init__(self, config, *args):
- StackableWindow.__init__(self)
- Disconnector.__init__(self)
-# FremantleRotation.__init__(self, "DrlaunchPlugin",
-# mode=FremantleRotation.AUTOMATIC)
-
- self.config=copy.copy(config)
-
- self.setupUi()
-
-# h=self.c(self, 'delete-event', self.slotDeleteEvent)
-
-# This is a nice test. If it is displayed then the window is actually
-# destroyed and there is no memory leak
-# def __del__(self):
-# print "wc-del"
-
- def setupUi(self):
- """
- self.pa Main Pannable Area
- self.col1 A VBox for the first column
- self.col2 A VBox for the second column
- self.w_igw The IGW in an alignment
- """
- self.set_title("DrLaunch v" + config.version)
-
- self.pa=hildon.PannableArea()
-# self.add(self.pa)
- self.pa.set_property('mov-mode', hildon.MOVEMENT_MODE_HORIZ)
-
- self.add(self.pa)
-
-#1 hbox=gtk.HBox()
-#1 self.pa.add_with_viewport(hbox)
-
- # Add the first column of options
- al=gtk.Alignment(yscale=1)
-#1 hbox.add(al)
-
- vbox=gtk.VBox()
- al.add(vbox)
- self.col1=al
-
- maxsz=self.config.getMaxSize()
-
- self.maxmaxsz=(16,16)
-
- # ----------------------------------------------
- vbox.add(gtk.Label('Width:'))
-
- hbox2=gtk.HBox()
- vbox.add(hbox2)
-
- self.butsSizeX=[]
- for i in xrange(self.maxmaxsz[0]):
- but=hildon.GtkToggleButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
- but.set_label("%s" % (i+1,))
- self.c(but, 'toggled', self.slotButtonSizeX, i)
-
- self.butsSizeX.append(but)
-
- hbox2.add(but)
-
- # ----------------------------------------------
- vbox.add(gtk.Label('Height:'))
-
- hbox2=gtk.HBox()
- vbox.add(hbox2)
-
- self.butsSizeY=[]
- for i in xrange(self.maxmaxsz[1]):
- but=hildon.GtkToggleButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
- but.set_label("%s" % (i+1,))
- self.c(but, 'toggled', self.slotButtonSizeY, i)
-
- self.butsSizeY.append(but)
-
- hbox2.add(but)
-
- vbox2=gtk.VBox()
-
- al=gtk.Alignment(xalign=0, yalign=1, xscale=1)
- al.add(vbox2)
- self.col11=al
-
- vbox.add(al)
- vbox=vbox2
-
- but=hildon.CheckButton(
- gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
- but.set_label("Rotate icons individually")
- self.c(but, 'toggled', self.slotButtonRotateIndividually)
- self.buttonRotateIndividually=but
- vbox.add(but)
-
- but=hildon.CheckButton(
- gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
- but.set_label("Require long press")
-# but.connect('toggled', self.slotButtonLongpress)
- self.buttonRequireLongpress=but
- vbox.add(but)
-
- but=hildon.CheckButton(
- gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
- but.set_label("Animate rotation")
- self.buttonAnimateRotation=but
- vbox.add(but)
-
- # -----------------------------------------------
- # Second column of options
- vbox=gtk.VBox()
-
- al=gtk.Alignment(xalign=0, yalign=1, xscale=1)
- al.add(vbox)
- self.col2=al
-
- but=hildon.CheckButton(
- gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
- but.set_label("Theme background")
- but.connect('toggled', self.slotButtonThemeBackground)
- self.buttonThemeBackground=but
- vbox.add(but)
-
- but=hildon.CheckButton(
- gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT)
- but.set_label("No background")
- self.c(but, 'toggled', self.slotButtonNoBackground)
- self.buttonNoBackground=but
- vbox.add(but)
-
- but=hildon.Button(
- gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
- hildon.BUTTON_ARRANGEMENT_VERTICAL)
- but.set_label("Adjust icon size")
- self.c(but, 'clicked', self.slotButtonIconSize)
- self.buttonIconSize=but
- vbox.add(but)
-
-#1 hbox.add(al)
- but=hildon.Button(
- gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
- hildon.BUTTON_ARRANGEMENT_VERTICAL)
- but.set_title("About")
- self.c(but, "clicked", self.slotButtonAbout)
- vbox.add(but)
- self.buttonAbout=but
-
- # -----------------------------------------------
- # Add the icongrid
- self.igw=IconGridWidget(True, self.config, False)
-# self.igw.do_realize()
-# self.igw.setSize(config.getSize())
- al=gtk.Alignment(xalign=0, xscale=0)
- al.add(self.igw)
- al.set_padding(0, 0, 20, 0)
- self.w_igw=al
-#1 hbox.add(al)
-
- self.igw.connect('long-press', self.slotLongpress)
- self.igw.connect('click', self.slotLongpress)
-
- self.ignore_toggle=False
-
- self.setSize(self.config.getSize())
- self.setIndiv(self.config.getIndiv())
- self.setLongpress(self.config.getLongpress())
- self.setAnimate(self.config.getAnimate())
- self.setNoBg(self.config.getNoBg())
- self.setThemeBg(self.config.getThemeBg())
-# self.setIconSize(self.config.getIconSize())
-# self.setIconPadding(self.config.getIconPadding())
-# self.setIconMargin(self.config.getIconMargin())
-
- hbox=gtk.HBox()
- hbox.add(self.col1)
- hbox.add(self.col2)
- hbox.add(self.w_igw)
- self.pa.add_with_viewport(hbox)
-
- self.adjustMaxSize()
-
-# def setupUi(self, orientation):
-#
-# self.setupUi0()
-#
-# hbox=gtk.HBox()
-#
-# if orientation=='l':
-# hbox.add(self.col1)
-# hbox.add(self.col2)
-# hbox.add(self.w_igw)
-# else:
-# vbox=gtk.VBox()
-# hbox.add(vbox)
-# vbox.add(self.col1)
-# vbox.add(self.col2)
-# hbox.add(self.w_igw)
-#
-# self.pa.add_with_viewport(hbox)
-#
-# if self.get_child()!=None:
-# self.remove(self.get_child())
-# self.add(self.pa)
-# self.pa.show_all()
-
- def adjustMaxSize(self):
- sz=self.config.getMaxSize()
- maxsz=self.maxmaxsz
-
- for x in xrange(maxsz[0]):
- if x<sz[0]:
- self.butsSizeX[x].show()
- else:
- self.butsSizeX[x].hide()
-
- for y in xrange(maxsz[1]):
- if y<sz[1]:
- self.butsSizeY[y].show()
- else:
- self.butsSizeY[y].hide()
-
- osz=self.getSize()
- nsz=[osz[0], osz[1]]
-
- if osz[0]>sz[0]:
- nsz[0]=sz[0]
- if osz[1]>sz[1]:
- nsz[1]=sz[1]
- self.setSize(nsz)
-
- self.setIndiv(self.getIndiv())
-
- def setLayoutPortrait(self):
- print "lo: p"
- hbox=gtk.HBox()
-
- vbox=gtk.VBox()
- hbox.add(vbox)
- self.col1.reparent(vbox)
- self.col2.reparent(vbox)
- self.w_igw.reparent(hbox)
-
- r=self.pa.get_children()[0]
- self.pa.remove(r)
- r.destroy()
- self.pa.add_with_viewport(hbox)
-
- self.pa.show_all()
-
- def setLayoutLandscape(self):
- print "lo: l"
- hbox=gtk.HBox()
-
- self.col1.reparent(hbox)
- self.col2.reparent(hbox)
- self.w_igw.reparent(hbox)
-
- r=self.pa.get_children()[0]
- self.pa.remove(r)
- r.destroy()
- self.pa.add_with_viewport(hbox)
-
- self.pa.show_all()
-
- def on_orientation_changed(self, orientation):
- # This is disabled for now since I've not found any reliable
- # way for supporting orientation changes (#$#%$#*&% GTK)
- return
-
- print "orch:", orientation
- if orientation=='portrait':
- self.setLayoutPortrait()
- else:
- self.setLayoutLandscape()
-
-# def slotDeleteEvent(self, sender, event):
-# print "wc-del-event"
-# return(False)
-
- def slotLongpress(self, sender, icon):
- self.doConfig(icon)
-
- def slotButtonSizeX(self, sender, id):
- if self.getIndiv():
- old=self.getSize()
- sz=(id+1, old[1])
- else:
- sz=(id+1, id+1)
-
- self.setSize(sz)
-
- def slotButtonSizeY(self, sender, id):
- old=self.getSize()
- sz=(old[0], id+1)
- self.setSize(sz)
-
- def slotButtonRotateIndividually(self, sender):
- but=self.buttonRotateIndividually
- self.setIndiv(but.get_active())
-
- def slotButtonNoBackground(self, sender):
- nobg=self.getNoBg()
- self.setNoBg(nobg)
-
- def slotButtonThemeBackground(self, sender):
- themebg=self.getThemeBg()
- self.setThemeBg(themebg)
-
- def slotButtonAbout(self, sender):
- DlgAbout.present2(self)
-
- def slotButtonIconSize(self, sender):
- dlg=DialogIconSize(self.config)
- ret=dlg.run()
- dt=dlg.getData()
- dlg.destroy()
-
- if ret!=gtk.RESPONSE_OK:
- return
-
- self.config.setIconSize(dt['size'])
- self.config.setIconMargin(dt['margin'])
- self.config.setIconPadding(dt['padding'])
- self.igw.reconfig()
- self.adjustMaxSize()
- self.queue_draw()
-
- def show_all(self):
- StackableWindow.show_all(self)
-# return
- self.adjustMaxSize()
- self.queue_draw()
-
-# def slotScaleIconSzChange(self, sender):
-# return
-# self.config.setIconSize(self.getIconSize())
-# self.config.setIconMargin(self.getIconMargin())
-# self.config.setIconPadding(self.getIconPadding())
-# self.igw.reconfig()
-# self.adjustMaxSize()
-# self.queue_draw()
-
-# def slotButtonLongpress(self, sender):
-# but=self.buttonRequireLongpress
-# self.set
-
- def setSize(self, sz):
- if self.ignore_toggle:
- return
-
- self.ignore_toggle=True
-
- maxsz=self.config.getMaxSize()
-
- id=sz[0]-1
-
- for i in xrange(maxsz[0]):
- but=self.butsSizeX[i]
- but.set_active(i==id)
-
- id=sz[1]-1
-
- for i in xrange(maxsz[1]):
- but=self.butsSizeY[i]
- but.set_active(i==id)
-
- self.ignore_toggle=False
-
- self.igw.setSize(sz)
-
- self.igw.queue_draw()
- self.queue_draw()
-
- def getSize(self):
- return(self.igw.getSize())
-
- def getIndiv(self):
- ret=self.buttonRotateIndividually.get_active()
-
- return(ret)
-
- def setIndiv(self, indiv):
- if indiv:
- for i in self.butsSizeY:
- i.set_sensitive(True)
- for i in self.butsSizeX:
- i.set_sensitive(True)
-
- else:
- sz=self.config.getMaxSize()
-
- for i in self.butsSizeY:
- i.set_sensitive(False)
-
- cnt=0
- for i in self.butsSizeX:
- cnt+=1
- # Height is always the narrower, so use that as a limit
- if cnt>sz[1]:
- i.set_sensitive(False)
- else:
- i.set_sensitive(True)
-
- sz=self.getSize()
- szx=sz[0]
- if szx>sz[1]:
- szx=sz[1]
- self.setSize((szx, szx))
-
- self.buttonRotateIndividually.set_active(indiv)
-
- def setLongpress(self, lp):
- self.buttonRequireLongpress.set_active(lp)
-
- def setAnimate(self, ar):
- self.buttonAnimateRotation.set_active(ar)
-
- def getNoBg(self):
- ret=self.buttonNoBackground.get_active()
- return(ret)
-
- def setNoBg(self, nobg):
- self.buttonNoBackground.set_active(nobg)
-
- self.buttonThemeBackground.set_sensitive(not nobg)
- self.config.setNoBg(nobg)
- self.igw.reconfig()
-
- def getThemeBg(self):
- ret=self.buttonThemeBackground.get_active()
- return(ret)
-
- def setThemeBg(self, themebg):
- self.buttonThemeBackground.set_active(themebg)
- self.config.setThemeBg(themebg)
- self.igw.reconfig()
-
- def doConfig(self, icon):
- aps=apps.scan()
-
- lst=[aps[x]['name'] for x in aps]
- lst.sort()
-
- dialog=gtk.Dialog('App select', None,
- gtk.DIALOG_DESTROY_WITH_PARENT, buttons=())
-
- selector=hildon.TouchSelectorEntry(text=True)
- selector.set_column_selection_mode(
- hildon.TOUCH_SELECTOR_SELECTION_MODE_SINGLE)
-
- dialog.vbox.pack_start(selector, True, True, 0)
- dialog.set_size_request(0,900)
- dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
- dialog.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
-
- selector.append_text('None')
-
- idx=0
- cnt=1
- for app in lst:
- if app==None:
- continue
- selector.append_text(app)
- if icon.appname!=None and aps[icon.appname]['name']==app:
- idx=cnt
- cnt+=1
-
- selector.set_active(0, idx)
-
- dialog.show_all()
-
- app=None
-
- r=dialog.run()
-
- if r==gtk.RESPONSE_OK:
- idx2=selector.get_active(0)
- if idx2<1:
- app=None
- else:
- cur=lst[idx2-1]
- for i in aps:
- if aps[i]['name']==cur:
- app=aps[i]
- break
- if app!=None:
- app['icon2']=getIcon(app['icon'], self.config.getIconSize())
- else:
- app={
- 'id': None,
- 'icon2': None,
- }
- icon.setApp(app)
-
- dialog.destroy()
-
-# def finish(self):
-# print "wc-finish"
-# self.igw=None
-#
-# self.dis_finish()
-
- def getData(self):
- szx=0
- szy=0
- for but in self.butsSizeX:
- szx+=1
- if but.get_active()==True:
- break
-
- for but in self.butsSizeY:
- szy+=1
- if but.get_active()==True:
- break
-
- if self.getIndiv():
- sz=(szx, szy)
- else:
- sz=(szx, szx)
-
- wapps={}
-
- for x in xrange(sz[0]):
- for y in xrange(sz[1]):
- ico=self.igw.get(x,y)
- k=(x,y)
- wapps[k]=ico.appname
-
- indiv=self.buttonRotateIndividually.get_active()
- lp=self.buttonRequireLongpress.get_active()
- ar=self.buttonAnimateRotation.get_active()
- nobg=self.buttonNoBackground.get_active()
- themebg=self.buttonThemeBackground.get_active()
-
- ret={
- 'size': sz,
- 'apps': wapps,
- 'indiv': indiv,
- 'longpress': lp,
- 'animate': ar,
- 'nobg': nobg,
- 'themebg': themebg,
- 'iconsize': self.config.getIconSize(),
- 'iconpadding': self.config.getIconPadding(),
- 'iconmargin': self.config.getIconMargin(),
- }
-
- return(ret)
-
-if __name__=="__main__":
- win=WinConfig()
- win.connect('delete-event', gtk.main_quit)
-
- win.show_all()
- gtk.main()
-
-
-
-# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
-
+++ /dev/null
-"""
-This module is based on a rox module (LGPL):
-
-http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/basedir.py?rev=1.9&view=log
-
-The freedesktop.org Base Directory specification provides a way for
-applications to locate shared data and configuration:
-
- http://standards.freedesktop.org/basedir-spec/
-
-(based on version 0.6)
-
-This module can be used to load and save from and to these directories.
-
-Typical usage:
-
- from rox import basedir
-
- for dir in basedir.load_config_paths('mydomain.org', 'MyProg', 'Options'):
- print "Load settings from", dir
-
- dir = basedir.save_config_path('mydomain.org', 'MyProg')
- print >>file(os.path.join(dir, 'Options'), 'w'), "foo=2"
-
-Note: see the rox.Options module for a higher-level API for managing options.
-"""
-
-from __future__ import generators
-import os
-
-_home = os.environ.get('HOME', '/')
-xdg_data_home = os.environ.get('XDG_DATA_HOME',
- os.path.join(_home, '.local', 'share'))
-
-xdg_data_dirs = [xdg_data_home] + \
- os.environ.get('XDG_DATA_DIRS', '/usr/local/share:/usr/share').split(':')
-
-xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
- os.path.join(_home, '.config'))
-
-xdg_config_dirs = [xdg_config_home] + \
- os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':')
-
-xdg_cache_home = os.environ.get('XDG_CACHE_HOME',
- os.path.join(_home, '.cache'))
-
-xdg_data_dirs = filter(lambda x: x, xdg_data_dirs)
-xdg_config_dirs = filter(lambda x: x, xdg_config_dirs)
-
-def save_config_path(*resource):
- """Ensure $XDG_CONFIG_HOME/<resource>/ exists, and return its path.
- 'resource' should normally be the name of your application. Use this
- when SAVING configuration settings. Use the xdg_config_dirs variable
- for loading."""
- resource = os.path.join(*resource)
- assert not resource.startswith('/')
- path = os.path.join(xdg_config_home, resource)
- if not os.path.isdir(path):
- os.makedirs(path, 0700)
- return path
-
-def save_data_path(*resource):
- """Ensure $XDG_DATA_HOME/<resource>/ exists, and return its path.
- 'resource' is the name of some shared resource. Use this when updating
- a shared (between programs) database. Use the xdg_data_dirs variable
- for loading."""
- resource = os.path.join(*resource)
- assert not resource.startswith('/')
- path = os.path.join(xdg_data_home, resource)
- if not os.path.isdir(path):
- os.makedirs(path)
- return path
-
-def load_config_paths(*resource):
- """Returns an iterator which gives each directory named 'resource' in the
- configuration search path. Information provided by earlier directories should
- take precedence over later ones (ie, the user's config dir comes first)."""
- resource = os.path.join(*resource)
- for config_dir in xdg_config_dirs:
- path = os.path.join(config_dir, resource)
- if os.path.exists(path): yield path
-
-def load_first_config(*resource):
- """Returns the first result from load_config_paths, or None if there is nothing
- to load."""
- for x in load_config_paths(*resource):
- return x
- return None
-
-def load_data_paths(*resource):
- """Returns an iterator which gives each directory named 'resource' in the
- shared data search path. Information provided by earlier directories should
- take precedence over later ones."""
- resource = os.path.join(*resource)
- for data_dir in xdg_data_dirs:
- path = os.path.join(data_dir, resource)
- if os.path.exists(path): yield path
+++ /dev/null
-"""
-Functions to configure Basic Settings
-"""
-
-language = "C"
-windowmanager = None
-icon_theme = "highcolor"
-icon_size = 48
-cache_time = 5
-root_mode = False
-
-def setWindowManager(wm):
- global windowmanager
- windowmanager = wm
-
-def setIconTheme(theme):
- global icon_theme
- icon_theme = theme
- import xdg.IconTheme
- xdg.IconTheme.themes = []
-
-def setIconSize(size):
- global icon_size
- icon_size = size
-
-def setCacheTime(time):
- global cache_time
- cache_time = time
-
-def setLocale(lang):
- import locale
- lang = locale.normalize(lang)
- locale.setlocale(locale.LC_ALL, lang)
- import xdg.Locale
- xdg.Locale.update(lang)
-
-def setRootMode(boolean):
- global root_mode
- root_mode = boolean
+++ /dev/null
-"""
-Complete implementation of the XDG Desktop Entry Specification Version 0.9.4
-http://standards.freedesktop.org/desktop-entry-spec/
-
-Not supported:
-- Encoding: Legacy Mixed
-- Does not check exec parameters
-- Does not check URL's
-- Does not completly validate deprecated/kde items
-- Does not completly check categories
-"""
-
-from xdg.IniFile import *
-from xdg.BaseDirectory import *
-import os.path
-
-class DesktopEntry(IniFile):
- "Class to parse and validate DesktopEntries"
-
- defaultGroup = 'Desktop Entry'
-
- def __init__(self, filename=None):
- self.content = dict()
- if filename and os.path.exists(filename):
- self.parse(filename)
- elif filename:
- self.new(filename)
-
- def __str__(self):
- return self.getName()
-
- def parse(self, file):
- IniFile.parse(self, file, ["Desktop Entry", "KDE Desktop Entry"])
-
- # start standard keys
- def getType(self):
- return self.get('Type')
- """ @deprecated, use getVersionString instead """
- def getVersion(self):
- return self.get('Version', type="numeric")
- def getVersionString(self):
- return self.get('Version')
- def getName(self):
- return self.get('Name', locale=True)
- def getGenericName(self):
- return self.get('GenericName', locale=True)
- def getNoDisplay(self):
- return self.get('NoDisplay', type="boolean")
- def getComment(self):
- return self.get('Comment', locale=True)
- def getIcon(self):
- return self.get('Icon', locale=True)
- def getHidden(self):
- return self.get('Hidden', type="boolean")
- def getOnlyShowIn(self):
- return self.get('OnlyShowIn', list=True)
- def getNotShowIn(self):
- return self.get('NotShowIn', list=True)
- def getTryExec(self):
- return self.get('TryExec')
- def getExec(self):
- return self.get('Exec')
- def getPath(self):
- return self.get('Path')
- def getTerminal(self):
- return self.get('Terminal', type="boolean")
- """ @deprecated, use getMimeTypes instead """
- def getMimeType(self):
- return self.get('MimeType', list=True, type="regex")
- def getMimeTypes(self):
- return self.get('MimeType', list=True)
- def getCategories(self):
- return self.get('Categories', list=True)
- def getStartupNotify(self):
- return self.get('StartupNotify', type="boolean")
- def getStartupWMClass(self):
- return self.get('StartupWMClass')
- def getURL(self):
- return self.get('URL')
- # end standard keys
-
- # start kde keys
- def getServiceTypes(self):
- return self.get('ServiceTypes', list=True)
- def getDocPath(self):
- return self.get('DocPath')
- def getKeywords(self):
- return self.get('Keywords', list=True, locale=True)
- def getInitialPreference(self):
- return self.get('InitialPreference')
- def getDev(self):
- return self.get('Dev')
- def getFSType(self):
- return self.get('FSType')
- def getMountPoint(self):
- return self.get('MountPoint')
- def getReadonly(self):
- return self.get('ReadOnly', type="boolean")
- def getUnmountIcon(self):
- return self.get('UnmountIcon', locale=True)
- # end kde keys
-
- # start deprecated keys
- def getMiniIcon(self):
- return self.get('MiniIcon', locale=True)
- def getTerminalOptions(self):
- return self.get('TerminalOptions')
- def getDefaultApp(self):
- return self.get('DefaultApp')
- def getProtocols(self):
- return self.get('Protocols', list=True)
- def getExtensions(self):
- return self.get('Extensions', list=True)
- def getBinaryPattern(self):
- return self.get('BinaryPattern')
- def getMapNotify(self):
- return self.get('MapNotify')
- def getEncoding(self):
- return self.get('Encoding')
- def getSwallowTitle(self):
- return self.get('SwallowTitle', locale=True)
- def getSwallowExec(self):
- return self.get('SwallowExec')
- def getSortOrder(self):
- return self.get('SortOrder', list=True)
- def getFilePattern(self):
- return self.get('FilePattern', type="regex")
- def getActions(self):
- return self.get('Actions', list=True)
- # end deprecated keys
-
- # desktop entry edit stuff
- def new(self, filename):
- if os.path.splitext(filename)[1] == ".desktop":
- type = "Application"
- elif os.path.splitext(filename)[1] == ".directory":
- type = "Directory"
- else:
- raise ParsingError("Unknown extension", filename)
-
- self.content = dict()
- self.addGroup(self.defaultGroup)
- self.set("Type", type)
- self.filename = filename
- # end desktop entry edit stuff
-
- # validation stuff
- def checkExtras(self):
- # header
- if self.defaultGroup == "KDE Desktop Entry":
- self.warnings.append('[KDE Desktop Entry]-Header is deprecated')
-
- # file extension
- if self.fileExtension == ".kdelnk":
- self.warnings.append("File extension .kdelnk is deprecated")
- elif self.fileExtension != ".desktop" and self.fileExtension != ".directory":
- self.warnings.append('Unknown File extension')
-
- # Type
- try:
- self.type = self.content[self.defaultGroup]["Type"]
- except KeyError:
- self.errors.append("Key 'Type' is missing")
-
- # Name
- try:
- self.name = self.content[self.defaultGroup]["Name"]
- except KeyError:
- self.errors.append("Key 'Name' is missing")
-
- def checkGroup(self, group):
- # check if group header is valid
- if not (group == self.defaultGroup \
- or re.match("^\Desktop Action [a-zA-Z]+\$", group) \
- or (re.match("^\X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group)):
- self.errors.append("Invalid Group name: %s" % group)
- else:
- #OnlyShowIn and NotShowIn
- if self.content[group].has_key("OnlyShowIn") and self.content[group].has_key("NotShowIn"):
- self.errors.append("Group may either have OnlyShowIn or NotShowIn, but not both")
-
- def checkKey(self, key, value, group):
- # standard keys
- if key == "Type":
- if value == "ServiceType" or value == "Service" or value == "FSDevice":
- self.warnings.append("Type=%s is a KDE extension" % key)
- elif value == "MimeType":
- self.warnings.append("Type=MimeType is deprecated")
- elif not (value == "Application" or value == "Link" or value == "Directory"):
- self.errors.append("Value of key 'Type' must be Application, Link or Directory, but is '%s'" % value)
-
- if self.fileExtension == ".directory" and not value == "Directory":
- self.warnings.append("File extension is .directory, but Type is '%s'" % value)
- elif self.fileExtension == ".desktop" and value == "Directory":
- self.warnings.append("Files with Type=Directory should have the extension .directory")
-
- if value == "Application":
- if not self.content[group].has_key("Exec"):
- self.warnings.append("Type=Application needs 'Exec' key")
- if value == "Link":
- if not self.content[group].has_key("URL"):
- self.warnings.append("Type=Application needs 'Exec' key")
-
- elif key == "Version":
- self.checkValue(key, value)
-
- elif re.match("^Name"+xdg.Locale.regex+"$", key):
- pass # locale string
-
- elif re.match("^GenericName"+xdg.Locale.regex+"$", key):
- pass # locale string
-
- elif key == "NoDisplay":
- self.checkValue(key, value, type="boolean")
-
- elif re.match("^Comment"+xdg.Locale.regex+"$", key):
- pass # locale string
-
- elif re.match("^Icon"+xdg.Locale.regex+"$", key):
- self.checkValue(key, value)
-
- elif key == "Hidden":
- self.checkValue(key, value, type="boolean")
-
- elif key == "OnlyShowIn":
- self.checkValue(key, value, list=True)
- self.checkOnlyShowIn(value)
-
- elif key == "NotShowIn":
- self.checkValue(key, value, list=True)
- self.checkOnlyShowIn(value)
-
- elif key == "TryExec":
- self.checkValue(key, value)
- self.checkType(key, "Application")
-
- elif key == "Exec":
- self.checkValue(key, value)
- self.checkType(key, "Application")
-
- elif key == "Path":
- self.checkValue(key, value)
- self.checkType(key, "Application")
-
- elif key == "Terminal":
- self.checkValue(key, value, type="boolean")
- self.checkType(key, "Application")
-
- elif key == "MimeType":
- self.checkValue(key, value, list=True)
- self.checkType(key, "Application")
-
- elif key == "Categories":
- self.checkValue(key, value)
- self.checkType(key, "Application")
- self.checkCategorie(value)
-
- elif key == "StartupNotify":
- self.checkValue(key, value, type="boolean")
- self.checkType(key, "Application")
-
- elif key == "StartupWMClass":
- self.checkType(key, "Application")
-
- elif key == "URL":
- self.checkValue(key, value)
- self.checkType(key, "URL")
-
- # kde extensions
- elif key == "ServiceTypes":
- self.checkValue(key, value, list=True)
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- elif key == "DocPath":
- self.checkValue(key, value)
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- elif re.match("^Keywords"+xdg.Locale.regex+"$", key):
- self.checkValue(key, value, list=True)
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- elif key == "InitialPreference":
- self.checkValue(key, value, type="numeric")
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- elif key == "Dev":
- self.checkValue(key, value)
- self.checkType(key, "FSDevice")
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- elif key == "FSType":
- self.checkValue(key, value)
- self.checkType(key, "FSDevice")
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- elif key == "MountPoint":
- self.checkValue(key, value)
- self.checkType(key, "FSDevice")
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- elif key == "ReadOnly":
- self.checkValue(key, value, type="boolean")
- self.checkType(key, "FSDevice")
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- elif re.match("^UnmountIcon"+xdg.Locale.regex+"$", key):
- self.checkValue(key, value)
- self.checkType(key, "FSDevice")
- self.warnings.append("Key '%s' is a KDE extension" % key)
-
- # deprecated keys
- elif key == "Encoding":
- self.checkValue(key, value)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif re.match("^MiniIcon"+xdg.Locale.regex+"$", key):
- self.checkValue(key, value)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif key == "TerminalOptions":
- self.checkValue(key, value)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif key == "DefaultApp":
- self.checkValue(key, value)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif key == "Protocols":
- self.checkValue(key, value, list=True)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif key == "Extensions":
- self.checkValue(key, value, list=True)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif key == "BinaryPattern":
- self.checkValue(key, value)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif key == "MapNotify":
- self.checkValue(key, value)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif re.match("^SwallowTitle"+xdg.Locale.regex+"$", key):
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif key == "SwallowExec":
- self.checkValue(key, value)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif key == "FilePattern":
- self.checkValue(key, value, type="regex", list=True)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif key == "SortOrder":
- self.checkValue(key, value, list=True)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- elif key == "Actions":
- self.checkValue(key, value, list=True)
- self.warnings.append("Key '%s' is deprecated" % key)
-
- # "X-" extensions
- elif re.match("^X-[a-zA-Z0-9-]+", key):
- pass
-
- else:
- self.errors.append("Invalid key: %s" % key)
-
- def checkType(self, key, type):
- if not self.getType() == type:
- self.errors.append("Key '%s' only allowed in Type=%s" % (key, type))
-
- def checkOnlyShowIn(self, value):
- values = self.getList(value)
- valid = ["GNOME", "KDE", "ROX", "XFCE", "Old", "LXDE"]
- for item in values:
- if item not in valid and item[0:2] != "X-":
- self.errors.append("'%s' is not a registered OnlyShowIn value" % item);
-
- def checkCategorie(self, value):
- values = self.getList(value)
-
- main = ["AudioVideo", "Audio", "Video", "Development", "Education", "Game", "Graphics", "Network", "Office", "Settings", "System", "Utility"]
- hasmain = False
- for item in values:
- if item in main:
- hasmain = True
- if hasmain == False:
- self.errors.append("Missing main category")
-
- additional = ["Building", "Debugger", "IDE", "GUIDesigner", "Profiling", "RevisionControl", "Translation", "Calendar", "ContactManagement", "Database", "Dictionary", "Chart", "Email", "Finance", "FlowChart", "PDA", "ProjectManagement", "Presentation", "Spreadsheet", "WordProcessor", "2DGraphics", "VectorGraphics", "3DGraphics", "RasterGraphics", "Scanning", "OCR", "Photography", "Publishing", "Viewer", "TextTools", "DesktopSettings", "HardwareSettings", "Printing", "PackageManager", "Dialup", "InstantMessaging", "Chat", "IRCClient", "FileTransfer", "HamRadio", "News", "P2P", "RemoteAccess", "Telephony", "TelephonyTools", "VideoConference", "WebBrowser", "WebDevelopment", "Midi", "Mixer", "Sequencer", "Tuner", "TV", "AudioVideoEditing", "Player", "Recorder", "DiscBurning", "ActionGame", "AdventureGame", "ArcadeGame", "BoardGame", "BlocksGame", "CardGame", "KidsGame", "LogicGame", "RolePlaying", "Simulation", "SportsGame", "StrategyGame", "Art", "Construction", "Music", "Languages", "Science", "ArtificialIntelligence", "Astronomy", "Biology", "Chemistry", "ComputerScience", "DataVisualization", "Economy", "Electricity", "Geography", "Geology", "Geoscience", "History", "ImageProcessing", "Literature", "Math", "NumericalAnalysis", "MedicalSoftware", "Physics", "Robotics", "Sports", "ParallelComputing", "Amusement", "Archiving", "Compression", "Electronics", "Emulator", "Engineering", "FileTools", "FileManager", "TerminalEmulator", "Filesystem", "Monitor", "Security", "Accessibility", "Calculator", "Clock", "TextEditor", "Documentation", "Core", "KDE", "GNOME", "GTK", "Qt", "Motif", "Java", "ConsoleOnly", "Screensaver", "TrayIcon", "Applet", "Shell"]
-
- for item in values:
- if item not in additional + main and item[0:2] != "X-":
- self.errors.append("'%s' is not a registered Category" % item);
-
+++ /dev/null
-"""
-Exception Classes for the xdg package
-"""
-
-debug = False
-
-class Error(Exception):
- def __init__(self, msg):
- self.msg = msg
- Exception.__init__(self, msg)
- def __str__(self):
- return self.msg
-
-class ValidationError(Error):
- def __init__(self, msg, file):
- self.msg = msg
- self.file = file
- Error.__init__(self, "ValidationError in file '%s': %s " % (file, msg))
-
-class ParsingError(Error):
- def __init__(self, msg, file):
- self.msg = msg
- self.file = file
- Error.__init__(self, "ParsingError in file '%s', %s" % (file, msg))
-
-class NoKeyError(Error):
- def __init__(self, key, group, file):
- Error.__init__(self, "No key '%s' in group %s of file %s" % (key, group, file))
- self.key = key
- self.group = group
-
-class DuplicateKeyError(Error):
- def __init__(self, key, group, file):
- Error.__init__(self, "Duplicate key '%s' in group %s of file %s" % (key, group, file))
- self.key = key
- self.group = group
-
-class NoGroupError(Error):
- def __init__(self, group, file):
- Error.__init__(self, "No group: %s in file %s" % (group, file))
- self.group = group
-
-class DuplicateGroupError(Error):
- def __init__(self, group, file):
- Error.__init__(self, "Duplicate group: %s in file %s" % (group, file))
- self.group = group
-
-class NoThemeError(Error):
- def __init__(self, theme):
- Error.__init__(self, "No such icon-theme: %s" % theme)
- self.theme = theme
+++ /dev/null
-"""
-Complete implementation of the XDG Icon Spec Version 0.8
-http://standards.freedesktop.org/icon-theme-spec/
-"""
-
-import os, sys, time
-
-from xdg.IniFile import *
-from xdg.BaseDirectory import *
-from xdg.Exceptions import *
-
-import xdg.Config
-
-class IconTheme(IniFile):
- "Class to parse and validate IconThemes"
- def __init__(self):
- IniFile.__init__(self)
-
- def __repr__(self):
- return self.name
-
- def parse(self, file):
- IniFile.parse(self, file, ["Icon Theme", "KDE Icon Theme"])
- self.dir = os.path.dirname(file)
- (nil, self.name) = os.path.split(self.dir)
-
- def getDir(self):
- return self.dir
-
- # Standard Keys
- def getName(self):
- return self.get('Name', locale=True)
- def getComment(self):
- return self.get('Comment', locale=True)
- def getInherits(self):
- return self.get('Inherits', list=True)
- def getDirectories(self):
- return self.get('Directories', list=True)
- def getHidden(self):
- return self.get('Hidden', type="boolean")
- def getExample(self):
- return self.get('Example')
-
- # Per Directory Keys
- def getSize(self, directory):
- return self.get('Size', type="integer", group=directory)
- def getContext(self, directory):
- return self.get('Context', group=directory)
- def getType(self, directory):
- value = self.get('Type', group=directory)
- if value:
- return value
- else:
- return "Threshold"
- def getMaxSize(self, directory):
- value = self.get('MaxSize', type="integer", group=directory)
- if value or value == 0:
- return value
- else:
- return self.getSize(directory)
- def getMinSize(self, directory):
- value = self.get('MinSize', type="integer", group=directory)
- if value or value == 0:
- return value
- else:
- return self.getSize(directory)
- def getThreshold(self, directory):
- value = self.get('Threshold', type="integer", group=directory)
- if value or value == 0:
- return value
- else:
- return 2
-
- # validation stuff
- def checkExtras(self):
- # header
- if self.defaultGroup == "KDE Icon Theme":
- self.warnings.append('[KDE Icon Theme]-Header is deprecated')
-
- # file extension
- if self.fileExtension == ".theme":
- pass
- elif self.fileExtension == ".desktop":
- self.warnings.append('.desktop fileExtension is deprecated')
- else:
- self.warnings.append('Unknown File extension')
-
- # Check required keys
- # Name
- try:
- self.name = self.content[self.defaultGroup]["Name"]
- except KeyError:
- self.errors.append("Key 'Name' is missing")
-
- # Comment
- try:
- self.comment = self.content[self.defaultGroup]["Comment"]
- except KeyError:
- self.errors.append("Key 'Comment' is missing")
-
- # Directories
- try:
- self.directories = self.content[self.defaultGroup]["Directories"]
- except KeyError:
- self.errors.append("Key 'Directories' is missing")
-
- def checkGroup(self, group):
- # check if group header is valid
- if group == self.defaultGroup:
- pass
- elif group in self.getDirectories():
- try:
- self.type = self.content[group]["Type"]
- except KeyError:
- self.type = "Threshold"
- try:
- self.name = self.content[group]["Name"]
- except KeyError:
- self.errors.append("Key 'Name' in Group '%s' is missing" % group)
- elif not (re.match("^\[X-", group) and group.decode("utf-8", "ignore").encode("ascii", 'ignore') == group):
- self.errors.append("Invalid Group name: %s" % group)
-
- def checkKey(self, key, value, group):
- # standard keys
- if group == self.defaultGroup:
- if re.match("^Name"+xdg.Locale.regex+"$", key):
- pass
- elif re.match("^Comment"+xdg.Locale.regex+"$", key):
- pass
- elif key == "Inherits":
- self.checkValue(key, value, list=True)
- elif key == "Directories":
- self.checkValue(key, value, list=True)
- elif key == "Hidden":
- self.checkValue(key, value, type="boolean")
- elif key == "Example":
- self.checkValue(key, value)
- elif re.match("^X-[a-zA-Z0-9-]+", key):
- pass
- else:
- self.errors.append("Invalid key: %s" % key)
- elif group in self.getDirectories():
- if key == "Size":
- self.checkValue(key, value, type="integer")
- elif key == "Context":
- self.checkValue(key, value)
- elif key == "Type":
- self.checkValue(key, value)
- if value not in ["Fixed", "Scalable", "Threshold"]:
- self.errors.append("Key 'Type' must be one out of 'Fixed','Scalable','Threshold', but is %s" % value)
- elif key == "MaxSize":
- self.checkValue(key, value, type="integer")
- if self.type != "Scalable":
- self.errors.append("Key 'MaxSize' give, but Type is %s" % self.type)
- elif key == "MinSize":
- self.checkValue(key, value, type="integer")
- if self.type != "Scalable":
- self.errors.append("Key 'MinSize' give, but Type is %s" % self.type)
- elif key == "Threshold":
- self.checkValue(key, value, type="integer")
- if self.type != "Threshold":
- self.errors.append("Key 'Threshold' give, but Type is %s" % self.type)
- elif re.match("^X-[a-zA-Z0-9-]+", key):
- pass
- else:
- self.errors.append("Invalid key: %s" % key)
-
-
-class IconData(IniFile):
- "Class to parse and validate IconData Files"
- def __init__(self):
- IniFile.__init__(self)
-
- def __repr__(self):
- return self.getDisplayName()
-
- def parse(self, file):
- IniFile.parse(self, file, ["Icon Data"])
-
- # Standard Keys
- def getDisplayName(self):
- return self.get('DisplayName', locale=True)
- def getEmbeddedTextRectangle(self):
- return self.get('EmbeddedTextRectangle', list=True)
- def getAttachPoints(self):
- return self.get('AttachPoints', type="point", list=True)
-
- # validation stuff
- def checkExtras(self):
- # file extension
- if self.fileExtension != ".icon":
- self.warnings.append('Unknown File extension')
-
- def checkGroup(self, group):
- # check if group header is valid
- if not (group == self.defaultGroup \
- or (re.match("^\[X-", group) and group.encode("ascii", 'ignore') == group)):
- self.errors.append("Invalid Group name: %s" % group.encode("ascii", "replace"))
-
- def checkKey(self, key, value, group):
- # standard keys
- if re.match("^DisplayName"+xdg.Locale.regex+"$", key):
- pass
- elif key == "EmbeddedTextRectangle":
- self.checkValue(key, value, type="integer", list=True)
- elif key == "AttachPoints":
- self.checkValue(key, value, type="point", list=True)
- elif re.match("^X-[a-zA-Z0-9-]+", key):
- pass
- else:
- self.errors.append("Invalid key: %s" % key)
-
-
-
-icondirs = []
-for basedir in xdg_data_dirs:
- icondirs.append(os.path.join(basedir, "icons"))
- icondirs.append(os.path.join(basedir, "pixmaps"))
-icondirs.append(os.path.expanduser("~/.icons"))
-
-# just cache variables, they give a 10x speed improvement
-themes = []
-cache = dict()
-dache = dict()
-eache = dict()
-
-def getIconPath(iconname, size = None, theme = None, extensions = ["png", "svg", "xpm"]):
- global themes
-
- if size == None:
- size = xdg.Config.icon_size
- if theme == None:
- theme = xdg.Config.icon_theme
-
- # if we have an absolute path, just return it
- if os.path.isabs(iconname):
- return iconname
-
- # check if it has an extension and strip it
- if os.path.splitext(iconname)[1][1:] in extensions:
- iconname = os.path.splitext(iconname)[0]
-
- # parse theme files
- try:
- if themes[0].name != theme:
- themes = []
- __addTheme(theme)
- except IndexError:
- __addTheme(theme)
-
- # more caching (icon looked up in the last 5 seconds?)
- tmp = "".join([iconname, str(size), theme, "".join(extensions)])
- if eache.has_key(tmp):
- if int(time.time() - eache[tmp][0]) >= xdg.Config.cache_time:
- del eache[tmp]
- else:
- return eache[tmp][1]
-
- for thme in themes:
- icon = LookupIcon(iconname, size, thme, extensions)
- if icon:
- eache[tmp] = [time.time(), icon]
- return icon
-
- # cache stuff again (directories lookuped up in the last 5 seconds?)
- for directory in icondirs:
- if (not dache.has_key(directory) \
- or (int(time.time() - dache[directory][1]) >= xdg.Config.cache_time \
- and dache[directory][2] < os.path.getmtime(directory))) \
- and os.path.isdir(directory):
- dache[directory] = [os.listdir(directory), time.time(), os.path.getmtime(directory)]
-
- for dir, values in dache.items():
- for extension in extensions:
- try:
- if iconname + "." + extension in values[0]:
- icon = os.path.join(dir, iconname + "." + extension)
- eache[tmp] = [time.time(), icon]
- return icon
- except UnicodeDecodeError, e:
- if debug:
- raise e
- else:
- pass
-
- # we haven't found anything? "hicolor" is our fallback
- if theme != "hicolor":
- icon = getIconPath(iconname, size, "hicolor")
- eache[tmp] = [time.time(), icon]
- return icon
-
-def getIconData(path):
- if os.path.isfile(path):
- dirname = os.path.dirname(path)
- basename = os.path.basename(path)
- if os.path.isfile(os.path.join(dirname, basename + ".icon")):
- data = IconData()
- data.parse(os.path.join(dirname, basename + ".icon"))
- return data
-
-def __addTheme(theme):
- for dir in icondirs:
- if os.path.isfile(os.path.join(dir, theme, "index.theme")):
- __parseTheme(os.path.join(dir,theme, "index.theme"))
- break
- elif os.path.isfile(os.path.join(dir, theme, "index.desktop")):
- __parseTheme(os.path.join(dir,theme, "index.desktop"))
- break
- else:
- if debug:
- raise NoThemeError(theme)
-
-def __parseTheme(file):
- theme = IconTheme()
- theme.parse(file)
- themes.append(theme)
- for subtheme in theme.getInherits():
- __addTheme(subtheme)
-
-def LookupIcon(iconname, size, theme, extensions):
- # look for the cache
- if not cache.has_key(theme.name):
- cache[theme.name] = []
- cache[theme.name].append(time.time() - (xdg.Config.cache_time + 1)) # [0] last time of lookup
- cache[theme.name].append(0) # [1] mtime
- cache[theme.name].append(dict()) # [2] dir: [subdir, [items]]
-
- # cache stuff (directory lookuped up the in the last 5 seconds?)
- if int(time.time() - cache[theme.name][0]) >= xdg.Config.cache_time:
- cache[theme.name][0] = time.time()
- for subdir in theme.getDirectories():
- for directory in icondirs:
- dir = os.path.join(directory,theme.name,subdir)
- if (not cache[theme.name][2].has_key(dir) \
- or cache[theme.name][1] < os.path.getmtime(os.path.join(directory,theme.name))) \
- and subdir != "" \
- and os.path.isdir(dir):
- cache[theme.name][2][dir] = [subdir, os.listdir(dir)]
- cache[theme.name][1] = os.path.getmtime(os.path.join(directory,theme.name))
-
- for dir, values in cache[theme.name][2].items():
- if DirectoryMatchesSize(values[0], size, theme):
- for extension in extensions:
- if iconname + "." + extension in values[1]:
- return os.path.join(dir, iconname + "." + extension)
-
- minimal_size = sys.maxint
- closest_filename = ""
- for dir, values in cache[theme.name][2].items():
- distance = DirectorySizeDistance(values[0], size, theme)
- if distance < minimal_size:
- for extension in extensions:
- if iconname + "." + extension in values[1]:
- closest_filename = os.path.join(dir, iconname + "." + extension)
- minimal_size = distance
-
- return closest_filename
-
-def DirectoryMatchesSize(subdir, iconsize, theme):
- Type = theme.getType(subdir)
- Size = theme.getSize(subdir)
- Threshold = theme.getThreshold(subdir)
- MinSize = theme.getMinSize(subdir)
- MaxSize = theme.getMaxSize(subdir)
- if Type == "Fixed":
- return Size == iconsize
- elif Type == "Scaleable":
- return MinSize <= iconsize <= MaxSize
- elif Type == "Threshold":
- return Size - Threshold <= iconsize <= Size + Threshold
-
-def DirectorySizeDistance(subdir, iconsize, theme):
- Type = theme.getType(subdir)
- Size = theme.getSize(subdir)
- Threshold = theme.getThreshold(subdir)
- MinSize = theme.getMinSize(subdir)
- MaxSize = theme.getMaxSize(subdir)
- if Type == "Fixed":
- return abs(Size - iconsize)
- elif Type == "Scalable":
- if iconsize < MinSize:
- return MinSize - iconsize
- elif iconsize > MaxSize:
- return MaxSize - iconsize
- return 0
- elif Type == "Threshold":
- if iconsize < Size - Threshold:
- return MinSize - iconsize
- elif iconsize > Size + Threshold:
- return iconsize - MaxSize
- return 0
+++ /dev/null
-"""
-Base Class for DesktopEntry, IconTheme and IconData
-"""
-
-import re, os, stat, codecs
-from Exceptions import *
-import xdg.Locale
-
-class IniFile:
- defaultGroup = ''
- fileExtension = ''
-
- filename = ''
-
- tainted = False
-
- def __init__(self, filename=None):
- self.content = dict()
- if filename:
- self.parse(filename)
-
- def __cmp__(self, other):
- return cmp(self.content, other.content)
-
- def parse(self, filename, headers=None):
- # for performance reasons
- content = self.content
-
- if not os.path.isfile(filename):
- raise ParsingError("File not found", filename)
-
- try:
- fd = file(filename, 'r')
- except IOError, e:
- if debug:
- raise e
- else:
- return
-
- # parse file
- for line in fd:
- line = line.strip()
- # empty line
- if not line:
- continue
- # comment
- elif line[0] == '#':
- continue
- # new group
- elif line[0] == '[':
- currentGroup = line.lstrip("[").rstrip("]")
- if debug and self.hasGroup(currentGroup):
- raise DuplicateGroupError(currentGroup, filename)
- else:
- content[currentGroup] = {}
- # key
- else:
- index = line.find("=")
- key = line[0:index].strip()
- value = line[index+1:].strip()
- try:
- if debug and self.hasKey(key, currentGroup):
- raise DuplicateKeyError(key, currentGroup, filename)
- else:
- content[currentGroup][key] = value
- except (IndexError, UnboundLocalError):
- raise ParsingError("Parsing error on key, group missing", filename)
-
- fd.close()
-
- self.filename = filename
- self.tainted = False
-
- # check header
- if headers:
- for header in headers:
- if content.has_key(header):
- self.defaultGroup = header
- break
- else:
- raise ParsingError("[%s]-Header missing" % headers[0], filename)
-
- # start stuff to access the keys
- def get(self, key, group=None, locale=False, type="string", list=False):
- # set default group
- if not group:
- group = self.defaultGroup
-
- # return key (with locale)
- if self.content.has_key(group) and self.content[group].has_key(key):
- if locale:
- value = self.content[group][self.__addLocale(key, group)]
- else:
- value = self.content[group][key]
- else:
- if debug:
- if not self.content.has_key(group):
- raise NoGroupError(group, self.filename)
- elif not self.content[group].has_key(key):
- raise NoKeyError(key, group, self.filename)
- else:
- value = ""
-
- if list == True:
- values = self.getList(value)
- result = []
- else:
- values = [value]
-
- for value in values:
- if type == "string" and locale == True:
- value = value.decode("utf-8", "ignore")
- elif type == "boolean":
- value = self.__getBoolean(value)
- elif type == "integer":
- try:
- value = int(value)
- except ValueError:
- value = 0
- elif type == "numeric":
- try:
- value = float(value)
- except ValueError:
- value = 0.0
- elif type == "regex":
- value = re.compile(value)
- elif type == "point":
- value = value.split(",")
-
- if list == True:
- result.append(value)
- else:
- result = value
-
- return result
- # end stuff to access the keys
-
- # start subget
- def getList(self, string):
- if re.search(r"(?<!\\)\;", string):
- list = re.split(r"(?<!\\);", string)
- elif re.search(r"(?<!\\)\|", string):
- list = re.split(r"(?<!\\)\|", string)
- elif re.search(r"(?<!\\),", string):
- list = re.split(r"(?<!\\),", string)
- else:
- list = [string]
- if list[-1] == "":
- list.pop()
- return list
-
- def __getBoolean(self, boolean):
- if boolean == 1 or boolean == "true" or boolean == "True":
- return True
- elif boolean == 0 or boolean == "false" or boolean == "False":
- return False
- return False
- # end subget
-
- def __addLocale(self, key, group=None):
- "add locale to key according the current lc_messages"
- # set default group
- if not group:
- group = self.defaultGroup
-
- for lang in xdg.Locale.langs:
- if self.content[group].has_key(key+'['+lang+']'):
- return key+'['+lang+']'
-
- return key
-
- # start validation stuff
- def validate(self, report="All"):
- "validate ... report = All / Warnings / Errors"
-
- self.warnings = []
- self.errors = []
-
- # get file extension
- self.fileExtension = os.path.splitext(self.filename)[1]
-
- # overwrite this for own checkings
- self.checkExtras()
-
- # check all keys
- for group in self.content:
- self.checkGroup(group)
- for key in self.content[group]:
- self.checkKey(key, self.content[group][key], group)
- # check if value is empty
- if self.content[group][key] == "":
- self.warnings.append("Value of Key '%s' is empty" % key)
-
- # raise Warnings / Errors
- msg = ""
-
- if report == "All" or report == "Warnings":
- for line in self.warnings:
- msg += "\n- " + line
-
- if report == "All" or report == "Errors":
- for line in self.errors:
- msg += "\n- " + line
-
- if msg:
- raise ValidationError(msg, self.filename)
-
- # check if group header is valid
- def checkGroup(self, group):
- pass
-
- # check if key is valid
- def checkKey(self, key, value, group):
- pass
-
- # check random stuff
- def checkValue(self, key, value, type="string", list=False):
- if list == True:
- values = self.getList(value)
- else:
- values = [value]
-
- for value in values:
- if type == "string":
- code = self.checkString(value)
- elif type == "boolean":
- code = self.checkBoolean(value)
- elif type == "numeric":
- code = self.checkNumber(value)
- elif type == "integer":
- code = self.checkInteger(value)
- elif type == "regex":
- code = self.checkRegex(value)
- elif type == "point":
- code = self.checkPoint(value)
- if code == 1:
- self.errors.append("'%s' is not a valid %s" % (value, type))
- elif code == 2:
- self.warnings.append("Value of key '%s' is deprecated" % key)
-
- def checkExtras(self):
- pass
-
- def checkBoolean(self, value):
- # 1 or 0 : deprecated
- if (value == "1" or value == "0"):
- return 2
- # true or false: ok
- elif not (value == "true" or value == "false"):
- return 1
-
- def checkNumber(self, value):
- # float() ValueError
- try:
- float(value)
- except:
- return 1
-
- def checkInteger(self, value):
- # int() ValueError
- try:
- int(value)
- except:
- return 1
-
- def checkPoint(self, value):
- if not re.match("^[0-9]+,[0-9]+$", value):
- return 1
-
- def checkString(self, value):
- # convert to ascii
- if not value.decode("utf-8", "ignore").encode("ascii", 'ignore') == value:
- return 1
-
- def checkRegex(self, value):
- try:
- re.compile(value)
- except:
- return 1
-
- # write support
- def write(self, filename=None, trusted=False):
- if not filename and not self.filename:
- raise ParsingError("File not found", "")
-
- if filename:
- self.filename = filename
- else:
- filename = self.filename
-
- if os.path.dirname(filename) and not os.path.isdir(os.path.dirname(filename)):
- os.makedirs(os.path.dirname(filename))
-
- fp = codecs.open(filename, 'w')
-
- # An executable bit signifies that the desktop file is
- # trusted, but then the file can be executed. Add hashbang to
- # make sure that the file is opened by something that
- # understands desktop files.
- if trusted:
- fp.write("#!/usr/bin/env xdg-open\n")
-
- if self.defaultGroup:
- fp.write("[%s]\n" % self.defaultGroup)
- for (key, value) in self.content[self.defaultGroup].items():
- fp.write("%s=%s\n" % (key, value))
- fp.write("\n")
- for (name, group) in self.content.items():
- if name != self.defaultGroup:
- fp.write("[%s]\n" % name)
- for (key, value) in group.items():
- fp.write("%s=%s\n" % (key, value))
- fp.write("\n")
-
- # Add executable bits to the file to show that it's trusted.
- if trusted:
- oldmode = os.stat(filename).st_mode
- mode = oldmode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
- os.chmod(filename, mode)
-
- self.tainted = False
-
- def set(self, key, value, group=None, locale=False):
- # set default group
- if not group:
- group = self.defaultGroup
-
- if locale == True and len(xdg.Locale.langs) > 0:
- key = key + "[" + xdg.Locale.langs[0] + "]"
-
- try:
- if isinstance(value, unicode):
- self.content[group][key] = value.encode("utf-8", "ignore")
- else:
- self.content[group][key] = value
- except KeyError:
- raise NoGroupError(group, self.filename)
-
- self.tainted = (value == self.get(key, group))
-
- def addGroup(self, group):
- if self.hasGroup(group):
- if debug:
- raise DuplicateGroupError(group, self.filename)
- else:
- pass
- else:
- self.content[group] = {}
- self.tainted = True
-
- def removeGroup(self, group):
- existed = group in self.content
- if existed:
- del self.content[group]
- self.tainted = True
- else:
- if debug:
- raise NoGroupError(group, self.filename)
- return existed
-
- def removeKey(self, key, group=None, locales=True):
- # set default group
- if not group:
- group = self.defaultGroup
-
- try:
- if locales:
- for (name, value) in self.content[group].items():
- if re.match("^" + key + xdg.Locale.regex + "$", name) and name != key:
- value = self.content[group][name]
- del self.content[group][name]
- value = self.content[group][key]
- del self.content[group][key]
- self.tainted = True
- return value
- except KeyError, e:
- if debug:
- if e == group:
- raise NoGroupError(group, self.filename)
- else:
- raise NoKeyError(key, group, self.filename)
- else:
- return ""
-
- # misc
- def groups(self):
- return self.content.keys()
-
- def hasGroup(self, group):
- if self.content.has_key(group):
- return True
- else:
- return False
-
- def hasKey(self, key, group=None):
- # set default group
- if not group:
- group = self.defaultGroup
-
- if self.content[group].has_key(key):
- return True
- else:
- return False
-
- def getFileName(self):
- return self.filename
+++ /dev/null
-"""
-Helper Module for Locale settings
-
-This module is based on a ROX module (LGPL):
-
-http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/i18n.py?rev=1.3&view=log
-"""
-
-import os
-from locale import normalize
-
-regex = "(\[([a-zA-Z]+)(_[a-zA-Z]+)?(\.[a-zA-Z\-0-9]+)?(@[a-zA-Z]+)?\])?"
-
-def _expand_lang(locale):
- locale = normalize(locale)
- COMPONENT_CODESET = 1 << 0
- COMPONENT_MODIFIER = 1 << 1
- COMPONENT_TERRITORY = 1 << 2
- # split up the locale into its base components
- mask = 0
- pos = locale.find('@')
- if pos >= 0:
- modifier = locale[pos:]
- locale = locale[:pos]
- mask |= COMPONENT_MODIFIER
- else:
- modifier = ''
- pos = locale.find('.')
- codeset = ''
- if pos >= 0:
- locale = locale[:pos]
- pos = locale.find('_')
- if pos >= 0:
- territory = locale[pos:]
- locale = locale[:pos]
- mask |= COMPONENT_TERRITORY
- else:
- territory = ''
- language = locale
- ret = []
- for i in range(mask+1):
- if not (i & ~mask): # if all components for this combo exist ...
- val = language
- if i & COMPONENT_TERRITORY: val += territory
- if i & COMPONENT_CODESET: val += codeset
- if i & COMPONENT_MODIFIER: val += modifier
- ret.append(val)
- ret.reverse()
- return ret
-
-def expand_languages(languages=None):
- # Get some reasonable defaults for arguments that were not supplied
- if languages is None:
- languages = []
- for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
- val = os.environ.get(envar)
- if val:
- languages = val.split(':')
- break
- #if 'C' not in languages:
- # languages.append('C')
-
- # now normalize and expand the languages
- nelangs = []
- for lang in languages:
- for nelang in _expand_lang(lang):
- if nelang not in nelangs:
- nelangs.append(nelang)
- return nelangs
-
-def update(language=None):
- global langs
- if language:
- langs = expand_languages([language])
- else:
- langs = expand_languages()
-
-langs = []
-update()
+++ /dev/null
-"""
-Implementation of the XDG Menu Specification Version 1.0.draft-1
-http://standards.freedesktop.org/menu-spec/
-"""
-
-from __future__ import generators
-import locale, os, xml.dom.minidom
-
-from xdg.BaseDirectory import *
-from xdg.DesktopEntry import *
-from xdg.Exceptions import *
-
-import xdg.Locale
-import xdg.Config
-
-ELEMENT_NODE = xml.dom.Node.ELEMENT_NODE
-
-# for python <= 2.3
-try:
- reversed = reversed
-except NameError:
- def reversed(x):
- return x[::-1]
-
-class Menu:
- def __init__(self):
- # Public stuff
- self.Name = ""
- self.Directory = None
- self.Entries = []
- self.Doc = ""
- self.Filename = ""
- self.Depth = 0
- self.Parent = None
- self.NotInXml = False
-
- # Can be one of Deleted/NoDisplay/Hidden/Empty/NotShowIn or True
- self.Show = True
- self.Visible = 0
-
- # Private stuff, only needed for parsing
- self.AppDirs = []
- self.DefaultLayout = None
- self.Deleted = "notset"
- self.Directories = []
- self.DirectoryDirs = []
- self.Layout = None
- self.MenuEntries = []
- self.Moves = []
- self.OnlyUnallocated = "notset"
- self.Rules = []
- self.Submenus = []
-
- def __str__(self):
- return self.Name
-
- def __add__(self, other):
- for dir in other.AppDirs:
- self.AppDirs.append(dir)
-
- for dir in other.DirectoryDirs:
- self.DirectoryDirs.append(dir)
-
- for directory in other.Directories:
- self.Directories.append(directory)
-
- if other.Deleted != "notset":
- self.Deleted = other.Deleted
-
- if other.OnlyUnallocated != "notset":
- self.OnlyUnallocated = other.OnlyUnallocated
-
- if other.Layout:
- self.Layout = other.Layout
-
- if other.DefaultLayout:
- self.DefaultLayout = other.DefaultLayout
-
- for rule in other.Rules:
- self.Rules.append(rule)
-
- for move in other.Moves:
- self.Moves.append(move)
-
- for submenu in other.Submenus:
- self.addSubmenu(submenu)
-
- return self
-
- # FIXME: Performance: cache getName()
- def __cmp__(self, other):
- return locale.strcoll(self.getName(), other.getName())
-
- def __eq__(self, other):
- if self.Name == str(other):
- return True
- else:
- return False
-
- """ PUBLIC STUFF """
- def getEntries(self, hidden=False):
- for entry in self.Entries:
- if hidden == True:
- yield entry
- elif entry.Show == True:
- yield entry
-
- # FIXME: Add searchEntry/seaqrchMenu function
- # search for name/comment/genericname/desktopfileide
- # return multiple items
-
- def getMenuEntry(self, desktopfileid, deep = False):
- for menuentry in self.MenuEntries:
- if menuentry.DesktopFileID == desktopfileid:
- return menuentry
- if deep == True:
- for submenu in self.Submenus:
- submenu.getMenuEntry(desktopfileid, deep)
-
- def getMenu(self, path):
- array = path.split("/", 1)
- for submenu in self.Submenus:
- if submenu.Name == array[0]:
- if len(array) > 1:
- return submenu.getMenu(array[1])
- else:
- return submenu
-
- def getPath(self, org=False, toplevel=False):
- parent = self
- names=[]
- while 1:
- if org:
- names.append(parent.Name)
- else:
- names.append(parent.getName())
- if parent.Depth > 0:
- parent = parent.Parent
- else:
- break
- names.reverse()
- path = ""
- if toplevel == False:
- names.pop(0)
- for name in names:
- path = os.path.join(path, name)
- return path
-
- def getName(self):
- try:
- return self.Directory.DesktopEntry.getName()
- except AttributeError:
- return self.Name
-
- def getGenericName(self):
- try:
- return self.Directory.DesktopEntry.getGenericName()
- except AttributeError:
- return ""
-
- def getComment(self):
- try:
- return self.Directory.DesktopEntry.getComment()
- except AttributeError:
- return ""
-
- def getIcon(self):
- try:
- return self.Directory.DesktopEntry.getIcon()
- except AttributeError:
- return ""
-
- """ PRIVATE STUFF """
- def addSubmenu(self, newmenu):
- for submenu in self.Submenus:
- if submenu == newmenu:
- submenu += newmenu
- break
- else:
- self.Submenus.append(newmenu)
- newmenu.Parent = self
- newmenu.Depth = self.Depth + 1
-
-class Move:
- "A move operation"
- def __init__(self, node=None):
- if node:
- self.parseNode(node)
- else:
- self.Old = ""
- self.New = ""
-
- def __cmp__(self, other):
- return cmp(self.Old, other.Old)
-
- def parseNode(self, node):
- for child in node.childNodes:
- if child.nodeType == ELEMENT_NODE:
- if child.tagName == "Old":
- try:
- self.parseOld(child.childNodes[0].nodeValue)
- except IndexError:
- raise ValidationError('Old cannot be empty', '??')
- elif child.tagName == "New":
- try:
- self.parseNew(child.childNodes[0].nodeValue)
- except IndexError:
- raise ValidationError('New cannot be empty', '??')
-
- def parseOld(self, value):
- self.Old = value
- def parseNew(self, value):
- self.New = value
-
-
-class Layout:
- "Menu Layout class"
- def __init__(self, node=None):
- self.order = []
- if node:
- self.show_empty = node.getAttribute("show_empty") or "false"
- self.inline = node.getAttribute("inline") or "false"
- self.inline_limit = node.getAttribute("inline_limit") or 4
- self.inline_header = node.getAttribute("inline_header") or "true"
- self.inline_alias = node.getAttribute("inline_alias") or "false"
- self.inline_limit = int(self.inline_limit)
- self.parseNode(node)
- else:
- self.show_empty = "false"
- self.inline = "false"
- self.inline_limit = 4
- self.inline_header = "true"
- self.inline_alias = "false"
- self.order.append(["Merge", "menus"])
- self.order.append(["Merge", "files"])
-
- def parseNode(self, node):
- for child in node.childNodes:
- if child.nodeType == ELEMENT_NODE:
- if child.tagName == "Menuname":
- try:
- self.parseMenuname(
- child.childNodes[0].nodeValue,
- child.getAttribute("show_empty") or "false",
- child.getAttribute("inline") or "false",
- child.getAttribute("inline_limit") or 4,
- child.getAttribute("inline_header") or "true",
- child.getAttribute("inline_alias") or "false" )
- except IndexError:
- raise ValidationError('Menuname cannot be empty', "")
- elif child.tagName == "Separator":
- self.parseSeparator()
- elif child.tagName == "Filename":
- try:
- self.parseFilename(child.childNodes[0].nodeValue)
- except IndexError:
- raise ValidationError('Filename cannot be empty', "")
- elif child.tagName == "Merge":
- self.parseMerge(child.getAttribute("type") or "all")
-
- def parseMenuname(self, value, empty="false", inline="false", inline_limit=4, inline_header="true", inline_alias="false"):
- self.order.append(["Menuname", value, empty, inline, inline_limit, inline_header, inline_alias])
- self.order[-1][4] = int(self.order[-1][4])
-
- def parseSeparator(self):
- self.order.append(["Separator"])
-
- def parseFilename(self, value):
- self.order.append(["Filename", value])
-
- def parseMerge(self, type="all"):
- self.order.append(["Merge", type])
-
-
-class Rule:
- "Inlcude / Exclude Rules Class"
- def __init__(self, type, node=None):
- # Type is Include or Exclude
- self.Type = type
- # Rule is a python expression
- self.Rule = ""
-
- # Private attributes, only needed for parsing
- self.Depth = 0
- self.Expr = [ "or" ]
- self.New = True
-
- # Begin parsing
- if node:
- self.parseNode(node)
- self.compile()
-
- def __str__(self):
- return self.Rule
-
- def compile(self):
- exec("""
-def do(menuentries, type, run):
- for menuentry in menuentries:
- if run == 2 and ( menuentry.MatchedInclude == True \
- or menuentry.Allocated == True ):
- continue
- elif %s:
- if type == "Include":
- menuentry.Add = True
- menuentry.MatchedInclude = True
- else:
- menuentry.Add = False
- return menuentries
-""" % self.Rule) in self.__dict__
-
- def parseNode(self, node):
- for child in node.childNodes:
- if child.nodeType == ELEMENT_NODE:
- if child.tagName == 'Filename':
- try:
- self.parseFilename(child.childNodes[0].nodeValue)
- except IndexError:
- raise ValidationError('Filename cannot be empty', "???")
- elif child.tagName == 'Category':
- try:
- self.parseCategory(child.childNodes[0].nodeValue)
- except IndexError:
- raise ValidationError('Category cannot be empty', "???")
- elif child.tagName == 'All':
- self.parseAll()
- elif child.tagName == 'And':
- self.parseAnd(child)
- elif child.tagName == 'Or':
- self.parseOr(child)
- elif child.tagName == 'Not':
- self.parseNot(child)
-
- def parseNew(self, set=True):
- if not self.New:
- self.Rule += " " + self.Expr[self.Depth] + " "
- if not set:
- self.New = True
- elif set:
- self.New = False
-
- def parseFilename(self, value):
- self.parseNew()
- self.Rule += "menuentry.DesktopFileID == '%s'" % value.strip().replace("\\", r"\\").replace("'", r"\'")
-
- def parseCategory(self, value):
- self.parseNew()
- self.Rule += "'%s' in menuentry.Categories" % value.strip()
-
- def parseAll(self):
- self.parseNew()
- self.Rule += "True"
-
- def parseAnd(self, node):
- self.parseNew(False)
- self.Rule += "("
- self.Depth += 1
- self.Expr.append("and")
- self.parseNode(node)
- self.Depth -= 1
- self.Expr.pop()
- self.Rule += ")"
-
- def parseOr(self, node):
- self.parseNew(False)
- self.Rule += "("
- self.Depth += 1
- self.Expr.append("or")
- self.parseNode(node)
- self.Depth -= 1
- self.Expr.pop()
- self.Rule += ")"
-
- def parseNot(self, node):
- self.parseNew(False)
- self.Rule += "not ("
- self.Depth += 1
- self.Expr.append("or")
- self.parseNode(node)
- self.Depth -= 1
- self.Expr.pop()
- self.Rule += ")"
-
-
-class MenuEntry:
- "Wrapper for 'Menu Style' Desktop Entries"
- def __init__(self, filename, dir="", prefix=""):
- # Create entry
- self.DesktopEntry = DesktopEntry(os.path.join(dir,filename))
- self.setAttributes(filename, dir, prefix)
-
- # Can be one of Deleted/Hidden/Empty/NotShowIn/NoExec or True
- self.Show = True
-
- # Semi-Private
- self.Original = None
- self.Parents = []
-
- # Private Stuff
- self.Allocated = False
- self.Add = False
- self.MatchedInclude = False
-
- # Caching
- self.Categories = self.DesktopEntry.getCategories()
-
- def save(self):
- if self.DesktopEntry.tainted == True:
- self.DesktopEntry.write()
-
- def getDir(self):
- return self.DesktopEntry.filename.replace(self.Filename, '')
-
- def getType(self):
- # Can be one of System/User/Both
- if xdg.Config.root_mode == False:
- if self.Original:
- return "Both"
- elif xdg_data_dirs[0] in self.DesktopEntry.filename:
- return "User"
- else:
- return "System"
- else:
- return "User"
-
- def setAttributes(self, filename, dir="", prefix=""):
- self.Filename = filename
- self.Prefix = prefix
- self.DesktopFileID = os.path.join(prefix,filename).replace("/", "-")
-
- if not os.path.isabs(self.DesktopEntry.filename):
- self.__setFilename()
-
- def updateAttributes(self):
- if self.getType() == "System":
- self.Original = MenuEntry(self.Filename, self.getDir(), self.Prefix)
- self.__setFilename()
-
- def __setFilename(self):
- if xdg.Config.root_mode == False:
- path = xdg_data_dirs[0]
- else:
- path= xdg_data_dirs[1]
-
- if self.DesktopEntry.getType() == "Application":
- dir = os.path.join(path, "applications")
- else:
- dir = os.path.join(path, "desktop-directories")
-
- self.DesktopEntry.filename = os.path.join(dir, self.Filename)
-
- def __cmp__(self, other):
- return locale.strcoll(self.DesktopEntry.getName(), other.DesktopEntry.getName())
-
- def __eq__(self, other):
- if self.DesktopFileID == str(other):
- return True
- else:
- return False
-
- def __repr__(self):
- return self.DesktopFileID
-
-
-class Separator:
- "Just a dummy class for Separators"
- def __init__(self, parent):
- self.Parent = parent
- self.Show = True
-
-
-class Header:
- "Class for Inline Headers"
- def __init__(self, name, generic_name, comment):
- self.Name = name
- self.GenericName = generic_name
- self.Comment = comment
-
- def __str__(self):
- return self.Name
-
-
-tmp = {}
-
-def __getFileName(filename):
- dirs = xdg_config_dirs[:]
- if xdg.Config.root_mode == True:
- dirs.pop(0)
-
- for dir in dirs:
- menuname = os.path.join (dir, "menus" , filename)
- if os.path.isdir(dir) and os.path.isfile(menuname):
- return menuname
-
-def parse(filename=None):
- # conver to absolute path
- if filename and not os.path.isabs(filename):
- filename = __getFileName(filename)
-
- # use default if no filename given
- if not filename:
- candidate = os.environ.get('XDG_MENU_PREFIX', '') + "applications.menu"
- filename = __getFileName(candidate)
-
- if not filename:
- raise ParsingError('File not found', "/etc/xdg/menus/%s" % candidate)
-
- # check if it is a .menu file
- if not os.path.splitext(filename)[1] == ".menu":
- raise ParsingError('Not a .menu file', filename)
-
- # create xml parser
- try:
- doc = xml.dom.minidom.parse(filename)
- except xml.parsers.expat.ExpatError:
- raise ParsingError('Not a valid .menu file', filename)
-
- # parse menufile
- tmp["Root"] = ""
- tmp["mergeFiles"] = []
- tmp["DirectoryDirs"] = []
- tmp["cache"] = MenuEntryCache()
-
- __parse(doc, filename, tmp["Root"])
- __parsemove(tmp["Root"])
- __postparse(tmp["Root"])
-
- tmp["Root"].Doc = doc
- tmp["Root"].Filename = filename
-
- # generate the menu
- __genmenuNotOnlyAllocated(tmp["Root"])
- __genmenuOnlyAllocated(tmp["Root"])
-
- # and finally sort
- sort(tmp["Root"])
-
- return tmp["Root"]
-
-
-def __parse(node, filename, parent=None):
- for child in node.childNodes:
- if child.nodeType == ELEMENT_NODE:
- if child.tagName == 'Menu':
- __parseMenu(child, filename, parent)
- elif child.tagName == 'AppDir':
- try:
- __parseAppDir(child.childNodes[0].nodeValue, filename, parent)
- except IndexError:
- raise ValidationError('AppDir cannot be empty', filename)
- elif child.tagName == 'DefaultAppDirs':
- __parseDefaultAppDir(filename, parent)
- elif child.tagName == 'DirectoryDir':
- try:
- __parseDirectoryDir(child.childNodes[0].nodeValue, filename, parent)
- except IndexError:
- raise ValidationError('DirectoryDir cannot be empty', filename)
- elif child.tagName == 'DefaultDirectoryDirs':
- __parseDefaultDirectoryDir(filename, parent)
- elif child.tagName == 'Name' :
- try:
- parent.Name = child.childNodes[0].nodeValue
- except IndexError:
- raise ValidationError('Name cannot be empty', filename)
- elif child.tagName == 'Directory' :
- try:
- parent.Directories.append(child.childNodes[0].nodeValue)
- except IndexError:
- raise ValidationError('Directory cannot be empty', filename)
- elif child.tagName == 'OnlyUnallocated':
- parent.OnlyUnallocated = True
- elif child.tagName == 'NotOnlyUnallocated':
- parent.OnlyUnallocated = False
- elif child.tagName == 'Deleted':
- parent.Deleted = True
- elif child.tagName == 'NotDeleted':
- parent.Deleted = False
- elif child.tagName == 'Include' or child.tagName == 'Exclude':
- parent.Rules.append(Rule(child.tagName, child))
- elif child.tagName == 'MergeFile':
- try:
- if child.getAttribute("type") == "parent":
- __parseMergeFile("applications.menu", child, filename, parent)
- else:
- __parseMergeFile(child.childNodes[0].nodeValue, child, filename, parent)
- except IndexError:
- raise ValidationError('MergeFile cannot be empty', filename)
- elif child.tagName == 'MergeDir':
- try:
- __parseMergeDir(child.childNodes[0].nodeValue, child, filename, parent)
- except IndexError:
- raise ValidationError('MergeDir cannot be empty', filename)
- elif child.tagName == 'DefaultMergeDirs':
- __parseDefaultMergeDirs(child, filename, parent)
- elif child.tagName == 'Move':
- parent.Moves.append(Move(child))
- elif child.tagName == 'Layout':
- if len(child.childNodes) > 1:
- parent.Layout = Layout(child)
- elif child.tagName == 'DefaultLayout':
- if len(child.childNodes) > 1:
- parent.DefaultLayout = Layout(child)
- elif child.tagName == 'LegacyDir':
- try:
- __parseLegacyDir(child.childNodes[0].nodeValue, child.getAttribute("prefix"), filename, parent)
- except IndexError:
- raise ValidationError('LegacyDir cannot be empty', filename)
- elif child.tagName == 'KDELegacyDirs':
- __parseKDELegacyDirs(filename, parent)
-
-def __parsemove(menu):
- for submenu in menu.Submenus:
- __parsemove(submenu)
-
- # parse move operations
- for move in menu.Moves:
- move_from_menu = menu.getMenu(move.Old)
- if move_from_menu:
- move_to_menu = menu.getMenu(move.New)
-
- menus = move.New.split("/")
- oldparent = None
- while len(menus) > 0:
- if not oldparent:
- oldparent = menu
- newmenu = oldparent.getMenu(menus[0])
- if not newmenu:
- newmenu = Menu()
- newmenu.Name = menus[0]
- if len(menus) > 1:
- newmenu.NotInXml = True
- oldparent.addSubmenu(newmenu)
- oldparent = newmenu
- menus.pop(0)
-
- newmenu += move_from_menu
- move_from_menu.Parent.Submenus.remove(move_from_menu)
-
-def __postparse(menu):
- # unallocated / deleted
- if menu.Deleted == "notset":
- menu.Deleted = False
- if menu.OnlyUnallocated == "notset":
- menu.OnlyUnallocated = False
-
- # Layout Tags
- if not menu.Layout or not menu.DefaultLayout:
- if menu.DefaultLayout:
- menu.Layout = menu.DefaultLayout
- elif menu.Layout:
- if menu.Depth > 0:
- menu.DefaultLayout = menu.Parent.DefaultLayout
- else:
- menu.DefaultLayout = Layout()
- else:
- if menu.Depth > 0:
- menu.Layout = menu.Parent.DefaultLayout
- menu.DefaultLayout = menu.Parent.DefaultLayout
- else:
- menu.Layout = Layout()
- menu.DefaultLayout = Layout()
-
- # add parent's app/directory dirs
- if menu.Depth > 0:
- menu.AppDirs = menu.Parent.AppDirs + menu.AppDirs
- menu.DirectoryDirs = menu.Parent.DirectoryDirs + menu.DirectoryDirs
-
- # remove duplicates
- menu.Directories = __removeDuplicates(menu.Directories)
- menu.DirectoryDirs = __removeDuplicates(menu.DirectoryDirs)
- menu.AppDirs = __removeDuplicates(menu.AppDirs)
-
- # go recursive through all menus
- for submenu in menu.Submenus:
- __postparse(submenu)
-
- # reverse so handling is easier
- menu.Directories.reverse()
- menu.DirectoryDirs.reverse()
- menu.AppDirs.reverse()
-
- # get the valid .directory file out of the list
- for directory in menu.Directories:
- for dir in menu.DirectoryDirs:
- if os.path.isfile(os.path.join(dir, directory)):
- menuentry = MenuEntry(directory, dir)
- if not menu.Directory:
- menu.Directory = menuentry
- elif menuentry.getType() == "System":
- if menu.Directory.getType() == "User":
- menu.Directory.Original = menuentry
- if menu.Directory:
- break
-
-
-# Menu parsing stuff
-def __parseMenu(child, filename, parent):
- m = Menu()
- __parse(child, filename, m)
- if parent:
- parent.addSubmenu(m)
- else:
- tmp["Root"] = m
-
-# helper function
-def __check(value, filename, type):
- path = os.path.dirname(filename)
-
- if not os.path.isabs(value):
- value = os.path.join(path, value)
-
- value = os.path.abspath(value)
-
- if type == "dir" and os.path.exists(value) and os.path.isdir(value):
- return value
- elif type == "file" and os.path.exists(value) and os.path.isfile(value):
- return value
- else:
- return False
-
-# App/Directory Dir Stuff
-def __parseAppDir(value, filename, parent):
- value = __check(value, filename, "dir")
- if value:
- parent.AppDirs.append(value)
-
-def __parseDefaultAppDir(filename, parent):
- for dir in reversed(xdg_data_dirs):
- __parseAppDir(os.path.join(dir, "applications"), filename, parent)
-
-def __parseDirectoryDir(value, filename, parent):
- value = __check(value, filename, "dir")
- if value:
- parent.DirectoryDirs.append(value)
-
-def __parseDefaultDirectoryDir(filename, parent):
- for dir in reversed(xdg_data_dirs):
- __parseDirectoryDir(os.path.join(dir, "desktop-directories"), filename, parent)
-
-# Merge Stuff
-def __parseMergeFile(value, child, filename, parent):
- if child.getAttribute("type") == "parent":
- for dir in xdg_config_dirs:
- rel_file = filename.replace(dir, "").strip("/")
- if rel_file != filename:
- for p in xdg_config_dirs:
- if dir == p:
- continue
- if os.path.isfile(os.path.join(p,rel_file)):
- __mergeFile(os.path.join(p,rel_file),child,parent)
- break
- else:
- value = __check(value, filename, "file")
- if value:
- __mergeFile(value, child, parent)
-
-def __parseMergeDir(value, child, filename, parent):
- value = __check(value, filename, "dir")
- if value:
- for item in os.listdir(value):
- try:
- if os.path.splitext(item)[1] == ".menu":
- __mergeFile(os.path.join(value, item), child, parent)
- except UnicodeDecodeError:
- continue
-
-def __parseDefaultMergeDirs(child, filename, parent):
- basename = os.path.splitext(os.path.basename(filename))[0]
- for dir in reversed(xdg_config_dirs):
- __parseMergeDir(os.path.join(dir, "menus", basename + "-merged"), child, filename, parent)
-
-def __mergeFile(filename, child, parent):
- # check for infinite loops
- if filename in tmp["mergeFiles"]:
- if debug:
- raise ParsingError('Infinite MergeFile loop detected', filename)
- else:
- return
-
- tmp["mergeFiles"].append(filename)
-
- # load file
- try:
- doc = xml.dom.minidom.parse(filename)
- except IOError:
- if debug:
- raise ParsingError('File not found', filename)
- else:
- return
- except xml.parsers.expat.ExpatError:
- if debug:
- raise ParsingError('Not a valid .menu file', filename)
- else:
- return
-
- # append file
- for child in doc.childNodes:
- if child.nodeType == ELEMENT_NODE:
- __parse(child,filename,parent)
- break
-
-# Legacy Dir Stuff
-def __parseLegacyDir(dir, prefix, filename, parent):
- m = __mergeLegacyDir(dir,prefix,filename,parent)
- if m:
- parent += m
-
-def __mergeLegacyDir(dir, prefix, filename, parent):
- dir = __check(dir,filename,"dir")
- if dir and dir not in tmp["DirectoryDirs"]:
- tmp["DirectoryDirs"].append(dir)
-
- m = Menu()
- m.AppDirs.append(dir)
- m.DirectoryDirs.append(dir)
- m.Name = os.path.basename(dir)
- m.NotInXml = True
-
- for item in os.listdir(dir):
- try:
- if item == ".directory":
- m.Directories.append(item)
- elif os.path.isdir(os.path.join(dir,item)):
- m.addSubmenu(__mergeLegacyDir(os.path.join(dir,item), prefix, filename, parent))
- except UnicodeDecodeError:
- continue
-
- tmp["cache"].addMenuEntries([dir],prefix, True)
- menuentries = tmp["cache"].getMenuEntries([dir], False)
-
- for menuentry in menuentries:
- categories = menuentry.Categories
- if len(categories) == 0:
- r = Rule("Include")
- r.parseFilename(menuentry.DesktopFileID)
- r.compile()
- m.Rules.append(r)
- if not dir in parent.AppDirs:
- categories.append("Legacy")
- menuentry.Categories = categories
-
- return m
-
-def __parseKDELegacyDirs(filename, parent):
- f=os.popen3("kde-config --path apps")
- output = f[1].readlines()
- try:
- for dir in output[0].split(":"):
- __parseLegacyDir(dir,"kde", filename, parent)
- except IndexError:
- pass
-
-# remove duplicate entries from a list
-def __removeDuplicates(list):
- set = {}
- list.reverse()
- list = [set.setdefault(e,e) for e in list if e not in set]
- list.reverse()
- return list
-
-# Finally generate the menu
-def __genmenuNotOnlyAllocated(menu):
- for submenu in menu.Submenus:
- __genmenuNotOnlyAllocated(submenu)
-
- if menu.OnlyUnallocated == False:
- tmp["cache"].addMenuEntries(menu.AppDirs)
- menuentries = []
- for rule in menu.Rules:
- menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 1)
- for menuentry in menuentries:
- if menuentry.Add == True:
- menuentry.Parents.append(menu)
- menuentry.Add = False
- menuentry.Allocated = True
- menu.MenuEntries.append(menuentry)
-
-def __genmenuOnlyAllocated(menu):
- for submenu in menu.Submenus:
- __genmenuOnlyAllocated(submenu)
-
- if menu.OnlyUnallocated == True:
- tmp["cache"].addMenuEntries(menu.AppDirs)
- menuentries = []
- for rule in menu.Rules:
- menuentries = rule.do(tmp["cache"].getMenuEntries(menu.AppDirs), rule.Type, 2)
- for menuentry in menuentries:
- if menuentry.Add == True:
- menuentry.Parents.append(menu)
- # menuentry.Add = False
- # menuentry.Allocated = True
- menu.MenuEntries.append(menuentry)
-
-# And sorting ...
-def sort(menu):
- menu.Entries = []
- menu.Visible = 0
-
- for submenu in menu.Submenus:
- sort(submenu)
-
- tmp_s = []
- tmp_e = []
-
- for order in menu.Layout.order:
- if order[0] == "Filename":
- tmp_e.append(order[1])
- elif order[0] == "Menuname":
- tmp_s.append(order[1])
-
- for order in menu.Layout.order:
- if order[0] == "Separator":
- separator = Separator(menu)
- if len(menu.Entries) > 0 and isinstance(menu.Entries[-1], Separator):
- separator.Show = False
- menu.Entries.append(separator)
- elif order[0] == "Filename":
- menuentry = menu.getMenuEntry(order[1])
- if menuentry:
- menu.Entries.append(menuentry)
- elif order[0] == "Menuname":
- submenu = menu.getMenu(order[1])
- if submenu:
- __parse_inline(submenu, menu)
- elif order[0] == "Merge":
- if order[1] == "files" or order[1] == "all":
- menu.MenuEntries.sort()
- for menuentry in menu.MenuEntries:
- if menuentry not in tmp_e:
- menu.Entries.append(menuentry)
- elif order[1] == "menus" or order[1] == "all":
- menu.Submenus.sort()
- for submenu in menu.Submenus:
- if submenu.Name not in tmp_s:
- __parse_inline(submenu, menu)
-
- # getHidden / NoDisplay / OnlyShowIn / NotOnlyShowIn / Deleted / NoExec
- for entry in menu.Entries:
- entry.Show = True
- menu.Visible += 1
- if isinstance(entry, Menu):
- if entry.Deleted == True:
- entry.Show = "Deleted"
- menu.Visible -= 1
- elif isinstance(entry.Directory, MenuEntry):
- if entry.Directory.DesktopEntry.getNoDisplay() == True:
- entry.Show = "NoDisplay"
- menu.Visible -= 1
- elif entry.Directory.DesktopEntry.getHidden() == True:
- entry.Show = "Hidden"
- menu.Visible -= 1
- elif isinstance(entry, MenuEntry):
- if entry.DesktopEntry.getNoDisplay() == True:
- entry.Show = "NoDisplay"
- menu.Visible -= 1
- elif entry.DesktopEntry.getHidden() == True:
- entry.Show = "Hidden"
- menu.Visible -= 1
- elif entry.DesktopEntry.getTryExec() and not __try_exec(entry.DesktopEntry.getTryExec()):
- entry.Show = "NoExec"
- menu.Visible -= 1
- elif xdg.Config.windowmanager:
- if ( entry.DesktopEntry.getOnlyShowIn() != [] and xdg.Config.windowmanager not in entry.DesktopEntry.getOnlyShowIn() ) \
- or xdg.Config.windowmanager in entry.DesktopEntry.getNotShowIn():
- entry.Show = "NotShowIn"
- menu.Visible -= 1
- elif isinstance(entry,Separator):
- menu.Visible -= 1
-
- # remove separators at the beginning and at the end
- if len(menu.Entries) > 0:
- if isinstance(menu.Entries[0], Separator):
- menu.Entries[0].Show = False
- if len(menu.Entries) > 1:
- if isinstance(menu.Entries[-1], Separator):
- menu.Entries[-1].Show = False
-
- # show_empty tag
- for entry in menu.Entries:
- if isinstance(entry,Menu) and entry.Layout.show_empty == "false" and entry.Visible == 0:
- entry.Show = "Empty"
- menu.Visible -= 1
- if entry.NotInXml == True:
- menu.Entries.remove(entry)
-
-def __try_exec(executable):
- paths = os.environ['PATH'].split(os.pathsep)
- if not os.path.isfile(executable):
- for p in paths:
- f = os.path.join(p, executable)
- if os.path.isfile(f):
- if os.access(f, os.X_OK):
- return True
- else:
- if os.access(executable, os.X_OK):
- return True
- return False
-
-# inline tags
-def __parse_inline(submenu, menu):
- if submenu.Layout.inline == "true":
- if len(submenu.Entries) == 1 and submenu.Layout.inline_alias == "true":
- menuentry = submenu.Entries[0]
- menuentry.DesktopEntry.set("Name", submenu.getName(), locale = True)
- menuentry.DesktopEntry.set("GenericName", submenu.getGenericName(), locale = True)
- menuentry.DesktopEntry.set("Comment", submenu.getComment(), locale = True)
- menu.Entries.append(menuentry)
- elif len(submenu.Entries) <= submenu.Layout.inline_limit or submenu.Layout.inline_limit == 0:
- if submenu.Layout.inline_header == "true":
- header = Header(submenu.getName(), submenu.getGenericName(), submenu.getComment())
- menu.Entries.append(header)
- for entry in submenu.Entries:
- menu.Entries.append(entry)
- else:
- menu.Entries.append(submenu)
- else:
- menu.Entries.append(submenu)
-
-class MenuEntryCache:
- "Class to cache Desktop Entries"
- def __init__(self):
- self.cacheEntries = {}
- self.cacheEntries['legacy'] = []
- self.cache = {}
-
- def addMenuEntries(self, dirs, prefix="", legacy=False):
- for dir in dirs:
- if not self.cacheEntries.has_key(dir):
- self.cacheEntries[dir] = []
- self.__addFiles(dir, "", prefix, legacy)
-
- def __addFiles(self, dir, subdir, prefix, legacy):
- for item in os.listdir(os.path.join(dir,subdir)):
- if os.path.splitext(item)[1] == ".desktop":
- try:
- menuentry = MenuEntry(os.path.join(subdir,item), dir, prefix)
- except ParsingError:
- continue
-
- self.cacheEntries[dir].append(menuentry)
- if legacy == True:
- self.cacheEntries['legacy'].append(menuentry)
- elif os.path.isdir(os.path.join(dir,subdir,item)) and legacy == False:
- self.__addFiles(dir, os.path.join(subdir,item), prefix, legacy)
-
- def getMenuEntries(self, dirs, legacy=True):
- list = []
- ids = []
- # handle legacy items
- appdirs = dirs[:]
- if legacy == True:
- appdirs.append("legacy")
- # cache the results again
- key = "".join(appdirs)
- try:
- return self.cache[key]
- except KeyError:
- pass
- for dir in appdirs:
- for menuentry in self.cacheEntries[dir]:
- try:
- if menuentry.DesktopFileID not in ids:
- ids.append(menuentry.DesktopFileID)
- list.append(menuentry)
- elif menuentry.getType() == "System":
- # FIXME: This is only 99% correct, but still...
- i = list.index(menuentry)
- e = list[i]
- if e.getType() == "User":
- e.Original = menuentry
- except UnicodeDecodeError:
- continue
- self.cache[key] = list
- return list
+++ /dev/null
-""" CLass to edit XDG Menus """
-
-from xdg.Menu import *
-from xdg.BaseDirectory import *
-from xdg.Exceptions import *
-from xdg.DesktopEntry import *
-from xdg.Config import *
-
-import xml.dom.minidom
-import os
-import re
-
-# XML-Cleanups: Move / Exclude
-# FIXME: proper reverte/delete
-# FIXME: pass AppDirs/DirectoryDirs around in the edit/move functions
-# FIXME: catch Exceptions
-# FIXME: copy functions
-# FIXME: More Layout stuff
-# FIXME: unod/redo function / remove menu...
-# FIXME: Advanced MenuEditing Stuff: LegacyDir/MergeFile
-# Complex Rules/Deleted/OnlyAllocated/AppDirs/DirectoryDirs
-
-class MenuEditor:
- def __init__(self, menu=None, filename=None, root=False):
- self.menu = None
- self.filename = None
- self.doc = None
- self.parse(menu, filename, root)
-
- # fix for creating two menus with the same name on the fly
- self.filenames = []
-
- def parse(self, menu=None, filename=None, root=False):
- if root == True:
- setRootMode(True)
-
- if isinstance(menu, Menu):
- self.menu = menu
- elif menu:
- self.menu = parse(menu)
- else:
- self.menu = parse()
-
- if root == True:
- self.filename = self.menu.Filename
- elif filename:
- self.filename = filename
- else:
- self.filename = os.path.join(xdg_config_dirs[0], "menus", os.path.split(self.menu.Filename)[1])
-
- try:
- self.doc = xml.dom.minidom.parse(self.filename)
- except IOError:
- self.doc = xml.dom.minidom.parseString('<!DOCTYPE Menu PUBLIC "-//freedesktop//DTD Menu 1.0//EN" "http://standards.freedesktop.org/menu-spec/menu-1.0.dtd"><Menu><Name>Applications</Name><MergeFile type="parent">'+self.menu.Filename+'</MergeFile></Menu>')
- except xml.parsers.expat.ExpatError:
- raise ParsingError('Not a valid .menu file', self.filename)
-
- self.__remove_whilespace_nodes(self.doc)
-
- def save(self):
- self.__saveEntries(self.menu)
- self.__saveMenu()
-
- def createMenuEntry(self, parent, name, command=None, genericname=None, comment=None, icon=None, terminal=None, after=None, before=None):
- menuentry = MenuEntry(self.__getFileName(name, ".desktop"))
- menuentry = self.editMenuEntry(menuentry, name, genericname, comment, command, icon, terminal)
-
- self.__addEntry(parent, menuentry, after, before)
-
- sort(self.menu)
-
- return menuentry
-
- def createMenu(self, parent, name, genericname=None, comment=None, icon=None, after=None, before=None):
- menu = Menu()
-
- menu.Parent = parent
- menu.Depth = parent.Depth + 1
- menu.Layout = parent.DefaultLayout
- menu.DefaultLayout = parent.DefaultLayout
-
- menu = self.editMenu(menu, name, genericname, comment, icon)
-
- self.__addEntry(parent, menu, after, before)
-
- sort(self.menu)
-
- return menu
-
- def createSeparator(self, parent, after=None, before=None):
- separator = Separator(parent)
-
- self.__addEntry(parent, separator, after, before)
-
- sort(self.menu)
-
- return separator
-
- def moveMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
- self.__deleteEntry(oldparent, menuentry, after, before)
- self.__addEntry(newparent, menuentry, after, before)
-
- sort(self.menu)
-
- return menuentry
-
- def moveMenu(self, menu, oldparent, newparent, after=None, before=None):
- self.__deleteEntry(oldparent, menu, after, before)
- self.__addEntry(newparent, menu, after, before)
-
- root_menu = self.__getXmlMenu(self.menu.Name)
- if oldparent.getPath(True) != newparent.getPath(True):
- self.__addXmlMove(root_menu, os.path.join(oldparent.getPath(True), menu.Name), os.path.join(newparent.getPath(True), menu.Name))
-
- sort(self.menu)
-
- return menu
-
- def moveSeparator(self, separator, parent, after=None, before=None):
- self.__deleteEntry(parent, separator, after, before)
- self.__addEntry(parent, separator, after, before)
-
- sort(self.menu)
-
- return separator
-
- def copyMenuEntry(self, menuentry, oldparent, newparent, after=None, before=None):
- self.__addEntry(newparent, menuentry, after, before)
-
- sort(self.menu)
-
- return menuentry
-
- def editMenuEntry(self, menuentry, name=None, genericname=None, comment=None, command=None, icon=None, terminal=None, nodisplay=None, hidden=None):
- deskentry = menuentry.DesktopEntry
-
- if name:
- if not deskentry.hasKey("Name"):
- deskentry.set("Name", name)
- deskentry.set("Name", name, locale = True)
- if comment:
- if not deskentry.hasKey("Comment"):
- deskentry.set("Comment", comment)
- deskentry.set("Comment", comment, locale = True)
- if genericname:
- if not deskentry.hasKey("GnericNe"):
- deskentry.set("GenericName", genericname)
- deskentry.set("GenericName", genericname, locale = True)
- if command:
- deskentry.set("Exec", command)
- if icon:
- deskentry.set("Icon", icon)
-
- if terminal == True:
- deskentry.set("Terminal", "true")
- elif terminal == False:
- deskentry.set("Terminal", "false")
-
- if nodisplay == True:
- deskentry.set("NoDisplay", "true")
- elif nodisplay == False:
- deskentry.set("NoDisplay", "false")
-
- if hidden == True:
- deskentry.set("Hidden", "true")
- elif hidden == False:
- deskentry.set("Hidden", "false")
-
- menuentry.updateAttributes()
-
- if len(menuentry.Parents) > 0:
- sort(self.menu)
-
- return menuentry
-
- def editMenu(self, menu, name=None, genericname=None, comment=None, icon=None, nodisplay=None, hidden=None):
- # Hack for legacy dirs
- if isinstance(menu.Directory, MenuEntry) and menu.Directory.Filename == ".directory":
- xml_menu = self.__getXmlMenu(menu.getPath(True, True))
- self.__addXmlTextElement(xml_menu, 'Directory', menu.Name + ".directory")
- menu.Directory.setAttributes(menu.Name + ".directory")
- # Hack for New Entries
- elif not isinstance(menu.Directory, MenuEntry):
- if not name:
- name = menu.Name
- filename = self.__getFileName(name, ".directory").replace("/", "")
- if not menu.Name:
- menu.Name = filename.replace(".directory", "")
- xml_menu = self.__getXmlMenu(menu.getPath(True, True))
- self.__addXmlTextElement(xml_menu, 'Directory', filename)
- menu.Directory = MenuEntry(filename)
-
- deskentry = menu.Directory.DesktopEntry
-
- if name:
- if not deskentry.hasKey("Name"):
- deskentry.set("Name", name)
- deskentry.set("Name", name, locale = True)
- if genericname:
- if not deskentry.hasKey("GenericName"):
- deskentry.set("GenericName", genericname)
- deskentry.set("GenericName", genericname, locale = True)
- if comment:
- if not deskentry.hasKey("Comment"):
- deskentry.set("Comment", comment)
- deskentry.set("Comment", comment, locale = True)
- if icon:
- deskentry.set("Icon", icon)
-
- if nodisplay == True:
- deskentry.set("NoDisplay", "true")
- elif nodisplay == False:
- deskentry.set("NoDisplay", "false")
-
- if hidden == True:
- deskentry.set("Hidden", "true")
- elif hidden == False:
- deskentry.set("Hidden", "false")
-
- menu.Directory.updateAttributes()
-
- if isinstance(menu.Parent, Menu):
- sort(self.menu)
-
- return menu
-
- def hideMenuEntry(self, menuentry):
- self.editMenuEntry(menuentry, nodisplay = True)
-
- def unhideMenuEntry(self, menuentry):
- self.editMenuEntry(menuentry, nodisplay = False, hidden = False)
-
- def hideMenu(self, menu):
- self.editMenu(menu, nodisplay = True)
-
- def unhideMenu(self, menu):
- self.editMenu(menu, nodisplay = False, hidden = False)
- xml_menu = self.__getXmlMenu(menu.getPath(True,True), False)
- for node in self.__getXmlNodesByName(["Deleted", "NotDeleted"], xml_menu):
- node.parentNode.removeChild(node)
-
- def deleteMenuEntry(self, menuentry):
- if self.getAction(menuentry) == "delete":
- self.__deleteFile(menuentry.DesktopEntry.filename)
- for parent in menuentry.Parents:
- self.__deleteEntry(parent, menuentry)
- sort(self.menu)
- return menuentry
-
- def revertMenuEntry(self, menuentry):
- if self.getAction(menuentry) == "revert":
- self.__deleteFile(menuentry.DesktopEntry.filename)
- menuentry.Original.Parents = []
- for parent in menuentry.Parents:
- index = parent.Entries.index(menuentry)
- parent.Entries[index] = menuentry.Original
- index = parent.MenuEntries.index(menuentry)
- parent.MenuEntries[index] = menuentry.Original
- menuentry.Original.Parents.append(parent)
- sort(self.menu)
- return menuentry
-
- def deleteMenu(self, menu):
- if self.getAction(menu) == "delete":
- self.__deleteFile(menu.Directory.DesktopEntry.filename)
- self.__deleteEntry(menu.Parent, menu)
- xml_menu = self.__getXmlMenu(menu.getPath(True, True))
- xml_menu.parentNode.removeChild(xml_menu)
- sort(self.menu)
- return menu
-
- def revertMenu(self, menu):
- if self.getAction(menu) == "revert":
- self.__deleteFile(menu.Directory.DesktopEntry.filename)
- menu.Directory = menu.Directory.Original
- sort(self.menu)
- return menu
-
- def deleteSeparator(self, separator):
- self.__deleteEntry(separator.Parent, separator, after=True)
-
- sort(self.menu)
-
- return separator
-
- """ Private Stuff """
- def getAction(self, entry):
- if isinstance(entry, Menu):
- if not isinstance(entry.Directory, MenuEntry):
- return "none"
- elif entry.Directory.getType() == "Both":
- return "revert"
- elif entry.Directory.getType() == "User" \
- and (len(entry.Submenus) + len(entry.MenuEntries)) == 0:
- return "delete"
-
- elif isinstance(entry, MenuEntry):
- if entry.getType() == "Both":
- return "revert"
- elif entry.getType() == "User":
- return "delete"
- else:
- return "none"
-
- return "none"
-
- def __saveEntries(self, menu):
- if not menu:
- menu = self.menu
- if isinstance(menu.Directory, MenuEntry):
- menu.Directory.save()
- for entry in menu.getEntries(hidden=True):
- if isinstance(entry, MenuEntry):
- entry.save()
- elif isinstance(entry, Menu):
- self.__saveEntries(entry)
-
- def __saveMenu(self):
- if not os.path.isdir(os.path.dirname(self.filename)):
- os.makedirs(os.path.dirname(self.filename))
- fd = open(self.filename, 'w')
- fd.write(re.sub("\n[\s]*([^\n<]*)\n[\s]*</", "\\1</", self.doc.toprettyxml().replace('<?xml version="1.0" ?>\n', '')))
- fd.close()
-
- def __getFileName(self, name, extension):
- postfix = 0
- while 1:
- if postfix == 0:
- filename = name + extension
- else:
- filename = name + "-" + str(postfix) + extension
- if extension == ".desktop":
- dir = "applications"
- elif extension == ".directory":
- dir = "desktop-directories"
- if not filename in self.filenames and not \
- os.path.isfile(os.path.join(xdg_data_dirs[0], dir, filename)):
- self.filenames.append(filename)
- break
- else:
- postfix += 1
-
- return filename
-
- def __getXmlMenu(self, path, create=True, element=None):
- if not element:
- element = self.doc
-
- if "/" in path:
- (name, path) = path.split("/", 1)
- else:
- name = path
- path = ""
-
- found = None
- for node in self.__getXmlNodesByName("Menu", element):
- for child in self.__getXmlNodesByName("Name", node):
- if child.childNodes[0].nodeValue == name:
- if path:
- found = self.__getXmlMenu(path, create, node)
- else:
- found = node
- break
- if found:
- break
- if not found and create == True:
- node = self.__addXmlMenuElement(element, name)
- if path:
- found = self.__getXmlMenu(path, create, node)
- else:
- found = node
-
- return found
-
- def __addXmlMenuElement(self, element, name):
- node = self.doc.createElement('Menu')
- self.__addXmlTextElement(node, 'Name', name)
- return element.appendChild(node)
-
- def __addXmlTextElement(self, element, name, text):
- node = self.doc.createElement(name)
- text = self.doc.createTextNode(text)
- node.appendChild(text)
- return element.appendChild(node)
-
- def __addXmlFilename(self, element, filename, type = "Include"):
- # remove old filenames
- for node in self.__getXmlNodesByName(["Include", "Exclude"], element):
- if node.childNodes[0].nodeName == "Filename" and node.childNodes[0].childNodes[0].nodeValue == filename:
- element.removeChild(node)
-
- # add new filename
- node = self.doc.createElement(type)
- node.appendChild(self.__addXmlTextElement(node, 'Filename', filename))
- return element.appendChild(node)
-
- def __addXmlMove(self, element, old, new):
- node = self.doc.createElement("Move")
- node.appendChild(self.__addXmlTextElement(node, 'Old', old))
- node.appendChild(self.__addXmlTextElement(node, 'New', new))
- return element.appendChild(node)
-
- def __addXmlLayout(self, element, layout):
- # remove old layout
- for node in self.__getXmlNodesByName("Layout", element):
- element.removeChild(node)
-
- # add new layout
- node = self.doc.createElement("Layout")
- for order in layout.order:
- if order[0] == "Separator":
- child = self.doc.createElement("Separator")
- node.appendChild(child)
- elif order[0] == "Filename":
- child = self.__addXmlTextElement(node, "Filename", order[1])
- elif order[0] == "Menuname":
- child = self.__addXmlTextElement(node, "Menuname", order[1])
- elif order[0] == "Merge":
- child = self.doc.createElement("Merge")
- child.setAttribute("type", order[1])
- node.appendChild(child)
- return element.appendChild(node)
-
- def __getXmlNodesByName(self, name, element):
- for child in element.childNodes:
- if child.nodeType == xml.dom.Node.ELEMENT_NODE and child.nodeName in name:
- yield child
-
- def __addLayout(self, parent):
- layout = Layout()
- layout.order = []
- layout.show_empty = parent.Layout.show_empty
- layout.inline = parent.Layout.inline
- layout.inline_header = parent.Layout.inline_header
- layout.inline_alias = parent.Layout.inline_alias
- layout.inline_limit = parent.Layout.inline_limit
-
- layout.order.append(["Merge", "menus"])
- for entry in parent.Entries:
- if isinstance(entry, Menu):
- layout.parseMenuname(entry.Name)
- elif isinstance(entry, MenuEntry):
- layout.parseFilename(entry.DesktopFileID)
- elif isinstance(entry, Separator):
- layout.parseSeparator()
- layout.order.append(["Merge", "files"])
-
- parent.Layout = layout
-
- return layout
-
- def __addEntry(self, parent, entry, after=None, before=None):
- if after or before:
- if after:
- index = parent.Entries.index(after) + 1
- elif before:
- index = parent.Entries.index(before)
- parent.Entries.insert(index, entry)
- else:
- parent.Entries.append(entry)
-
- xml_parent = self.__getXmlMenu(parent.getPath(True, True))
-
- if isinstance(entry, MenuEntry):
- parent.MenuEntries.append(entry)
- entry.Parents.append(parent)
- self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Include")
- elif isinstance(entry, Menu):
- parent.addSubmenu(entry)
-
- if after or before:
- self.__addLayout(parent)
- self.__addXmlLayout(xml_parent, parent.Layout)
-
- def __deleteEntry(self, parent, entry, after=None, before=None):
- parent.Entries.remove(entry)
-
- xml_parent = self.__getXmlMenu(parent.getPath(True, True))
-
- if isinstance(entry, MenuEntry):
- entry.Parents.remove(parent)
- parent.MenuEntries.remove(entry)
- self.__addXmlFilename(xml_parent, entry.DesktopFileID, "Exclude")
- elif isinstance(entry, Menu):
- parent.Submenus.remove(entry)
-
- if after or before:
- self.__addLayout(parent)
- self.__addXmlLayout(xml_parent, parent.Layout)
-
- def __deleteFile(self, filename):
- try:
- os.remove(filename)
- except OSError:
- pass
- try:
- self.filenames.remove(filename)
- except ValueError:
- pass
-
- def __remove_whilespace_nodes(self, node):
- remove_list = []
- for child in node.childNodes:
- if child.nodeType == xml.dom.minidom.Node.TEXT_NODE:
- child.data = child.data.strip()
- if not child.data.strip():
- remove_list.append(child)
- elif child.hasChildNodes():
- self.__remove_whilespace_nodes(child)
- for node in remove_list:
- node.parentNode.removeChild(node)
+++ /dev/null
-"""
-This module is based on a rox module (LGPL):
-
-http://cvs.sourceforge.net/viewcvs.py/rox/ROX-Lib2/python/rox/mime.py?rev=1.21&view=log
-
-This module provides access to the shared MIME database.
-
-types is a dictionary of all known MIME types, indexed by the type name, e.g.
-types['application/x-python']
-
-Applications can install information about MIME types by storing an
-XML file as <MIME>/packages/<application>.xml and running the
-update-mime-database command, which is provided by the freedesktop.org
-shared mime database package.
-
-See http://www.freedesktop.org/standards/shared-mime-info-spec/ for
-information about the format of these files.
-
-(based on version 0.13)
-"""
-
-import os
-import stat
-import fnmatch
-
-import xdg.BaseDirectory
-import xdg.Locale
-
-from xml.dom import Node, minidom, XML_NAMESPACE
-
-FREE_NS = 'http://www.freedesktop.org/standards/shared-mime-info'
-
-types = {} # Maps MIME names to type objects
-
-exts = None # Maps extensions to types
-globs = None # List of (glob, type) pairs
-literals = None # Maps liternal names to types
-magic = None
-
-def _get_node_data(node):
- """Get text of XML node"""
- return ''.join([n.nodeValue for n in node.childNodes]).strip()
-
-def lookup(media, subtype = None):
- "Get the MIMEtype object for this type, creating a new one if needed."
- if subtype is None and '/' in media:
- media, subtype = media.split('/', 1)
- if (media, subtype) not in types:
- types[(media, subtype)] = MIMEtype(media, subtype)
- return types[(media, subtype)]
-
-class MIMEtype:
- """Type holding data about a MIME type"""
- def __init__(self, media, subtype):
- "Don't use this constructor directly; use mime.lookup() instead."
- assert media and '/' not in media
- assert subtype and '/' not in subtype
- assert (media, subtype) not in types
-
- self.media = media
- self.subtype = subtype
- self._comment = None
-
- def _load(self):
- "Loads comment for current language. Use get_comment() instead."
- resource = os.path.join('mime', self.media, self.subtype + '.xml')
- for path in xdg.BaseDirectory.load_data_paths(resource):
- doc = minidom.parse(path)
- if doc is None:
- continue
- for comment in doc.documentElement.getElementsByTagNameNS(FREE_NS, 'comment'):
- lang = comment.getAttributeNS(XML_NAMESPACE, 'lang') or 'en'
- goodness = 1 + (lang in xdg.Locale.langs)
- if goodness > self._comment[0]:
- self._comment = (goodness, _get_node_data(comment))
- if goodness == 2: return
-
- # FIXME: add get_icon method
- def get_comment(self):
- """Returns comment for current language, loading it if needed."""
- # Should we ever reload?
- if self._comment is None:
- self._comment = (0, str(self))
- self._load()
- return self._comment[1]
-
- def __str__(self):
- return self.media + '/' + self.subtype
-
- def __repr__(self):
- return '[%s: %s]' % (self, self._comment or '(comment not loaded)')
-
-class MagicRule:
- def __init__(self, f):
- self.next=None
- self.prev=None
-
- #print line
- ind=''
- while True:
- c=f.read(1)
- if c=='>':
- break
- ind+=c
- if not ind:
- self.nest=0
- else:
- self.nest=int(ind)
-
- start=''
- while True:
- c=f.read(1)
- if c=='=':
- break
- start+=c
- self.start=int(start)
-
- hb=f.read(1)
- lb=f.read(1)
- self.lenvalue=ord(lb)+(ord(hb)<<8)
-
- self.value=f.read(self.lenvalue)
-
- c=f.read(1)
- if c=='&':
- self.mask=f.read(self.lenvalue)
- c=f.read(1)
- else:
- self.mask=None
-
- if c=='~':
- w=''
- while c!='+' and c!='\n':
- c=f.read(1)
- if c=='+' or c=='\n':
- break
- w+=c
-
- self.word=int(w)
- else:
- self.word=1
-
- if c=='+':
- r=''
- while c!='\n':
- c=f.read(1)
- if c=='\n':
- break
- r+=c
- #print r
- self.range=int(r)
- else:
- self.range=1
-
- if c!='\n':
- raise 'Malformed MIME magic line'
-
- def getLength(self):
- return self.start+self.lenvalue+self.range
-
- def appendRule(self, rule):
- if self.nest<rule.nest:
- self.next=rule
- rule.prev=self
-
- elif self.prev:
- self.prev.appendRule(rule)
-
- def match(self, buffer):
- if self.match0(buffer):
- if self.next:
- return self.next.match(buffer)
- return True
-
- def match0(self, buffer):
- l=len(buffer)
- for o in range(self.range):
- s=self.start+o
- e=s+self.lenvalue
- if l<e:
- return False
- if self.mask:
- test=''
- for i in range(self.lenvalue):
- c=ord(buffer[s+i]) & ord(self.mask[i])
- test+=chr(c)
- else:
- test=buffer[s:e]
-
- if test==self.value:
- return True
-
- def __repr__(self):
- return '<MagicRule %d>%d=[%d]%s&%s~%d+%d>' % (self.nest,
- self.start,
- self.lenvalue,
- `self.value`,
- `self.mask`,
- self.word,
- self.range)
-
-class MagicType:
- def __init__(self, mtype):
- self.mtype=mtype
- self.top_rules=[]
- self.last_rule=None
-
- def getLine(self, f):
- nrule=MagicRule(f)
-
- if nrule.nest and self.last_rule:
- self.last_rule.appendRule(nrule)
- else:
- self.top_rules.append(nrule)
-
- self.last_rule=nrule
-
- return nrule
-
- def match(self, buffer):
- for rule in self.top_rules:
- if rule.match(buffer):
- return self.mtype
-
- def __repr__(self):
- return '<MagicType %s>' % self.mtype
-
-class MagicDB:
- def __init__(self):
- self.types={} # Indexed by priority, each entry is a list of type rules
- self.maxlen=0
-
- def mergeFile(self, fname):
- f=file(fname, 'r')
- line=f.readline()
- if line!='MIME-Magic\0\n':
- raise 'Not a MIME magic file'
-
- while True:
- shead=f.readline()
- #print shead
- if not shead:
- break
- if shead[0]!='[' or shead[-2:]!=']\n':
- raise 'Malformed section heading'
- pri, tname=shead[1:-2].split(':')
- #print shead[1:-2]
- pri=int(pri)
- mtype=lookup(tname)
-
- try:
- ents=self.types[pri]
- except:
- ents=[]
- self.types[pri]=ents
-
- magictype=MagicType(mtype)
- #print tname
-
- #rline=f.readline()
- c=f.read(1)
- f.seek(-1, 1)
- while c and c!='[':
- rule=magictype.getLine(f)
- #print rule
- if rule and rule.getLength()>self.maxlen:
- self.maxlen=rule.getLength()
-
- c=f.read(1)
- f.seek(-1, 1)
-
- ents.append(magictype)
- #self.types[pri]=ents
- if not c:
- break
-
- def match_data(self, data, max_pri=100, min_pri=0):
- pris=self.types.keys()
- pris.sort(lambda a, b: -cmp(a, b))
- for pri in pris:
- #print pri, max_pri, min_pri
- if pri>max_pri:
- continue
- if pri<min_pri:
- break
- for type in self.types[pri]:
- m=type.match(data)
- if m:
- return m
-
-
- def match(self, path, max_pri=100, min_pri=0):
- try:
- buf=file(path, 'r').read(self.maxlen)
- return self.match_data(buf, max_pri, min_pri)
- except:
- pass
-
- return None
-
- def __repr__(self):
- return '<MagicDB %s>' % self.types
-
-
-# Some well-known types
-text = lookup('text', 'plain')
-inode_block = lookup('inode', 'blockdevice')
-inode_char = lookup('inode', 'chardevice')
-inode_dir = lookup('inode', 'directory')
-inode_fifo = lookup('inode', 'fifo')
-inode_socket = lookup('inode', 'socket')
-inode_symlink = lookup('inode', 'symlink')
-inode_door = lookup('inode', 'door')
-app_exe = lookup('application', 'executable')
-
-_cache_uptodate = False
-
-def _cache_database():
- global exts, globs, literals, magic, _cache_uptodate
-
- _cache_uptodate = True
-
- exts = {} # Maps extensions to types
- globs = [] # List of (glob, type) pairs
- literals = {} # Maps liternal names to types
- magic = MagicDB()
-
- def _import_glob_file(path):
- """Loads name matching information from a MIME directory."""
- for line in file(path):
- if line.startswith('#'): continue
- line = line[:-1]
-
- type_name, pattern = line.split(':', 1)
- mtype = lookup(type_name)
-
- if pattern.startswith('*.'):
- rest = pattern[2:]
- if not ('*' in rest or '[' in rest or '?' in rest):
- exts[rest] = mtype
- continue
- if '*' in pattern or '[' in pattern or '?' in pattern:
- globs.append((pattern, mtype))
- else:
- literals[pattern] = mtype
-
- for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'globs')):
- _import_glob_file(path)
- for path in xdg.BaseDirectory.load_data_paths(os.path.join('mime', 'magic')):
- magic.mergeFile(path)
-
- # Sort globs by length
- globs.sort(lambda a, b: cmp(len(b[0]), len(a[0])))
-
-def get_type_by_name(path):
- """Returns type of file by its name, or None if not known"""
- if not _cache_uptodate:
- _cache_database()
-
- leaf = os.path.basename(path)
- if leaf in literals:
- return literals[leaf]
-
- lleaf = leaf.lower()
- if lleaf in literals:
- return literals[lleaf]
-
- ext = leaf
- while 1:
- p = ext.find('.')
- if p < 0: break
- ext = ext[p + 1:]
- if ext in exts:
- return exts[ext]
- ext = lleaf
- while 1:
- p = ext.find('.')
- if p < 0: break
- ext = ext[p+1:]
- if ext in exts:
- return exts[ext]
- for (glob, mime_type) in globs:
- if fnmatch.fnmatch(leaf, glob):
- return mime_type
- if fnmatch.fnmatch(lleaf, glob):
- return mime_type
- return None
-
-def get_type_by_contents(path, max_pri=100, min_pri=0):
- """Returns type of file by its contents, or None if not known"""
- if not _cache_uptodate:
- _cache_database()
-
- return magic.match(path, max_pri, min_pri)
-
-def get_type_by_data(data, max_pri=100, min_pri=0):
- """Returns type of the data"""
- if not _cache_uptodate:
- _cache_database()
-
- return magic.match_data(data, max_pri, min_pri)
-
-def get_type(path, follow=1, name_pri=100):
- """Returns type of file indicated by path.
- path - pathname to check (need not exist)
- follow - when reading file, follow symbolic links
- name_pri - Priority to do name matches. 100=override magic"""
- if not _cache_uptodate:
- _cache_database()
-
- try:
- if follow:
- st = os.stat(path)
- else:
- st = os.lstat(path)
- except:
- t = get_type_by_name(path)
- return t or text
-
- if stat.S_ISREG(st.st_mode):
- t = get_type_by_contents(path, min_pri=name_pri)
- if not t: t = get_type_by_name(path)
- if not t: t = get_type_by_contents(path, max_pri=name_pri)
- if t is None:
- if stat.S_IMODE(st.st_mode) & 0111:
- return app_exe
- else:
- return text
- return t
- elif stat.S_ISDIR(st.st_mode): return inode_dir
- elif stat.S_ISCHR(st.st_mode): return inode_char
- elif stat.S_ISBLK(st.st_mode): return inode_block
- elif stat.S_ISFIFO(st.st_mode): return inode_fifo
- elif stat.S_ISLNK(st.st_mode): return inode_symlink
- elif stat.S_ISSOCK(st.st_mode): return inode_socket
- return inode_door
-
-def install_mime_info(application, package_file):
- """Copy 'package_file' as ~/.local/share/mime/packages/<application>.xml.
- If package_file is None, install <app_dir>/<application>.xml.
- If already installed, does nothing. May overwrite an existing
- file with the same name (if the contents are different)"""
- application += '.xml'
-
- new_data = file(package_file).read()
-
- # See if the file is already installed
- package_dir = os.path.join('mime', 'packages')
- resource = os.path.join(package_dir, application)
- for x in xdg.BaseDirectory.load_data_paths(resource):
- try:
- old_data = file(x).read()
- except:
- continue
- if old_data == new_data:
- return # Already installed
-
- global _cache_uptodate
- _cache_uptodate = False
-
- # Not already installed; add a new copy
- # Create the directory structure...
- new_file = os.path.join(xdg.BaseDirectory.save_data_path(package_dir), application)
-
- # Write the file...
- file(new_file, 'w').write(new_data)
-
- # Update the database...
- command = 'update-mime-database'
- if os.spawnlp(os.P_WAIT, command, command, xdg.BaseDirectory.save_data_path('mime')):
- os.unlink(new_file)
- raise Exception("The '%s' command returned an error code!\n" \
- "Make sure you have the freedesktop.org shared MIME package:\n" \
- "http://standards.freedesktop.org/shared-mime-info/") % command
+++ /dev/null
-"""
-Implementation of the XDG Recent File Storage Specification Version 0.2
-http://standards.freedesktop.org/recent-file-spec
-"""
-
-import xml.dom.minidom, xml.sax.saxutils
-import os, time, fcntl
-from xdg.Exceptions import *
-
-class RecentFiles:
- def __init__(self):
- self.RecentFiles = []
- self.filename = ""
-
- def parse(self, filename=None):
- if not filename:
- filename = os.path.join(os.getenv("HOME"), ".recently-used")
-
- try:
- doc = xml.dom.minidom.parse(filename)
- except IOError:
- raise ParsingError('File not found', filename)
- except xml.parsers.expat.ExpatError:
- raise ParsingError('Not a valid .menu file', filename)
-
- self.filename = filename
-
- for child in doc.childNodes:
- if child.nodeType == xml.dom.Node.ELEMENT_NODE:
- if child.tagName == "RecentFiles":
- for recent in child.childNodes:
- if recent.nodeType == xml.dom.Node.ELEMENT_NODE:
- if recent.tagName == "RecentItem":
- self.__parseRecentItem(recent)
-
- self.sort()
-
- def __parseRecentItem(self, item):
- recent = RecentFile()
- self.RecentFiles.append(recent)
-
- for attribute in item.childNodes:
- if attribute.nodeType == xml.dom.Node.ELEMENT_NODE:
- if attribute.tagName == "URI":
- recent.URI = attribute.childNodes[0].nodeValue
- elif attribute.tagName == "Mime-Type":
- recent.MimeType = attribute.childNodes[0].nodeValue
- elif attribute.tagName == "Timestamp":
- recent.Timestamp = attribute.childNodes[0].nodeValue
- elif attribute.tagName == "Private":
- recent.Prviate = True
- elif attribute.tagName == "Groups":
-
- for group in attribute.childNodes:
- if group.nodeType == xml.dom.Node.ELEMENT_NODE:
- if group.tagName == "Group":
- recent.Groups.append(group.childNodes[0].nodeValue)
-
- def write(self, filename=None):
- if not filename and not self.filename:
- raise ParsingError('File not found', filename)
- elif not filename:
- filename = self.filename
-
- f = open(filename, "w")
- fcntl.lockf(f, fcntl.LOCK_EX)
- f.write('<?xml version="1.0"?>\n')
- f.write("<RecentFiles>\n")
-
- for r in self.RecentFiles:
- f.write(" <RecentItem>\n")
- f.write(" <URI>%s</URI>\n" % xml.sax.saxutils.escape(r.URI))
- f.write(" <Mime-Type>%s</Mime-Type>\n" % r.MimeType)
- f.write(" <Timestamp>%s</Timestamp>\n" % r.Timestamp)
- if r.Private == True:
- f.write(" <Private/>\n")
- if len(r.Groups) > 0:
- f.write(" <Groups>\n")
- for group in r.Groups:
- f.write(" <Group>%s</Group>\n" % group)
- f.write(" </Groups>\n")
- f.write(" </RecentItem>\n")
-
- f.write("</RecentFiles>\n")
- fcntl.lockf(f, fcntl.LOCK_UN)
- f.close()
-
- def getFiles(self, mimetypes=None, groups=None, limit=0):
- tmp = []
- i = 0
- for item in self.RecentFiles:
- if groups:
- for group in groups:
- if group in item.Groups:
- tmp.append(item)
- i += 1
- elif mimetypes:
- for mimetype in mimetypes:
- if mimetype == item.MimeType:
- tmp.append(item)
- i += 1
- else:
- if item.Private == False:
- tmp.append(item)
- i += 1
- if limit != 0 and i == limit:
- break
-
- return tmp
-
- def addFile(self, item, mimetype, groups=None, private=False):
- # check if entry already there
- if item in self.RecentFiles:
- index = self.RecentFiles.index(item)
- recent = self.RecentFiles[index]
- else:
- # delete if more then 500 files
- if len(self.RecentFiles) == 500:
- self.RecentFiles.pop()
- # add entry
- recent = RecentFile()
- self.RecentFiles.append(recent)
-
- recent.URI = item
- recent.MimeType = mimetype
- recent.Timestamp = int(time.time())
- recent.Private = private
- recent.Groups = groups
-
- self.sort()
-
- def deleteFile(self, item):
- if item in self.RecentFiles:
- self.RecentFiles.remove(item)
-
- def sort(self):
- self.RecentFiles.sort()
- self.RecentFiles.reverse()
-
-
-class RecentFile:
- def __init__(self):
- self.URI = ""
- self.MimeType = ""
- self.Timestamp = ""
- self.Private = False
- self.Groups = []
-
- def __cmp__(self, other):
- return cmp(self.Timestamp, other.Timestamp)
-
- def __eq__(self, other):
- if self.URI == str(other):
- return True
- else:
- return False
-
- def __str__(self):
- return self.URI
+++ /dev/null
-__all__ = [ "BaseDirectory", "DesktopEntry", "Menu", "Exceptions", "IniFile", "IconTheme", "Locale", "Config", "Mime", "RecentFiles", "MenuEditor" ]