From: Stefanos Harhalakis Date: Sun, 20 Nov 2011 13:05:25 +0000 (+0000) Subject: move drlaunch in drlaunch X-Git-Url: http://git.maemo.org/git/?p=drlaunch;a=commitdiff_plain;h=94695a8c9a3ebf78e1f4c32d854e6b5a163d4667 move drlaunch in drlaunch --- diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 892a13d..0000000 --- a/LICENSE +++ /dev/null @@ -1,677 +0,0 @@ - - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. - - diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index a8bc4ad..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include README LICENSE drlaunch_widget.py -recursive-include src *.py -recursive-include misc * diff --git a/README b/README deleted file mode 100644 index e69de29..0000000 diff --git a/drlaunch/LICENSE b/drlaunch/LICENSE new file mode 100644 index 0000000..892a13d --- /dev/null +++ b/drlaunch/LICENSE @@ -0,0 +1,677 @@ + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. + + diff --git a/drlaunch/MANIFEST.in b/drlaunch/MANIFEST.in new file mode 100644 index 0000000..a8bc4ad --- /dev/null +++ b/drlaunch/MANIFEST.in @@ -0,0 +1,3 @@ +include README LICENSE drlaunch_widget.py +recursive-include src *.py +recursive-include misc * diff --git a/drlaunch/README b/drlaunch/README new file mode 100644 index 0000000..e69de29 diff --git a/drlaunch/misc/drlaunch-48.base64 b/drlaunch/misc/drlaunch-48.base64 new file mode 100644 index 0000000..47fd8d7 --- /dev/null +++ b/drlaunch/misc/drlaunch-48.base64 @@ -0,0 +1,124 @@ +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== +==== diff --git a/drlaunch/misc/drlaunch-48.png b/drlaunch/misc/drlaunch-48.png new file mode 100644 index 0000000..9c30d7d Binary files /dev/null and b/drlaunch/misc/drlaunch-48.png differ diff --git a/drlaunch/misc/drlaunch-64.png b/drlaunch/misc/drlaunch-64.png new file mode 100644 index 0000000..0091ace Binary files /dev/null and b/drlaunch/misc/drlaunch-64.png differ diff --git a/drlaunch/misc/drlaunch.desktop b/drlaunch/misc/drlaunch.desktop new file mode 100644 index 0000000..71434fc --- /dev/null +++ b/drlaunch/misc/drlaunch.desktop @@ -0,0 +1,6 @@ +[Desktop Entry] +Name=DrLaunch +Comment=Application launcer widget with portrait mode +Type=python +X-Path=drlaunch_widget.py +X-Multiple=true diff --git a/drlaunch/misc/mkico b/drlaunch/misc/mkico new file mode 100755 index 0000000..d86ff34 --- /dev/null +++ b/drlaunch/misc/mkico @@ -0,0 +1,2 @@ +#convert drlaunch-64x64.png -scale 48x48 -depth 8 drlaunch-48x48.png +uuencode -m drlaunch-48.png drlaunch-48.png > drlaunch-48.base64 diff --git a/drlaunch/mkdist b/drlaunch/mkdist new file mode 100755 index 0000000..9b59c08 --- /dev/null +++ b/drlaunch/mkdist @@ -0,0 +1,22 @@ +#!/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 + diff --git a/drlaunch/sdist b/drlaunch/sdist new file mode 100755 index 0000000..de1476b --- /dev/null +++ b/drlaunch/sdist @@ -0,0 +1,18 @@ +#!/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 + diff --git a/drlaunch/setup.py b/drlaunch/setup.py new file mode 100755 index 0000000..4ec6af6 --- /dev/null +++ b/drlaunch/setup.py @@ -0,0 +1,45 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/.gitignore b/drlaunch/src/.gitignore new file mode 100644 index 0000000..34fcef1 --- /dev/null +++ b/drlaunch/src/.gitignore @@ -0,0 +1,2 @@ +*.pyo +*.pyc diff --git a/drlaunch/src/0.py b/drlaunch/src/0.py new file mode 100755 index 0000000..2b790f5 --- /dev/null +++ b/drlaunch/src/0.py @@ -0,0 +1,26 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/__init__.py b/drlaunch/src/__init__.py new file mode 100755 index 0000000..2b790f5 --- /dev/null +++ b/drlaunch/src/__init__.py @@ -0,0 +1,26 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/about.py b/drlaunch/src/about.py new file mode 100755 index 0000000..6f26ccc --- /dev/null +++ b/drlaunch/src/about.py @@ -0,0 +1,91 @@ +#!/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 . +# +# $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 +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 + +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 . + +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: + diff --git a/drlaunch/src/about0.py b/drlaunch/src/about0.py new file mode 100644 index 0000000..eeb6ae3 --- /dev/null +++ b/drlaunch/src/about0.py @@ -0,0 +1,156 @@ +#!/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 . +# + +# Python implementation of HeAboutDialog from hildon-extras +# Copyright (c) 2010-04-11 Thomas Perl + +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() + diff --git a/drlaunch/src/apps.py b/drlaunch/src/apps.py new file mode 100755 index 0000000..1a7336f --- /dev/null +++ b/drlaunch/src/apps.py @@ -0,0 +1,146 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/config.py b/drlaunch/src/config.py new file mode 100755 index 0000000..50ae471 --- /dev/null +++ b/drlaunch/src/config.py @@ -0,0 +1,363 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/drlaunch_widget.py b/drlaunch/src/drlaunch_widget.py new file mode 100755 index 0000000..c0f1f2a --- /dev/null +++ b/drlaunch/src/drlaunch_widget.py @@ -0,0 +1,58 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/icon.py b/drlaunch/src/icon.py new file mode 100755 index 0000000..c4ea529 --- /dev/null +++ b/drlaunch/src/icon.py @@ -0,0 +1,450 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/icongrid.py b/drlaunch/src/icongrid.py new file mode 100755 index 0000000..96588d5 --- /dev/null +++ b/drlaunch/src/icongrid.py @@ -0,0 +1,488 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/icons.py b/drlaunch/src/icons.py new file mode 100755 index 0000000..808d922 --- /dev/null +++ b/drlaunch/src/icons.py @@ -0,0 +1,254 @@ +#!/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 . +# +# $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[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 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: + diff --git a/drlaunch/src/iconw.py b/drlaunch/src/iconw.py new file mode 100644 index 0000000..f7f13e7 --- /dev/null +++ b/drlaunch/src/iconw.py @@ -0,0 +1,125 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/launcher.py b/drlaunch/src/launcher.py new file mode 100755 index 0000000..36d14bc --- /dev/null +++ b/drlaunch/src/launcher.py @@ -0,0 +1,51 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/portrait.py b/drlaunch/src/portrait.py new file mode 100644 index 0000000..7ea8b09 --- /dev/null +++ b/drlaunch/src/portrait.py @@ -0,0 +1,242 @@ +# -*- 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 . +# + +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: diff --git a/drlaunch/src/portrait.py.orig b/drlaunch/src/portrait.py.orig new file mode 100755 index 0000000..8cefa3e --- /dev/null +++ b/drlaunch/src/portrait.py.orig @@ -0,0 +1,211 @@ +# -*- 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 . +# + +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 + diff --git a/drlaunch/src/sig.py b/drlaunch/src/sig.py new file mode 100644 index 0000000..f7cf115 --- /dev/null +++ b/drlaunch/src/sig.py @@ -0,0 +1,59 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/widget.py b/drlaunch/src/widget.py new file mode 100755 index 0000000..35141bb --- /dev/null +++ b/drlaunch/src/widget.py @@ -0,0 +1,303 @@ +#!/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 . +# +# $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: + diff --git a/drlaunch/src/win_config.py b/drlaunch/src/win_config.py new file mode 100755 index 0000000..4639e2b --- /dev/null +++ b/drlaunch/src/win_config.py @@ -0,0 +1,775 @@ +#!/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 . +# +# $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 xsz[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: + diff --git a/drlaunch/src/xdg/BaseDirectory.py b/drlaunch/src/xdg/BaseDirectory.py new file mode 100644 index 0000000..6f532c9 --- /dev/null +++ b/drlaunch/src/xdg/BaseDirectory.py @@ -0,0 +1,97 @@ +""" +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// 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// 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 diff --git a/drlaunch/src/xdg/Config.py b/drlaunch/src/xdg/Config.py new file mode 100644 index 0000000..e2fbe64 --- /dev/null +++ b/drlaunch/src/xdg/Config.py @@ -0,0 +1,39 @@ +""" +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 diff --git a/drlaunch/src/xdg/DesktopEntry.py b/drlaunch/src/xdg/DesktopEntry.py new file mode 100644 index 0000000..8626d7f --- /dev/null +++ b/drlaunch/src/xdg/DesktopEntry.py @@ -0,0 +1,397 @@ +""" +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); + diff --git a/drlaunch/src/xdg/Exceptions.py b/drlaunch/src/xdg/Exceptions.py new file mode 100644 index 0000000..f7d08be --- /dev/null +++ b/drlaunch/src/xdg/Exceptions.py @@ -0,0 +1,51 @@ +""" +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 diff --git a/drlaunch/src/xdg/IconTheme.py b/drlaunch/src/xdg/IconTheme.py new file mode 100644 index 0000000..1f1fa18 --- /dev/null +++ b/drlaunch/src/xdg/IconTheme.py @@ -0,0 +1,391 @@ +""" +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 diff --git a/drlaunch/src/xdg/IniFile.py b/drlaunch/src/xdg/IniFile.py new file mode 100644 index 0000000..f3f08c7 --- /dev/null +++ b/drlaunch/src/xdg/IniFile.py @@ -0,0 +1,406 @@ +""" +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"(? 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 diff --git a/drlaunch/src/xdg/Locale.py b/drlaunch/src/xdg/Locale.py new file mode 100644 index 0000000..d30d91a --- /dev/null +++ b/drlaunch/src/xdg/Locale.py @@ -0,0 +1,79 @@ +""" +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() diff --git a/drlaunch/src/xdg/Menu.py b/drlaunch/src/xdg/Menu.py new file mode 100644 index 0000000..d437ee4 --- /dev/null +++ b/drlaunch/src/xdg/Menu.py @@ -0,0 +1,1074 @@ +""" +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 diff --git a/drlaunch/src/xdg/MenuEditor.py b/drlaunch/src/xdg/MenuEditor.py new file mode 100644 index 0000000..cc5ce54 --- /dev/null +++ b/drlaunch/src/xdg/MenuEditor.py @@ -0,0 +1,511 @@ +""" 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('Applications'+self.menu.Filename+'') + 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]*\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) diff --git a/drlaunch/src/xdg/Mime.py b/drlaunch/src/xdg/Mime.py new file mode 100644 index 0000000..04fe0d2 --- /dev/null +++ b/drlaunch/src/xdg/Mime.py @@ -0,0 +1,474 @@ +""" +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 /packages/.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%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 '' % 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' % 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/.xml. + If package_file is None, install /.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 diff --git a/drlaunch/src/xdg/RecentFiles.py b/drlaunch/src/xdg/RecentFiles.py new file mode 100644 index 0000000..6c2cd85 --- /dev/null +++ b/drlaunch/src/xdg/RecentFiles.py @@ -0,0 +1,159 @@ +""" +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('\n') + f.write("\n") + + for r in self.RecentFiles: + f.write(" \n") + f.write(" %s\n" % xml.sax.saxutils.escape(r.URI)) + f.write(" %s\n" % r.MimeType) + f.write(" %s\n" % r.Timestamp) + if r.Private == True: + f.write(" \n") + if len(r.Groups) > 0: + f.write(" \n") + for group in r.Groups: + f.write(" %s\n" % group) + f.write(" \n") + f.write(" \n") + + f.write("\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 diff --git a/drlaunch/src/xdg/__init__.py b/drlaunch/src/xdg/__init__.py new file mode 100644 index 0000000..870cd00 --- /dev/null +++ b/drlaunch/src/xdg/__init__.py @@ -0,0 +1 @@ +__all__ = [ "BaseDirectory", "DesktopEntry", "Menu", "Exceptions", "IniFile", "IconTheme", "Locale", "Config", "Mime", "RecentFiles", "MenuEditor" ] diff --git a/misc/drlaunch-48.base64 b/misc/drlaunch-48.base64 deleted file mode 100644 index 47fd8d7..0000000 --- a/misc/drlaunch-48.base64 +++ /dev/null @@ -1,124 +0,0 @@ -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== -==== diff --git a/misc/drlaunch-48.png b/misc/drlaunch-48.png deleted file mode 100644 index 9c30d7d..0000000 Binary files a/misc/drlaunch-48.png and /dev/null differ diff --git a/misc/drlaunch-64.png b/misc/drlaunch-64.png deleted file mode 100644 index 0091ace..0000000 Binary files a/misc/drlaunch-64.png and /dev/null differ diff --git a/misc/drlaunch.desktop b/misc/drlaunch.desktop deleted file mode 100644 index 71434fc..0000000 --- a/misc/drlaunch.desktop +++ /dev/null @@ -1,6 +0,0 @@ -[Desktop Entry] -Name=DrLaunch -Comment=Application launcer widget with portrait mode -Type=python -X-Path=drlaunch_widget.py -X-Multiple=true diff --git a/misc/mkico b/misc/mkico deleted file mode 100755 index d86ff34..0000000 --- a/misc/mkico +++ /dev/null @@ -1,2 +0,0 @@ -#convert drlaunch-64x64.png -scale 48x48 -depth 8 drlaunch-48x48.png -uuencode -m drlaunch-48.png drlaunch-48.png > drlaunch-48.base64 diff --git a/mkdist b/mkdist deleted file mode 100755 index 9b59c08..0000000 --- a/mkdist +++ /dev/null @@ -1,22 +0,0 @@ -#!/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 - diff --git a/sdist b/sdist deleted file mode 100755 index de1476b..0000000 --- a/sdist +++ /dev/null @@ -1,18 +0,0 @@ -#!/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 - diff --git a/setup.py b/setup.py deleted file mode 100755 index 4ec6af6..0000000 --- a/setup.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/.gitignore b/src/.gitignore deleted file mode 100644 index 34fcef1..0000000 --- a/src/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.pyo -*.pyc diff --git a/src/0.py b/src/0.py deleted file mode 100755 index 2b790f5..0000000 --- a/src/0.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100755 index 2b790f5..0000000 --- a/src/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/about.py b/src/about.py deleted file mode 100755 index 6f26ccc..0000000 --- a/src/about.py +++ /dev/null @@ -1,91 +0,0 @@ -#!/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 . -# -# $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 -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 - -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 . - -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: - diff --git a/src/about0.py b/src/about0.py deleted file mode 100644 index eeb6ae3..0000000 --- a/src/about0.py +++ /dev/null @@ -1,156 +0,0 @@ -#!/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 . -# - -# Python implementation of HeAboutDialog from hildon-extras -# Copyright (c) 2010-04-11 Thomas Perl - -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() - diff --git a/src/apps.py b/src/apps.py deleted file mode 100755 index 1a7336f..0000000 --- a/src/apps.py +++ /dev/null @@ -1,146 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/config.py b/src/config.py deleted file mode 100755 index 50ae471..0000000 --- a/src/config.py +++ /dev/null @@ -1,363 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/drlaunch_widget.py b/src/drlaunch_widget.py deleted file mode 100755 index c0f1f2a..0000000 --- a/src/drlaunch_widget.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/icon.py b/src/icon.py deleted file mode 100755 index c4ea529..0000000 --- a/src/icon.py +++ /dev/null @@ -1,450 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/icongrid.py b/src/icongrid.py deleted file mode 100755 index 96588d5..0000000 --- a/src/icongrid.py +++ /dev/null @@ -1,488 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/icons.py b/src/icons.py deleted file mode 100755 index 808d922..0000000 --- a/src/icons.py +++ /dev/null @@ -1,254 +0,0 @@ -#!/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 . -# -# $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[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 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: - diff --git a/src/iconw.py b/src/iconw.py deleted file mode 100644 index f7f13e7..0000000 --- a/src/iconw.py +++ /dev/null @@ -1,125 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/launcher.py b/src/launcher.py deleted file mode 100755 index 36d14bc..0000000 --- a/src/launcher.py +++ /dev/null @@ -1,51 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/portrait.py b/src/portrait.py deleted file mode 100644 index 7ea8b09..0000000 --- a/src/portrait.py +++ /dev/null @@ -1,242 +0,0 @@ -# -*- 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 . -# - -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: diff --git a/src/portrait.py.orig b/src/portrait.py.orig deleted file mode 100755 index 8cefa3e..0000000 --- a/src/portrait.py.orig +++ /dev/null @@ -1,211 +0,0 @@ -# -*- 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 . -# - -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 - diff --git a/src/sig.py b/src/sig.py deleted file mode 100644 index f7cf115..0000000 --- a/src/sig.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/widget.py b/src/widget.py deleted file mode 100755 index 35141bb..0000000 --- a/src/widget.py +++ /dev/null @@ -1,303 +0,0 @@ -#!/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 . -# -# $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: - diff --git a/src/win_config.py b/src/win_config.py deleted file mode 100755 index 4639e2b..0000000 --- a/src/win_config.py +++ /dev/null @@ -1,775 +0,0 @@ -#!/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 . -# -# $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 xsz[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: - diff --git a/src/xdg/BaseDirectory.py b/src/xdg/BaseDirectory.py deleted file mode 100644 index 6f532c9..0000000 --- a/src/xdg/BaseDirectory.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -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// 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// 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 diff --git a/src/xdg/Config.py b/src/xdg/Config.py deleted file mode 100644 index e2fbe64..0000000 --- a/src/xdg/Config.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -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 diff --git a/src/xdg/DesktopEntry.py b/src/xdg/DesktopEntry.py deleted file mode 100644 index 8626d7f..0000000 --- a/src/xdg/DesktopEntry.py +++ /dev/null @@ -1,397 +0,0 @@ -""" -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); - diff --git a/src/xdg/Exceptions.py b/src/xdg/Exceptions.py deleted file mode 100644 index f7d08be..0000000 --- a/src/xdg/Exceptions.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -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 diff --git a/src/xdg/IconTheme.py b/src/xdg/IconTheme.py deleted file mode 100644 index 1f1fa18..0000000 --- a/src/xdg/IconTheme.py +++ /dev/null @@ -1,391 +0,0 @@ -""" -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 diff --git a/src/xdg/IniFile.py b/src/xdg/IniFile.py deleted file mode 100644 index f3f08c7..0000000 --- a/src/xdg/IniFile.py +++ /dev/null @@ -1,406 +0,0 @@ -""" -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"(? 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 diff --git a/src/xdg/Locale.py b/src/xdg/Locale.py deleted file mode 100644 index d30d91a..0000000 --- a/src/xdg/Locale.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -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() diff --git a/src/xdg/Menu.py b/src/xdg/Menu.py deleted file mode 100644 index d437ee4..0000000 --- a/src/xdg/Menu.py +++ /dev/null @@ -1,1074 +0,0 @@ -""" -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 diff --git a/src/xdg/MenuEditor.py b/src/xdg/MenuEditor.py deleted file mode 100644 index cc5ce54..0000000 --- a/src/xdg/MenuEditor.py +++ /dev/null @@ -1,511 +0,0 @@ -""" 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('Applications'+self.menu.Filename+'') - 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]*\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) diff --git a/src/xdg/Mime.py b/src/xdg/Mime.py deleted file mode 100644 index 04fe0d2..0000000 --- a/src/xdg/Mime.py +++ /dev/null @@ -1,474 +0,0 @@ -""" -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 /packages/.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%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 '' % 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' % 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/.xml. - If package_file is None, install /.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 diff --git a/src/xdg/RecentFiles.py b/src/xdg/RecentFiles.py deleted file mode 100644 index 6c2cd85..0000000 --- a/src/xdg/RecentFiles.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -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('\n') - f.write("\n") - - for r in self.RecentFiles: - f.write(" \n") - f.write(" %s\n" % xml.sax.saxutils.escape(r.URI)) - f.write(" %s\n" % r.MimeType) - f.write(" %s\n" % r.Timestamp) - if r.Private == True: - f.write(" \n") - if len(r.Groups) > 0: - f.write(" \n") - for group in r.Groups: - f.write(" %s\n" % group) - f.write(" \n") - f.write(" \n") - - f.write("\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 diff --git a/src/xdg/__init__.py b/src/xdg/__init__.py deleted file mode 100644 index 870cd00..0000000 --- a/src/xdg/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__all__ = [ "BaseDirectory", "DesktopEntry", "Menu", "Exceptions", "IniFile", "IconTheme", "Locale", "Config", "Mime", "RecentFiles", "MenuEditor" ]