pax_global_header00006660000000000000000000000064126722611740014522gustar00rootroot0000000000000052 comment=5cefe829dec46c0480214b92b0e60b931280cc20 dock-applet-0.70/000077500000000000000000000000001267226117400136535ustar00rootroot00000000000000dock-applet-0.70/.gitignore000066400000000000000000000002251267226117400156420ustar00rootroot00000000000000.*.*~ *.*~ *~ *.py *.swp *.pyc __pycache__ *.valid autom4te.cache configure configure.ac INSTALL install.sh Makefile Makefile.in missing py-compile dock-applet-0.70/AUTHORS000066400000000000000000000000171267226117400147210ustar00rootroot00000000000000Robin Thompson dock-applet-0.70/COPYING000066400000000000000000001045131267226117400147120ustar00rootroot00000000000000 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 . dock-applet-0.70/ChangeLog000066400000000000000000000174401267226117400154330ustar00rootroot00000000000000V0.70 Settings from previous instances of the applet are now imported silently (previously the user was presented with a dialog asking the user if they wanted to import the settings). The change is to prevent problems when switching to the Mutiny desktop layout via Mate Tweak in Ubuntu Mate 16.04 When saving custom launchers the ~/.local/share/applications directory will be created if it doesn't already exist v0.69 Added code to allow new instances of apps to be started by middle clicking their dock icon (see https://bugs.launchpad.net/ubuntu-mate/+bug/1554128) Fixed bug that would prevent apps from launching if they were in a directory structure which contained a space character e.g. ~/Downloads/PopCorn Time/ Fixed bug which associated newly opened windows with incorrect apps and which occurred when the wm_class_name of the window was not set. Fix for https://bugs.launchpad.net/ubuntu-mate/+bug/1555324 V0.68 Fix for Launchpad bug 1550392 (https://bugs.launchpad.net/ubuntu/+source/mate-dock-applet/+bug/1550392) V0.67 Panel colour changing now occurs smoothly over over 0.5s rather than abruptly. Big cleanup of git repository! V0.66 Improved matching of apps with their .desktop files, in particular Opera and Chrome Dock icons now pulse when a new window is opened for a running app Minimize targets are now recalculated when the applet is moved or changes panel, so that windows always minimize to the correct place on the dock Added option to change MATE panel colour according to the current desktop wallpaper. (Note: this works for images only, not slideshows, gradients, or solid colour backgrounds). The colour can be applied to all panels or just the panel containing the dock. The applet now depends on the Python Imaging Library and SciPy packages because of this change Added new preferences to options to both activate panel colour changing and to limit it to the dock panel panel only Added option to not display indicators under running apps Added option to not display in the dock running pinned apps which are not on the current workspace Using the mouse wheel on a dock icon to scroll through an app's windows will now change workspace if the new window is not on the current workspace Selecting an app's window from the pop-up window list will now change workspace if the window is not on the current workspace Prefs dialog reworked because of new options added in this version V0.65 Dock icons now blink when an app needs attention Change to window activation code so that the applet works with MATE 1.12 V0.64 Fixed bug that would sometimes prevent a window from being focused when a dock icon was clicked Many changes to Improve detection of .desktop files from running apps and linking to dock icons Right click options (e.g. 'Open new Window' and Open new incognito window with Chrome) are now read from .desktop files and appear on the dock icon right click menu Custom launchers now set the Type field of the .desktop files they create to 'Application' and also set the NoDisplay field to 'true' so that the launcher is not displayed in the MATE menu Customer launchers now write .desktop files that do not contain spaces in the filename, as per the GNOME developer docs App icons can now be sourced from ~/.local/share/icons (e.g popcorn-time puts its icon here) V0.63 Removed the tooltip that appears when the mouse hovers over a docked app and replaced it with a list of the app's open windows. For each window, the list displays the app icon, an indicator showing which window is currently active, the window title, and a close icon. Clicking the close icon closes the window, clicking anywhere else makes the window active. Removed the list of app windows from the applet right click menu as they are no longer required. Changed the way the applet works when a running app's dock icon is clicked. This no longer minimizes/maximises all windows, but simply activates the app's last active window. Using the mouse scroll wheel over a running app's dock icon now scrolls through each of the app's open windows, unminimizing them and activating them as necessary .desktop files located in the user's home directory now take precedence over those located elsewhere in the filesystem. This allows users to create their own .desktop files (e.g. to customize an app's icon) and have them recognized by the applet Changed factory service file to explicitly invoke applet with python 3 The applet now saves its settings in ~/.config/mate_dock_applet.conf as well as in dconf. On first being added to a panel, the applet checks to see if this file exists and if it does it offers to use this configuration. This allows e.g. an easy way to restore the applet after an accidental deletion from the panel, and also a way to move applet configurations from one computer to another. V0.62 Fixed app icon drawing on non-composited displays. For apps which the applet does not recognise (their names or icons are incorrect) added a new right click menu option on the dock to create a custom launcher for the app. This displays a dialog (like the one for the MATE panel) allowing the app's command line, name, and icon to be specified. For user convenience, the applet will automatically fill in as many of these details as it can. Once the new launcher has been created, the app needs to be closed and reopened for it to be recognised by the dock. Typically, this option will only be needed for apps which have not been installed into the usual locations within the Linux filesystem. When an app's windows are minimised by clicking on the app's dock icon and then maximised by clicking it again, the app window that was previously active is made active again. V0.61 improved the way in which the windows owned by apps are detected Fixed the function that calculates the average colour of icons (for use when drawing highligts on the dock). It works now.... Fixed launching of Caja on linux mint Fixed docked_app.setup_from_wnck so that it passes '/' terminated versions of all directories to be searched for .desktop file to get_desktop_from_app_info Shift-clicking the icon of running applications now opens a new window of the app Added an option for the dock to only display unpinned apps from the current workspace.Pinned apps are always displayed no matter what workspace is active so that the user always has quick access to them) V0.60 - various bugfixes and minor additions including: Added an option in the preferences dialog to display multiple indicators for each open window an app has. The maximum number of indicators has been limited to 4 because on small panels (<32 pixels) there just isn't room for any more changed the app icon drawing code so that most drawing is done off screen and only copied to the panel at the very end Improved detection of app icons and app windows. In general this is a good thing, and in particular it means that the applet now works correctly with guvcview on Ubuntu Mate 15.04 Fixed a bug on Ubuntu Mate 15.04 where terminals started from the applet would have their working directory set as / instead of the home directory Updated the readme with instructions on how to compile the applet from source V0.59 - initial commit to git. Honour panel transparency and background settings Pin and unpin apps to the dock Rearrange application icons on the dock Works when panel is aligned to any side of the screen Launch apps by clicking on their icons in the dock Minimize/unminimize running app windows by clicking the app's dock icon Detect changes in the current icon theme and update the dock accordinly Use an indicator by each app to show when it is running Allow the user to specify whether a light or dark indicator is used so that it can always be seen no matter what colour the panel is Provide an About dialog dock-applet-0.70/INSTALL000066400000000000000000000013161267226117400147050ustar00rootroot00000000000000 First install the required dependencies: * Python3 * gir1.2-wnck-1.0 * libglib2-dev * Python Imaging Library * SciPy then cd to the directory containing all of the development files and run: aclocal automake --add-missing autoreconf ./configure --prefix=/usr make sudo make install Installation on Ubuntu Mate on a Pi 2 This is a little more involved. First download gir1.2-wnck-1.0 for arm architechure from [here](http://launchpadlibrarian.net/160438738/gir1.2-wnck-1.0_2.30.7-0ubuntu4_armhf.deb) and install it with sudo dpkg -i. Then install other dependencies - sudo apt-get install git autoreconf libglib2.0-dev From this point the instructions above for compiling from source should be followed. dock-applet-0.70/Makefile.am000066400000000000000000000000161267226117400157040ustar00rootroot00000000000000SUBDIRS = src dock-applet-0.70/NEWS000066400000000000000000000025141267226117400143540ustar00rootroot0000000000000015 Jun 2015 - V0.62 uploaded to github. This new version fixes issues relating to non-composited displays, allows custom launchers to be added to the dock, and fixes a bug when minimising and then maximising an app's windows. Full details are in the ChangeLog 15 Oct 2015 - V0.64 uploaded to github 8 Jan 2016 - V0.66 uploaded to github This new version adds a feature shamlessly copied from the Unity Desktop - the ability of the applet to change the color of MATE panels to (hopefully) match the current desktop wallpaper. The change can be applied to all panels or just the panel containing the dock. As a result of this new feature, the applet has two new dependencies, the Python Imaging Library and SciPy. On Arch Linux they can be installed with: sudo pacman -S python-pillow python-scipy Whereas on Ubuntu 15.10 they can be installed with: sudo apt-get install python3-pil python3-scipy For other distributions the commands and package names will obviously vary. The new version also contains a couple of minor new features and a few bugfixes. Full details are in the Changelog. dock-applet-0.70/README000066400000000000000000000013261267226117400145350ustar00rootroot00000000000000An application dock applet for the MATE panel The applet allows you to: Place a dock on any MATE panel, of any size, on any side of the desktop you desire. Pin and unpin apps to the dock Rearrange application icons on the dock Launch apps by clicking on their icons in the dock Minimize/unminimize running app windows by clicking the app's dock icon Detect changes in the current icon theme and update the dock accordingly Use an indicator by each app to show when it is running Optionally, use multiple indicators for each window an app has open Use either a light or dark indicator that it can always be seen no matter what colour the panel is For installation instructions and screenshots, see README.md dock-applet-0.70/README.md000066400000000000000000000045771267226117400151470ustar00rootroot00000000000000# An application dock applet for the MATE panel The applet allows you to: * Place a dock on any MATE panel, of any size, on any side of the desktop you desire. * Pin and unpin apps to the dock * Rearrange application icons on the dock * Launch apps by clicking on their icons in the dock * Minimize/unminimize running app windows by clicking the app's dock icon * Detect changes in the current icon theme and update the dock accordingly * Use an indicator by each app to show when it is running * Optionally, use multiple indicators for each window an app has open * Use either a light or dark indicator that it can always be seen no matter what colour the panel is, or turn indicators off altogether * Change the colour of MATE panels to the dominant colour (i.e. the most common colour) of the desktop wallpaper. The colour can be applied to all panels or just the panel containing the dock. ### Installation Ubuntu and Mint users can install from the PPA kindly provided by [webupd8](http://www.webupd8.org/2015/05/dock-applet-icon-only-window-list-for.html) For Arch users, there's a [package](http://aur.archlinux.org/packages/mate-applet-dock-git) in the AUR. Users of other distros will need to install from source, so first install the required dependencies: * Python3 * gir1.2-wnck-1.0 * libglib2-dev * Python Imaging Library * SciPy * Python 3 Cairo bindings then cd to the directory containing all of the development files and run: ``` aclocal automake --add-missing autoreconf ./configure --prefix=/usr make sudo make install ``` ### Installation on Ubuntu Mate on a Pi 2 This is a little more involved. First download gir1.2-wnck-1.0 for arm architechure from [here](http://launchpadlibrarian.net/160438738/gir1.2-wnck-1.0_2.30.7-0ubuntu4_armhf.deb) and install it with sudo dpkg -i. Then install other dependencies - sudo apt-get install git autoreconf libglib2.0-dev From this point the instructions above for compiling from source should be followed. ### Obligatory screen shots Running on Arch with a Unity style layout ![Arch screenshot](https://github.com/robint99/screenshots/raw/master/arch_V0.6_ss.png) Running on Ubuntu with a Windows 7 style layout ![Ubuntu screenshot](https://github.com/robint99/screenshots/raw/master/Ubuntu_V0.6_ss.png) Running on a Raspberry Pi 2 with Ubuntu MATE ![Pi2 screenshot](https://github.com/robint99/screenshots/raw/master/pi2_mate_V0.62_ss.png) dock-applet-0.70/configure.ac000066400000000000000000000014301267226117400161370ustar00rootroot00000000000000AC_INIT([Dock Applet], [0.70]) AM_INIT_AUTOMAKE AM_PATH_PYTHON([3.0]) GLIB_GSETTINGS #IT_PROG_INTLTOOL([0.40.0]) #GETTEXT_PACKAGE=dock-applet #AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$GETEX_PACKAGE"], [Define the gettext package to be used]) #AC_SUBST(GETTEXT_PACKAGE) #AM_GLIB_GNU_GETTEXT #m4_pattern_allow([AM_V_GEN])dnl Make autoconf not complain about the rule below #PANEL_INTLTOOL_MATE_PANEL_APPLET_RULE='%.mate-panel-applet: %.mate-panel-applet.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*.po) ; $(AM_V_GEN) LC_ALL=C $(INTLTOOL_MERGE) -d -u -c $(top_builddir)/po/.intltool-merge-cache $(top_srcdir)/po $< [$]@' #AC_SUBST([PANEL_INTLTOOL_MATE_PANEL_APPLET_RULE]) #AC_CONFIG_FILES([Makefile src/Makefile po/Makefile.in]) AC_CONFIG_FILES([Makefile src/Makefile]) AC_OUTPUT dock-applet-0.70/src/000077500000000000000000000000001267226117400144425ustar00rootroot00000000000000dock-applet-0.70/src/Makefile.am000066400000000000000000000106621267226117400165030ustar00rootroot00000000000000 SUBDIRS = applet_SCRIPTS = dock_applet.py dock.py docked_app.py dock_prefs.py dock_about.py log_it.py dock_custom_launcher.py dock_win_list.py \ dock_xml.py dock_color_changer.py dom_color.py appletdir = $(libdir)/mate-applets/mate-dock-applet/ APPLET_LOCATION = $(appletdir)dock_applet.py appletsdir = $(datadir)/mate-panel/applets applets_in_files = org.mate.panel.DockApplet.mate-panel-applet applets_DATA = $(applets_in_files:.mate-panel-applet.in=.mate-panel-applet) do_substitution = $(AM_V_GEN)sed \ -e 's,[@]pythondir[@],$(pythondir),g' \ -e 's,[@]PACKAGE[@],$(PACKAGE),g' \ -e 's,[@]VERSION[@],$(VERSION),g' \ -e 's,[@]LOCATION[@],$(appletdir),g' \ -e 's,[@]localedir[@],$(datadir)/locale,g' #$(applets_in_files): $(applets_in_files).in Makefile # $(AM_V_GEN)sed \ # -e "s|\@LOCATION\@|$(appletdir)|" \ # -e "s|\@pythondir\@|$(pythondir)|" \ # -e "s|\@PACKAGE\@|$(PACKAGE)|" \ # -e "s|\@VERSION\@|$(VERSION)|" \ # $< > $@ #%.mate-panel-applet.in: %.mate-panel-applet.in.in Makefile # $(AM_V_GEN)sed \ # -e "s|\@LOCATION\@|$(appletdir)|" \ # -e "s|\@pythondir\@|$(pythondir)|" \ # -e "s|\@PACKAGE\@|$(PACKAGE)|" \ # -e "s|\@VERSION\@|$(VERSION)|" \ # -e "s|\@localdir\@|$(datadir)//locale|" \ # $< > $@ #@PANEL_INTLTOOL_MATE_PANEL_APPLET_RULE@ servicedir = $(datadir)/dbus-1/services service_in_files = org.mate.panel.applet.DockAppletFactory.service.in service_DATA = $(service_in_files:.service.in=.service) #imagedir = $(libexecdir) #image_DATA = sahsl.png \ # sahsd.png \ # boinc.png #%.gschema.xml.in: %.gschema.xml.in.in Makefile # $(AM_V_GEN)sed -e 's^\@GETTEXT_PACKAGE\@^$(GETTEXT_PACKAGE)^g' < $< > $@ # gsettings_SCHEMAS = org.mate.panel.applet.dock.gschema.xml # #@INTLTOOL_XML_NOMERGE_RULE@ #@GSETTINGS_RULES@ #$(gsettings_SCHEMAS).in: $(gsettings_SCHEMAS).in.in Makefile # $(AM_V_GEN)sed -e 's^\@GETTEXT_PACKAGE\@^$(GETTEXT_PACKAGE)^g' \ # -e "s|\@LOCATION\@|$(libexecdir)|" \ # < $< > $@ org.mate.panel.applet.DockAppletFactory.service: $(service_in_files) $(AM_V_GEN)sed \ -e "s|\@LOCATION\@|$(APPLET_LOCATION)|" \ $< > $@ org.mate.panel.DockApplet.mate-panel-applet: $(applet_in_files) $(do_substitution) < $(srcdir)/org.mate.panel.DockApplet.mate-panel-applet.in > org.mate.panel.DockApplet.mate-panel-applet dock_applet.py: dock_applet.in Makefile $(do_substitution) < $(srcdir)/dock_applet.in > dock_applet.py chmod +x dock_applet.py dock_about.py: dock_about.in Makefile $(do_substitution) < $(srcdir)/dock_about.in > dock_about.py chmod +x dock_about.py dock_prefs.py: dock_prefs.in Makefile $(do_substitution) < $(srcdir)/dock_prefs.in > dock_prefs.py chmod +x dock_prefs.py dock_custom_launcher.py: dock_custom_launcher.in Makefile $(do_substitution) < $(srcdir)/dock_custom_launcher.in > dock_custom_launcher.py chmod +x dock_custom_launcher.py dock.py: dock.in Makefile $(do_substitution) < $(srcdir)/dock.in > dock.py chmod +x dock.py docked_app.py: docked_app.in Makefile $(do_substitution) < $(srcdir)/docked_app.in > docked_app.py chmod +x docked_app.py log_it.py: log_it.in Makefile $(do_substitution) < $(srcdir)/log_it.in > log_it.py chmod +x log_it.py dock_win_list.py: dock_win_list.in Makefile $(do_substitution) < $(srcdir)/dock_win_list.in > dock_win_list.py chmod +x dock_win_list.py dock_xml.py: dock_xml.in Makefile $(do_substitution) < $(srcdir)/dock_xml.in > dock_xml.py chmod +x dock_xml.py dock_color_changer.py: dock_color_changer.in Makefile $(do_substitution) < $(srcdir)/dock_color_changer.in > dock_color_changer.py chmod +x dock_color_changer.py dom_color.py: dom_color.in Makefile $(do_substitution) < $(srcdir)/dom_color.in > dom_color.py chmod +x dom_color.py applet_PYTHON = \ dock_applet.py \ dock_about.py \ dock_prefs.py \ dock.py \ docked_app.py \ dock_custom_launcher.py \ log_it.py \ dock_win_list.py \ dock_xml.py \ dock_color_changer.py \ dom_color.py CLEANFILES = $(applet_SCRIPTS) \ org.mate.panel.applet.DockAppletFactory.service \ org.mate.panel.DockApplet.mate-panel-applet EXTRA_DIST = dock_applet.in \ dock_about.in \ dock_prefs.in \ dock.in \ docked_app.in \ dock_custom_launcher.in \ log_it.in \ dock_win_list.in \ dock_xml.in \ dock_color_changer.in \ dom_color.in \ $(gsettings_SCHEMAS) \ $(applets_in_files) \ $(service_in_files) dock-applet-0.70/src/dock.in000066400000000000000000002272311267226117400157210ustar00rootroot00000000000000#!/usr/bin/env python3 """Provide functionality relating to an application dock Manage a list of pinned and non-pinned dock apps. Handle all mouse ui events for docked apps Respond to window opening and closing events from libwnck Respond to changes to the Gtk icon theme and update all docked apps Load and save dock settings (e.g. pinned apps and indicator type) respond to selections made in the applet right click menu, specifically : allow apps to be pinned to the dock : allow apps to unpinned from the dock : allow app icons be to moved to a different position on the dock : disply an About dialog : display a Preferences dialog """ # Copyright (C) 1997-2003 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301, USA. # # Author: # Robin Thompson import gi gi.require_version("Gtk", "2.0") gi.require_version("MatePanelApplet", "4.0") from gi.repository import Gtk from gi.repository import Gdk from gi.repository import MatePanelApplet from gi.repository import GObject gi.require_version("Wnck", "1.0") from gi.repository import Wnck from gi.repository import GdkPixbuf from gi.repository import Gio from gi.repository import GLib import os import os.path from time import sleep import docked_app import dock_prefs import dock_about import dock_custom_launcher import dock_win_list import dock_xml import dock_color_changer from log_it import log_it as log_it class Dock(object): """The main application dock class Attributes: applet : the MATE panel applet wnck_screen : the currently active wnck_screen. Assumed to be the default wnck_screen on applet start up window : app_list : the list of DockedApp objects. Will contain running/non-running pinned apps and running unpinned apps box : A Gtk HBox or VBox (depending on the applet orientation) containing the drawing areas of each of the app in app_list icontheme : used to load application icons and detect changes in the icon theme about_win : the about window prefs_win : the preferences window ccl_win : the create custom launcher window app_with_mouse : the DockedApp that the mouse is currently over active_app : the DockedApp that is currently the foreground app right_clicked_app: the app that was most recently right clicked settings_path : the GIO.Settings path for the applet settings : GIO.Settings - the settings for the applet indicator : the indicator type (light or dark, or None) multi_ind : whether or not multiple indicators are to be used show_all_unpinned_apps : whether or not unpinned apps from all workspaces are displayed in the dock show_all_pinned_apps : whether or not pinned apps from all workspaces are displayed in the dock change_panel_color : whether or not the color of MATE panels are to be changed to the dominant colour of the desktop wallpaper change_dock_color_only : whether or not all MATE panels are to have their colour changed or just the panel containing the dock panel_id : the toplevel id of the panel the applet is on dock_action_group : Gtk Action group containing all of the actions for the applet right click menu app_win_list : a replacement for the default tooltip - a window which lists the currently highlighted app's open windows and allows the user to select one win_list_delay : the amount of time (in milliseconds) after which the app's window list will pop up when the mouse hovers over the app win_list_timer : timer object used to cound win_list_delay panel_cc : used to changed to the colour of the MATE panel(s) to the dominant color of the desktop wallpaper (if enabled in the applet settings) """ def __init__(self, applet): """Init the Dock. Load settings Setup the applet right click menu and actions Set default values """ super().__init__() self.applet = applet # the panel applet, in case we need it later self.app_list = [] self.box = None self.icontheme = Gtk.IconTheme.get_default() self.icontheme.connect("changed", self.icon_theme_changed) self.window = None self.wnck_screen = Wnck.Screen.get_default() self.app_with_mouse = None # the dock app that mouse is currently over self.active_app = None # the currently active app self.right_clicked_app = None # the app that most recently had a right click self.settings_path = self.applet.get_preferences_path() self.settings = Gio.Settings.new_with_path("org.mate.panel.applet.dock", \ self.settings_path) # instantiate these - will be set up later self.object_settings = "" self.panel_id = "" # specify the xml file to be used as an alternative storage location for the # applet settings self.xml_conf=os.path.expanduser("~/.config/mate_dock_applet.conf") self.prefs_win = None self.about_win = None self.ccl_win = None self.indicator = 0 self.multi_ind = False self.show_all_unpinned_apps = True self.show_all_pinned_apps = True self.click_restore_last_active = True self.change_panel_color = False self.change_dock_color_only = False self.read_settings() self.dock_action_group = None self.app_win_list = dock_win_list.DockWinList(self.wnck_screen) self.app_win_list.icontheme = self.icontheme self.win_list_delay = 500 self.win_list_timer = None # create an event handler so that we can react to changes e.g # the panel the applet is on, or it's position on the panel applet_spath = self.settings_path[0: len(self.settings_path)-6] # remove "prefs/" suffix self.object_settings = Gio.Settings.new_with_path("org.mate.panel.object", applet_spath) self.object_settings.connect("changed", self.panel_settings_changed) self.setup_menu() self.panel_cc = dock_color_changer.PanelColorChanger() # instantiate a timer to perform further setup once the applet has been fully # created GObject.timeout_add(1000, self.do_delayed_setup) def do_delayed_setup(self): """ Perform setup operations that we couldn't do until the dock was fully instantiated Get the id of the panel the applet is on Do the initial panel colour change (if necessary) """ self.get_panel_id() # now that we have our panel id, we can set use it to set to panel colour changer # if necessary if self.change_dock_color_only: self.panel_cc.set_single_panel(self.panel_id) else: self.panel_cc.set_single_panel("") # enable panel colour changing? if self.change_panel_color: self.panel_cc.enable_color_change() self.panel_cc.do_change_panel_color() # we're starting up so need to do an initial panel # colour change return False # cancel the timer def get_panel_id(self): """ Get the toplevel id of the panel the applet is on """ # get the info from dconf settings self.panel_id = self.object_settings.get_string("toplevel-id") def panel_settings_changed(self, settings, key): """ Callback for when the the applet settings with regards to it's panel are changed If the panel the applet is on is changed, update our panel id and recalculate all docked apps minimize positions accordingly If the appllet's position on its panel is changed, the minimize positions of all docked apps also need to be minimized """ if key == "toplevel-id": self.get_panel_id() if self.change_dock_color_only: self.panel_cc.set_single_panel(self.panel_id) self.set_app_minimise_targets() pass if key == "position": self.set_app_minimise_targets() pass def read_settings(self): """ Read the current dock settings from dconf If this particular dock has not been run before and a settings xml file exists offer to import the settings from the xml file ... """ if (self.settings.get_boolean("first-run") == True) and \ (os.path.isfile(self.xml_conf)): # this dock is being run for the first time, so if we have any saved settings # from other docks, import them. Note: prior to V0.70 the applet presented a # dialog asking the user whether or not to import the previous settings. # The is dialog has been removed and the previous settings are now silently # imported to prevent problems when swtiching to the Mutiny layout in Ubuntu # Mate 16.04 xml_settings = dock_xml.read_xml(self.xml_conf) if xml_settings[0] == True: # the settings were read correctly, so set everything up pinned_apps = [] for pinned_app in xml_settings[1]: pinned_apps.append(pinned_app) self.indicator = xml_settings[2] self.show_all_unpinned_apps = xml_settings[3] self.multi_ind = xml_settings[4] self.click_restore_last_active = xml_settings[5] self.show_all_pinned_apps = xml_settings[6] self.change_panel_color = xml_settings[7] self.change_dock_color_only = xml_settings[8] # now, immediately write the settings to dconf and back to the config file # so the dock can access them self.settings.set_value("pinned-apps", GLib.Variant('as', pinned_apps)) self.settings.set_int("indicator-type", self.indicator) self.settings.set_boolean("multi-ind", self.multi_ind) self.settings.set_boolean("apps-from-all-workspaces", self.show_all_unpinned_apps) self.settings.set_boolean("first-run", False) self.settings.set_boolean("click-restore-last-active", self.click_restore_last_active) self.settings.set_boolean("pinned-apps-from-all-workspaces", self.show_all_pinned_apps) self.settings.set_boolean("change-panel-color", self.change_panel_color) self.settings.set_boolean("change-panel-color-dock-only", self.change_dock_color_only) dock_xml.write_xml(self.xml_conf, pinned_apps, self.indicator, \ self.show_all_unpinned_apps, self.multi_ind, self.click_restore_last_active, \ self.show_all_pinned_apps, self.change_panel_color, self.change_dock_color_only) return # we get here if there was no previous configuration, or the configuration couldn't be read. # Where the configuration cou;n't be read this could be due to an error or because new versions # of the applet have added configuration options not yet in the user's xml file. To recover, # simply assume a default set of options self.indicator = self.settings.get_int("indicator-type") self.multi_ind = self.settings.get_boolean("multi-ind") self.show_all_unpinned_apps = self.settings.get_boolean("apps-from-all-workspaces") self.click_restore_last_active = self.settings.get_boolean("click-restore-last-active") self.show_all_pinned_apps = self.settings.get_boolean("pinned-apps-from-all-workspaces") self.change_panel_color = self.settings.get_boolean("change-panel-color") self.change_dock_color_only = self.settings.get_boolean("change-panel-color-dock-only") def write_settings(self): """Write the current dock settings. Write a list of all of the currently pinned apps .desktop files Write the indicator type, whether to use multiple indicators, and whether to show unpinned apps from all workspaces Set the first-run indicator to False """ pinned_apps = [] for dock_app in self.app_list: if (dock_app.desktop_file is not None) and (dock_app.is_pinned): pinned_apps.append(os.path.basename(dock_app.desktop_file)) if self.settings: self.settings.set_value("pinned-apps", GLib.Variant('as', pinned_apps)) self.settings.set_int("indicator-type", self.indicator) self.settings.set_boolean("multi-ind", self.multi_ind) self.settings.set_boolean("apps-from-all-workspaces", self.show_all_unpinned_apps) self.settings.set_boolean("click-restore-last-active", self.click_restore_last_active) self.settings.set_boolean("pinned-apps-from-all-workspaces", self.show_all_pinned_apps) self.settings.set_boolean("change-panel-color", self.change_panel_color) self.settings.set_boolean("change-panel-color-dock-only", self.change_dock_color_only) self.settings.set_boolean("first-run", False) dock_xml.write_xml(self.xml_conf, pinned_apps, self.indicator, \ self.show_all_unpinned_apps, self.multi_ind, self.click_restore_last_active, self.show_all_pinned_apps, self.change_panel_color, self.change_dock_color_only) def set_actions_for_app(self, app): """Show or hide actions in the context menu so that only relevant ones are shown for the specified app If the app is pinned, do not show the pin action. If the app in not pinned, don't show the unpin action. Depending on applet orientation actions to move the app left/right or up/down along the dock need to shown or hidden. If the app is the first or last on the dock, then the options to move it up/left or down/right will also need to be hidden Include the app name in the menu text e.g. Pin Caja to the dock If the app has more than one window on screen, show actions allowing the user to select one If the app is running and has one or more windows on screen, show an option allowing them to be closed If the app does not have a desktop file, show an option allowing a custom launcher to be created Show any right click options specified in the app's desktop file Args: app : The DockedApp """ df_shortcut_1_action = self.dock_action_group.get_action("df_shortcut_1_action") df_shortcut_2_action = self.dock_action_group.get_action("df_shortcut_2_action") df_shortcut_3_action = self.dock_action_group.get_action("df_shortcut_3_action") df_shortcut_4_action = self.dock_action_group.get_action("df_shortcut_4_action") act_exists, act_name, act_cmd_line = app.get_rc_action(1) df_shortcut_1_action.set_visible(act_exists) if act_exists == True: df_shortcut_1_action.set_label(act_name) df_shortcut_1_action.set_icon_name(app.icon_name) act_exists, act_name, act_cmd_line = app.get_rc_action(2) df_shortcut_2_action.set_visible(act_exists) if act_exists == True: df_shortcut_2_action.set_label(act_name) df_shortcut_2_action.set_icon_name(app.icon_name) act_exists, act_name, act_cmd_line = app.get_rc_action(3) df_shortcut_3_action.set_visible(act_exists) if act_exists == True: df_shortcut_3_action.set_label(act_name) df_shortcut_3_action.set_icon_name(app.icon_name) act_exists, act_name, act_cmd_line = app.get_rc_action(4) df_shortcut_4_action.set_visible(act_exists) if act_exists == True: df_shortcut_4_action.set_label(act_name) df_shortcut_4_action.set_icon_name(app.icon_name) pin_action = self.dock_action_group.get_action("pin_action") unpin_action = self.dock_action_group.get_action("unpin_action") move_up_action = self.dock_action_group.get_action("move_up_action") move_down_action = self.dock_action_group.get_action("move_down_action") move_left_action = self.dock_action_group.get_action("move_left_action") move_right_action = self.dock_action_group.get_action("move_right_action") close_win_action = self.dock_action_group.get_action("close_win_action") close_win_action.set_visible(False) index = self.get_app_position_in_dock(app) pin_action.set_visible(not app.is_pinned) unpin_action.set_visible(app.is_pinned) orientation = self.applet.get_orient() if orientation == MatePanelApplet.AppletOrient.LEFT or \ orientation == MatePanelApplet.AppletOrient.RIGHT: move_up_action.set_visible(index > 0) move_down_action.set_visible(index < (len(self.box.get_children()))-1) move_left_action.set_visible(False) move_right_action.set_visible(False) move_up_action.set_label("Move %s up the dock" %app.app_name) move_down_action.set_label("Move %s down the dock" %app.app_name) else: move_up_action.set_visible(False) move_down_action.set_visible(False) move_left_action.set_visible(index > 0) move_right_action.set_visible(index < (len(self.box.get_children()))-1) move_left_action.set_label("Move %s to the left on the dock" %app.app_name) move_right_action.set_label("Move %s to the right on the dock" %app.app_name) if pin_action.is_visible(): pin_action.set_label("Pin %s to the dock" %app.app_name) else: unpin_action.set_label("Unpin %s from the dock" %app.app_name) # set the actions for selecting specific windows num_win = app.get_num_windows() if num_win == 1: close_win_action.set_label("Close %s" %app.app_name) else: close_win_action.set_label("Close all windows") if num_win > 0: close_win_action.set_visible(True) ccl_action = self.dock_action_group.get_action("ccl_action") ccl_action.set_visible(not app.has_desktop_file()) def setup_menu(self): """Set up the actions and right click menu for the applet """ # actions named df_shortcut__action are used for implementing shortcuts/actions # specified in an app's .desktop file self.dock_action_group = Gtk.ActionGroup("DockActions") self.dock_action_group.add_actions([ ("df_shortcut_1_action", None, "df_shortcut_1_action", None, "df_shortcut_1_action",\ self.df_shortcut_1), \ ("df_shortcut_2_action", None, "df_shortcut_2_action", None, "df_shortcut_2_action",\ self.df_shortcut_2), \ ("df_shortcut_3_action", None, "df_shortcut_3_action", None, "df_shortcut_3_action",\ self.df_shortcut_3), \ ("df_shortcut_4_action", None, "df_shortcut_4_action", None, "df_shortcut_4_action",\ self.df_shortcut_4), \ ("pin_action", Gtk.STOCK_ADD, \ "_Pin app to the dock", None, "Pin app to the dock", \ self.pin_app), \ ("unpin_action", Gtk.STOCK_REMOVE, \ "_Unpin app from the dock", None, "Unpin app from the dock", \ self.unpin_app), \ ("move_up_action", Gtk.STOCK_GO_UP,\ "Move app _up the dock", None, "Move app up the dock", \ self.move_app_up), \ ("move_down_action", Gtk.STOCK_GO_DOWN, \ "Move app _down the dock", None, "Move app down the dock", \ self.move_app_down), \ ("move_left_action", Gtk.STOCK_GO_BACK,\ "Move app _left in the dock", None, "Move app left in the dock",\ self.move_app_up), \ ("move_right_action", Gtk.STOCK_GO_FORWARD, \ "Move app _right in the dock", None, "Move app right in the dock",\ self.move_app_down), \ ("prefs_action", Gtk.STOCK_PREFERENCES, \ "Dock P_references", None, "Dock Preferences", \ self.show_prefs_win), \ ("ccl_action", Gtk.STOCK_EXECUTE, \ "Create custo_m launcher for this app", None, "Create custom launcher for this app", \ self.show_ccl_win), \ ("about_action", Gtk.STOCK_ABOUT, \ "About...", None, "About...", self.show_about_win), \ ("close_win_action", Gtk.STOCK_CLOSE, \ "_Close", None, "Close", self.close_win) \ ]) menu_xml = '' menu_xml += '' menu_xml += '' menu_xml += '' menu_xml += '' menu_xml += '' menu_xml += '' menu_xml += '' menu_xml += '' menu_xml += '' menu_xml += '' menu_xml += '' menu_xml += '' menu_xml += '' self.applet.setup_menu(menu_xml, self.dock_action_group) def df_shortcut_1(self, data=None): """Perform the app's 1st .desktop file specified shortcut/action """ if self.right_clicked_app is not None: self.right_clicked_app.run_rc_action(1) def df_shortcut_2(self, data=None): """Perform the app's 1st .desktop file specified shortcut/action """ if self.right_clicked_app is not None: self.right_clicked_app.run_rc_action(2) def df_shortcut_3(self, data=None): """Perform the app's 1st .desktop file specified shortcut/action """ if self.right_clicked_app is not None: self.right_clicked_app.run_rc_action(3) def df_shortcut_4(self, data=None): """Perform the app's 1st .desktop file specified shortcut/action """ if self.right_clicked_app is not None: self.right_clicked_app.run_rc_action(4) def unpin_app(self, data=None): """Unpin the right clicked app from the dock. Unpin the app and update the dock settings. If the app is not running, remove it from the dock also """ if self.right_clicked_app is not None: self.right_clicked_app.is_pinned = False self.write_settings() if not self.right_clicked_app.is_running(): self.remove_app_from_dock(self.right_clicked_app) self.right_clicked_app = None def pin_app(self, data=None): """Pin the right clicked app to the dock. Pin the app and update the dock settings""" if self.right_clicked_app is not None: self.right_clicked_app.is_pinned = True self.write_settings() def get_app_position_in_dock(self, app): """ Get the position of a specified app in the dock. Args : app - A DockedApp Returns : the index of the app, or -1 if it wasn't found """ index = 0 for app_da in self.box.get_children(): if app_da == app.drawing_area: return index index += 1 return -1 def move_app_up(self, data=None): """ Move the right clicked app up one position on the dock (or left if the panel is on the top or bottom of the screen). Moves the app and then recaculates the minimize location for it's windows. Writes the dock settings once all is done. """ if self.right_clicked_app is not None: index = self.get_app_position_in_dock(self.right_clicked_app) if index > 0: # we need to move the app both in self.applist and self.box self.box.reorder_child(self.right_clicked_app.drawing_area, index-1) app = self.app_list[index-1] self.app_list[index-1] = self.app_list[index] self.app_list[index] = app # recalculate the minimize targets for each app self.app_list[index-1].set_icon_geometry() self.app_list[index].set_icon_geometry() self.write_settings() def move_app_down(self, data=None): """ Move the right clicked app down one position on the dock (or right if the panel is on the top or bottom of the screen). Moves the app and then recaculates the minimize location for it's windows. Writes the dock settings once all is done. """ if self.right_clicked_app is not None: index = self.get_app_position_in_dock(self.right_clicked_app) if index < len(self.box.get_children()) -1: # we need to move the app both in self.applist and self.box self.box.reorder_child(self.right_clicked_app.drawing_area, index+1) app = self.app_list[index+1] self.app_list[index+1] = self.app_list[index] self.app_list[index] = app # recalculate the minimize targets for each app self.app_list[index+1].set_icon_geometry() self.app_list[index].set_icon_geometry() self.write_settings() def show_prefs_win(self, data=None): """ Show the preferences window. If, necessary create the window and register a callback for the 'ok' button press If the window has already been shown, just show it again. """ if self.prefs_win is None: self.prefs_win = dock_prefs.DockPrefsWindow(self.prefs_win_ok_cb) self.prefs_win.set_indicator(self.indicator) self.prefs_win.set_multi_ind(self.multi_ind) self.prefs_win.set_show_unpinned_apps_on_all_ws(self.show_all_unpinned_apps) self.prefs_win.set_show_pinned_apps_on_all_ws(self.show_all_pinned_apps) self.prefs_win.set_click_restore_last_active(self.click_restore_last_active) self.prefs_win.set_change_panel_color(self.change_panel_color) self.prefs_win.set_change_dock_color_only(self.change_dock_color_only) else: self.prefs_win.show_all() def show_about_win(self, data=None): """ Show the About window. If, necessary create the window and show it. If the window has already been shown, just show it again. """ if self.about_win is None: self.about_win = dock_about.AboutWindow() self.about_win.show_all() def prefs_win_ok_cb(self, widget, event): """ Callback for the 'ok' button on the preferences window. If the preferences have been changed then: write the new settings redraw each running app in app_list with the new indicator type Args: widget - the button the caused the event event - the event args """ if (self.indicator != self.prefs_win.get_indicator_type()) or \ (self.multi_ind != self.prefs_win.get_multi_ind()) or \ (self.show_all_unpinned_apps != self.prefs_win.get_show_unpinned_apps_on_all_ws()) or \ (self.show_all_pinned_apps != self.prefs_win.get_show_pinned_apps_on_all_ws()) or \ (self.click_restore_last_active != self.prefs_win.get_click_restore_last_active()) or \ (self.change_panel_color != self.prefs_win.get_change_panel_color()) or \ (self.change_dock_color_only != self.prefs_win.get_change_dock_color_only()): self.indicator = self.prefs_win.get_indicator_type() self.multi_ind = self.prefs_win.get_multi_ind() self.show_all_unpinned_apps = self.prefs_win.get_show_unpinned_apps_on_all_ws() self.show_all_pinned_apps = self.prefs_win.get_show_pinned_apps_on_all_ws() self.click_restore_last_active = self.prefs_win.get_click_restore_last_active() new_panel_color_setting = self.change_panel_color != self.prefs_win.get_change_panel_color() self.change_panel_color = self.prefs_win.get_change_panel_color() self.change_dock_color_only = self.prefs_win.get_change_dock_color_only() if self.change_dock_color_only: self.panel_cc.set_single_panel(self.panel_id) else: self.panel_cc.set_single_panel("") self.write_settings() # redraw everything here for app in self.app_list: app.set_indicator(self.indicator) app.set_multi_ind(self.multi_ind) if app.is_running(): app.queue_draw() self.show_or_hide_app_icons() if new_panel_color_setting: # panel colour changing setting has been changed so we need to # enable or disable colour changing if self.change_panel_color: self.panel_cc.enable_color_change() self.panel_cc.do_change_panel_color() else: self.panel_cc.disable_color_change() self.prefs_win.hide() def show_ccl_win(self, data=None): """ Show the create custom launcher window. If, necessary create the window and register a callback for the 'ok' button press If the window has already been shown, clear all of the fields before showing it """ if self.ccl_win is None: self.ccl_win = dock_custom_launcher.DockCLWindow(self.ccl_win_ok_cb) else: self.ccl_win.set_default_values() self.ccl_win.name = self.right_clicked_app.app_name.strip() self.ccl_win.wm_class = self.right_clicked_app.wm_class_name self.ccl_win.show_all() def ccl_win_ok_cb(self, widget, event): """ Callback for the 'ok' button on the create custom launcher window. Check to ensure that all required fields (icon, launcher name and command) have been entered and display an error dialog if not. If all required fields have been entered, use the info from the window to create a .desktop file in ~/.local/share/applications The .desktop file will be named mda_.desktop - the initial 'mda_' will allow the applet to search for and priorities self created .desktop files over system created ones... Args: widget - the button the caused the event event - the event args """ valid_launcher = False if self.ccl_win.name == "": error_text = "The name of the launcher has not been set" elif self.ccl_win.command == "": error_text = "The command of the launcher has not been set" elif self.ccl_win.icon_filename == "": error_text = "The icon of the launcher has not been set" else: valid_launcher = True if valid_launcher == False: md = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.ERROR, Gtk.ButtonsType.OK, None) md.set_markup ('Cannot create launcher') md.format_secondary_text(error_text) md.run() md.destroy() return else: self.ccl_win.hide() # the gnome developer docs at # https://developer.gnome.org/integration-guide/stable/desktop-files.html.en # state that .desktop filenames should not contain spaces, so.... dfname = self.ccl_win.name.replace(" ", "-") local_apps = os.path.expanduser("~/.local/share/appplications") if not os.path.exists(local_apps): # ~/.local/share/applications doesn't exist, so create it os.mkdir(local_apps) dfname = os.path.expanduser("%s/mda-%s.desktop" %(local_apps,dfname)) dfile = open(dfname, "w") dfile.write("[Desktop Entry]\n") dfile.write("Name=%s\n" %self.ccl_win.name) dfile.write("Type=Application\n") dfile.write("Comment=%s\n" %self.ccl_win.comment) dfile.write("Exec=%s\n" %self.ccl_win.command) dfile.write("Icon=%s\n" %self.ccl_win.icon_filename) dfile.write("StartupWMClass=%s\n" %self.ccl_win.wm_class) # Code below can be uncommented if adding terminal apps to the dock ever # becomes a needed thing #term_app = "%s" %self.ccl_win.is_terminal_app #dfile.write("Terminal=%s\n" %term_app.lower()) # we don't want this launcher displayed in the MATe menu dfile.write("NoDisplay=true\n") dfile.close() # create a docked app from the .desktop we just created and add it to the dock dock_app = docked_app.DockedApp() dock_app.desktop_file = dfname dock_app.read_info_from_desktop_file() dock_app.is_pinned = True dock_app.applet_win = self.applet.window dock_app.applet_orient = self.applet.get_orient() dock_app.set_indicator(self.indicator) dock_app.set_multi_ind(self.multi_ind) size = self.applet.get_size() self.set_app_icon(dock_app, size) self.app_list.append(dock_app) self.add_app(dock_app) self.show_or_hide_app_icons() self.write_settings() def show_win(self, win_no): """ Bring the specified window number of the right clicked app to the front Args: win_no - the window number, starting at 1 """ win_list = self.right_clicked_app.get_wnck_windows() wnck_win = win_list[win_no-1] wnck_win.activate(0) def close_win(self, data=None): """Close all windows for the right clicked app""" win_list = self.right_clicked_app.get_wnck_windows() for wnck_win in win_list: wnck_win.close(0) # have to send 0 as the event time because we have # no event time to send def icon_theme_changed(self, icontheme): """ Callback for when the Gtk icon theme changes Load the new icon set Iterate through each app in self.app_list and get it to reload it's icon """ self.icontheme.rescan_if_needed() size = self.applet.get_size() for app in self.app_list: self.set_app_icon(app, size) def copy_wnck_info_to_app(self, app_from_dock, app_from_wnck, wnck_window): """Copy info from wnck into a docked applet If the wnck app pid is already present in the docked app, just copy the windows xids that are not in the docked app If the wnck app pid is not present, copy all of the the app_info """ if app_from_dock.wnck_class is None: app_from_dock.wnck_class = app_from_wnck.wnck_class app_from_dock.set_app_name(app_from_wnck.app_name) if app_from_dock.wm_class_name == "": app_from_dock.wm_class_name = app_from_wnck.wm_class_name for wai in app_from_wnck.app_info: # look for a process in the docked app with an identical pid existing_pid = False for dai in app_from_dock.app_info: # different wnck_apps for a single app can share the same pid # so just check the wnck_apps for equivalency if dai.app == wai.app: existing_pid = True if existing_pid == True: # we need to merge info from the new details into the dock_app for xid in wai.windows: try: unused_var = dai.windows.index(xid) #if we get here, the window was found, so there's no #need to do anything else except ValueError: # if we get here, the window wasn't found so it needs to be added dai.windows.append(xid) break if existing_pid == False: # add the info for the new process app_from_dock.app_info.append(wai) def find_desktop_file (self, df_name): """ Find the full filename of a specified .desktop file Search the following directories (and their subdirectories) for the specified filename /usr/share/applications /usr/local/share/applications ~/.local/share/applications Args : df_name : the name of the .desktop file e.g. pluma.desktop. The .desktop extension must be included Returns: The full filename (path + filename) of the desktop file if it exists or "" otherwise """ srch_dirs = ["/usr/share/applications/", \ "/usr/local/share/applications/", \ os.path.expanduser("~/.local/share/applications/")] for srch_dir in srch_dirs: for the_dir, dir_list, file_list in os.walk(srch_dir): try: unused_var = file_list.index(df_name) #if we get here the file is found the_name = os.path.join(the_dir, df_name) return the_name except ValueError: pass return "" def setup_app_list(self): """Setup the list of docked apps. First, read the list of pinned apps from the settings and add them to the app list Then iterate through the running apps, and then either: if this is a non-pinned app add it to app list if this is a pinned app, integrate the running app info with the pinned app details already set up Also, set up event handlers allowing us keep track of window open and close events and active window change events """ self.app_list = [] pinned_apps = self.settings.get_value("pinned-apps").unpack() # the settings contain a list of .desktop files, so we need to find and read each # file for pinned_app in pinned_apps: dock_app = docked_app.DockedApp() full_name = self.find_desktop_file(pinned_app) if full_name != "": dock_app.desktop_file = full_name if dock_app.read_info_from_desktop_file(): self.app_list.append(dock_app) dock_app.is_pinned = True self.wnck_screen.force_update() # recommended per Wnck documentation for wnck_window in self.wnck_screen.get_windows(): win_type = wnck_window.get_window_type() if ((win_type == Wnck.WindowType.NORMAL) or (win_type == Wnck.WindowType.DIALOG)) and \ wnck_window.is_skip_tasklist() == False: wnck_class = wnck_window.get_class_group() wnck_app = wnck_window.get_application() # setup a new docked app object with info from wnck dock_app = docked_app.DockedApp() dock_app.applet_win = self.applet.window dock_app.setup_from_wnck(wnck_app, wnck_class) # if this is a pinned app, merge the running app details with the item already # set up in app_list for app in self.app_list: if app.desktop_file == dock_app.desktop_file: # copy the running app's info to the pinned dock item self.copy_wnck_info_to_app(app, dock_app, wnck_window) break else: # it's not a pinned app so add it to the app list self.app_list.append(dock_app) self.wnck_screen.connect("active-window-changed", self.active_win_changed) self.wnck_screen.connect("active-workspace-changed", self.active_workspace_changed) self.wnck_screen.connect("window-opened", self.window_opened) self.wnck_screen.connect("window_closed", self.window_closed) def active_workspace_changed(self, wnck_screen, previously_active_space): """ Event handler for the active workspace change even Show or hide pinned and unpinned dock apps as appropriate Arguments : wnck_screen : the screen that emitted the event. Will be the same as self.wnck_screen previously_active_space : the workspace that was previously active """ self.show_or_hide_app_icons() def active_win_changed(self, wnck_screen, prev_active_window): """Event handler for the window change event Remove the highlighted background from any prevously active app and redraw its icon Set the new app as active and redraw it with a highlighted background Args: wnck_screen : the screen on which the event occurred - will be the same as : self.wnck_screen prev_active_window : the wnck_window that was previously active """ for app in self.app_list: if app.is_active == True: app.is_active = False app.queue_draw() new_win = self.wnck_screen.get_active_window() if new_win is not None: # the desktop itself can be the new active window. we need to ignore it if so.... # (fix for https://bugs.launchpad.net/ubuntu/+source/mate-dock-applet/+bug/1550392 if new_win.get_name().lower()!="x-caja-desktop": new_cg = new_win.get_class_group() new_wm_class_name = new_cg.get_res_class() new_app = new_win.get_application() for app in self.app_list: if app.wm_class_name == new_wm_class_name: for aai in app.app_info: if new_app == aai.app: app.is_active = True app.last_active_win = new_win app.queue_draw() break def window_opened(self, wnck_screen, wnck_window): """Event handler for the window_opened event If an aready running app has opened a new window, the window is added to to the running apps info If a newly lauched app is opening a new window, setup the app on the dock Args: wnck_screen : the screen on which the event occurred wnck_window : the newly opened window """ # we're only interested when normal and dialog windows if (wnck_window.get_window_type() != Wnck.WindowType.NORMAL) and \ (wnck_window.get_window_type() != Wnck.WindowType.DIALOG): return wnck_app = wnck_window.get_application() if not wnck_window.is_skip_tasklist(): # setup a new DockedApp wnck_class = wnck_window.get_class_group() dock_app = docked_app.DockedApp() dock_app.applet_win = self.applet.window dock_app.applet_orient = self.applet.get_orient() dock_app.set_indicator(self.indicator) dock_app.set_multi_ind(self.multi_ind) got_desktop = dock_app.setup_from_wnck(wnck_app, wnck_class) # look for the app relating to the window in the dock in_dock = False for app in self.app_list: if got_desktop == True: if app.desktop_file == dock_app.desktop_file: in_dock = True # if we don't have the .desktop file then things get a little more # complicated elif app.has_wnck_app(wnck_app) or \ ((app.wnck_class == dock_app.wnck_class or \ app.wm_class_name.upper() == dock_app.wm_class_name.upper() and \ # test below is fix for https://bugs.launchpad.net/ubuntu-mate/+bug/1555324 app.wm_class_name.strip() != "")): # in_dock = True if in_dock == True: # found it, so copy the running app's info to the pinned dock item self.copy_wnck_info_to_app(app, dock_app, wnck_window) # set the minimize target for the new window app.set_icon_geometry() # if the mouse pointer is over the app's dock icon, regenerate the list # of right click menu items to take acount of the new window if app.has_mouse: self.set_actions_for_app(app) # start the dock icon pulsing to let the user know something has happened if not app.is_pulsing: app.start_pulsing() break if in_dock == False: # append the app to the dock size = self.applet.get_size() self.set_app_icon(dock_app, size) self.app_list.append(dock_app) self.add_app(dock_app) self.show_or_hide_app_icons() def window_closed(self, wnck_screen, wnck_window): """Event handler for the window_closed event Find the app relating to the closed window. If it has no more open windows mark it as not running and redraw its icon without the running indicator. If the are open windows remaining, remove the closed window from the apps list of windows Args: wnck_screen : the screen on which the event occurred wnck_window : the closed window """ # we're only interested in the closure of normal and dialog windows if (wnck_window.get_window_type() != Wnck.WindowType.NORMAL) and \ (wnck_window.get_window_type() != Wnck.WindowType.DIALOG): return wnck_app = wnck_window.get_application() if wnck_app is None: # this always seems to be the case - so we need to find the app another # way - look through each of the running apps for the one which # has the closed window in it's window list # this is done by using xids - using wnck_windows in the app_info tuple # didn't seem to work very well xid = wnck_window.get_xid() for app in self.app_list: for aai in app.app_info: try: # a valueerror exception occurs if the window is not found unused_var = aai.windows.index(xid) # we've found the app so remove the window from it's list aai.windows.remove(xid) do_redraw = False # if the app has no more open windows, mark it as closed if len(aai.windows) == 0: # there no more open windows for this process. Does the app # have any other running processes ? if len(app.app_info) == 1: # No, it doesn't //// app.app_info = [] if not app.is_pinned: #rmeove the app from the dock self.remove_app_from_dock(app) else: app.is_active = False do_redraw = True else: # remove this processes info from docked app app.app_info.remove(aai) do_redraw = True else: do_redraw = True # was the closed window was the last active window for the app ? if app.last_active_win == wnck_window: app.last_active_win = None if do_redraw: app.queue_draw() if app.has_mouse: self.set_actions_for_app(app) break except ValueError: pass def show_or_hide_app_icons(self): """ If we're only showing unpinned and/or pinned apps from the current workspace then then show/hide as appropriate If we're showing pinned apps from all workspaces then show them all If we're showing unpinned apps from all workspaces then show them all Finally, recalculate all app minimization targets """ # do unpinned apps first if self.show_all_unpinned_apps: for app in self.app_list: if (not app.is_pinned) and (not app.is_visible()): app.show_icon() else: cur_ws = self.wnck_screen.get_active_workspace() for app in self.app_list: if not app.is_pinned: if app.has_windows_on_workspace(cur_ws): app.show_icon() else: app.hide_icon() # now do pinned apps if self.show_all_pinned_apps: for app in self.app_list: if (app.is_pinned) and (not app.is_visible()): app.show_icon() else: cur_ws = self.wnck_screen.get_active_workspace() for app in self.app_list: if app.is_pinned: # if the app is not running, then always show the icon regardless of the # current workspace. # This has to be done, since hiding none running pinned apps will not # give the user any way to start them.... if not app.is_running(): app.show_icon() else: if app.has_windows_on_workspace(cur_ws): app.show_icon() else: app.hide_icon() # recalculate all apps icon geometry for app in self.app_list: app.set_icon_geometry() def remove_app_from_dock(self, app): """Remove an app from the dock. Remove the app from the app_list Remove the app's drawing area from self.box Args: app : the app to be removed """ self.app_list.remove(app) self.box.remove(app.drawing_area) app = None def set_app_icon(self, dock_app, size): """ Sets up an app's icon, scaling it to a specified size Select an appropriate icon size based on the specified size Load the app's icon, using a fallback STOCK_EXEC as a fallback Scale the icon to the specified size Args: dock_app : the DockedApp size : the required icon size in pixels """ if size >= 56: icon_size = 64 stock_size = Gtk.IconSize.DIALOG elif size >= 40: icon_size = 48 stock_size = Gtk.IconSize.DIALOG elif size >= 28: icon_size = 32 stock_size = Gtk.IconSize.DND elif size >= 20: icon_size = 24 stock_size = Gtk.IconSize.LARGE_TOOLBAR else: icon_size = 16 stock_size = Gtk.IconSize.BUTTON if self.icontheme.has_icon(dock_app.icon_name): icon_info = self.icontheme.choose_icon([dock_app.icon_name, None], icon_size, 0) dock_app.icon_filename = icon_info.get_filename() try: pixbuf = icon_info.load_icon() except GLib.GError: # default to a stock icon if we couldn't load the app icon pixbuf = self.applet.render_icon(Gtk.STOCK_EXECUTE, stock_size, None) dock_app.icon_filename = "STOCK_EXECUTE" else: # the default theme has no icon for the app, so there are a few things we # can do... # # 1 .. quick and dirty - check to see if the icon points to an actual file # or ... # look in /usr/share/pixmaps for an icon of any type with # the same name as the app # then ... # look in ~/.local/share/icons for an icon with the same name # and extension as the icon # # 2 .. sloooow - iterate through each installed icon theme and try to # find the app - not implement for now # the png method. look for lower and uppercased variations of the filename # and note that all files in /usr/share/pixmaps are .png icon_file = "" if os.path.isfile(dock_app.icon_name): pixbuf = GdkPixbuf.Pixbuf.new_from_file(dock_app.icon_name) else: icon_file = "" icon_name = os.path.splitext(dock_app.icon_name)[0] # remove any extension if os.path.isfile("/usr/share/pixmaps/%s.png" %icon_name): icon_file = icon_name + ".png" elif os.path.isfile("/usr/share/pixmaps/%s.png" %icon_name.upper()): icon_file = icon_name.upper() + ".png" elif os.path.isfile("/usr/share/pixmaps/%s.png" %icon_name.lower()): icon_file = icon_name.lower() + ".png" if icon_file != "": pixbuf = GdkPixbuf.Pixbuf.new_from_file("/usr/share/pixmaps/%s"%icon_file) else: icon_file = os.path.expanduser("~/.local/share/icons/%s" %dock_app.icon_name) if os.path.isfile(icon_file): pixbuf= GdkPixbuf.Pixbuf.new_from_file(icon_file) else: pixbuf = self.applet.render_icon(Gtk.STOCK_EXECUTE, stock_size, None) dock_app.icon_filename = "STOCK_EXECUTE" # scale the icon to the size that is available # Note - we're allowing for a 3 pixel border all around the icon, hence using size-6 pixbuf = pixbuf.scale_simple(size -6, size - 6, \ GdkPixbuf.InterpType.BILINEAR) dock_app.set_drawing_area_size(size) dock_app.set_pixbuf(pixbuf) def add_app(self, dock_app): """ Add an app's drawing area to the VBox/Hbox container Args: dock_app : the DockedApp """ self.box.add(dock_app.drawing_area) def create_box(self, orientation): """Create a vertical or horizontal (depending on the applet orientation) box to contain the docked apps areas. Args: orientation : the applet orientation """ if orientation == MatePanelApplet.AppletOrient.LEFT or \ orientation == MatePanelApplet.AppletOrient.RIGHT: self.box = Gtk.VBox() else: self.box = Gtk.HBox() self.box.set_spacing(1) def setup_dock(self): """Setup the dock." Add all pinned apps to the dock Add all non-pinned running apps to the dock Setup all apps according to the applet size and orientation """ # make sure the applet is correctly oriented orientation = self.applet.get_orient() self.create_box(orientation) # setup up pinned and non-pinned running apps self.setup_app_list() applet_size = self.applet.get_size() # add the apps to the dock for dock_app in self.app_list: dock_app.applet_orient = orientation dock_app.applet_win = self.applet.window self.set_app_icon(dock_app, applet_size) dock_app.set_indicator(self.indicator) dock_app.set_multi_ind(self.multi_ind) self.add_app(dock_app) self.show_or_hide_app_icons() def set_new_orientation(self, new_orient): """Change the dock applet to a new applet orientation Remove all app's drawing areas from the V/HBox Remove the V/HBox and create a new one according to the new orientation Add all of the app's drawing areas to the new V/HBox Set all of the apps to use the new orientations and recalculate all of their minimize targets Args: new_orient : the new applet orientation """ for dock_app in self.app_list: self.box.remove(dock_app.drawing_area) self.applet.remove(self.box) self.box = None self.create_box(new_orient) for dock_app in self.app_list: self.box.add(dock_app.drawing_area) dock_app.set_icon_geometry() # reset minimize target dock_app.applet_orient = new_orient self.applet.add(self.box) def set_app_minimise_targets(self): """ Set all docked apps to recalculate their minimise targets""" for app in self.app_list: app.set_icon_geometry() def get_app_at_mouse(self, mouse_x, mouse_y): """ Find the app underneath the mouse cursor. Args: mouse_x : the x coord of the mouse mouse_y : the y coord of the mouse Returns: The app under the mouse, or None if one could not be found """ for app in self.app_list: if app.is_visible(): alloc = app.drawing_area.get_allocation() if (mouse_x >= alloc.x) and (mouse_x <= alloc.x + alloc.width): if (mouse_y >= alloc.y) and (mouse_y <= alloc.y + alloc.height): return app return None def reset_win_list_timer(self): """ Reset win_list timer If the timer is already instantiated, delete it. Start a new timer with the appropriate delay """ if self.win_list_timer is not None: GObject.source_remove(self.win_list_timer) self.win_list_timer = GObject.timeout_add(self.win_list_delay, self.show_win_list) def stop_win_list_timer(self): """ Stop the win list timer """ if self.win_list_timer is not None: GObject.source_remove(self.win_list_timer) self.win_list_timer = None def show_win_list(self): """ Show the the list of open windows for the currently highlighted app Get the currently highlighted app. If the highlighted app is not running or a window list is already being displayed for it, or a user interaction has already dismissed the window list, then do nothing Otherwise, fill the window list, set the window position and set the screen areas where the mouse must remain or the window list will hide """ panel_space = 5 # how many pixels away from the panel the window list will # appear win_border = 15 # size of the border (in pixels) around the window list where # the mouse must remain, outside of which the window list wil; # hide highlighted_app = self.app_with_mouse if (highlighted_app is None) or (highlighted_app.is_running == False): return if highlighted_app is not None: # we need to show to window list # first reset, the window list self.app_win_list.dismissed = True self.app_win_list.hide() self.app_win_list.clear_win_list() self.app_win_list.the_app = highlighted_app self.app_win_list.setup_list() self.app_win_list.clear_mouse_areas() # add the applet to the mouse areas applet_x, applet_y = self.applet.window.get_root_coords(0,0) applet_w, applet_h = self.applet.window.get_size() if applet_x < 0: applet_x = 0 if applet_y < 0: applet_y = 0 self.app_win_list.add_mouse_area (Gdk.Rectangle(applet_x, applet_y, applet_w, applet_h)) # get the display resolution screen = self.app_win_list.get_screen() screen_w = screen.get_width() screen_h = screen.get_height() # work out where to place the window - adjacent to the panel and centered on the # highlighted dock app app_alloc = highlighted_app.drawing_area.get_allocation() aax, aay = self.applet.window.get_root_coords(app_alloc.x, app_alloc.y) self.app_win_list.realize() win_w, win_h = self.app_win_list.get_size() orientation = self.applet.get_orient() if orientation == MatePanelApplet.AppletOrient.RIGHT: centre_pos = aay + app_alloc.height/2 win_x = applet_w + panel_space win_y = centre_pos - (win_h/2) # adjust win_y in case we're off the top the screen... if win_y < panel_space: win_y = panel_space # adjust win_y if case the window list extends beyound the end of the panel .. if (win_y + win_h) > screen_h: win_y = screen_h - win_h - panel_space # setup a new mouse area covering the window (minus a border) and extending # to the panel self.app_win_list.add_mouse_area(Gdk.Rectangle(applet_x, win_y - win_border , win_x +win_w + win_border, win_h + (2*win_border))) elif orientation == MatePanelApplet.AppletOrient.LEFT: centre_pos = aay + app_alloc.height/2 win_x = applet_x - panel_space - win_w win_y = centre_pos - (win_h/2) # adjust win_y in case we're off the top the screen... if win_y < panel_space: win_y = panel_space # adjust win_y if case the window list extends beyound the end of the panel .. if (win_y + win_h) > screen_h: win_y = screen_h - win_h - panel_space # setup a new mouse area covering the window (minus a border) and extending # to the panel self.app_win_list.add_mouse_area(Gdk.Rectangle(win_x - win_border, win_y - win_border, applet_x + applet_w, win_h + (2*win_border))) elif orientation == MatePanelApplet.AppletOrient.DOWN: centre_pos = aax+ app_alloc.width/2 win_x = centre_pos - (win_w / 2) win_y = applet_h + panel_space # adjust win_x in case we're off the left of the screen... if win_x < panel_space: win_x = panel_space # adjust win_x if case the window list extends beyond the end of the panel .. if (win_x + win_w) > screen_w: win_x = screen_w - win_w - panel_space # setup a new mouse area covering the window (minus a border) and extending # to the panel self.app_win_list.add_mouse_area(Gdk.Rectangle(win_x - win_border, applet_y, win_w + (2*win_border), win_y + win_h + win_border)) else: centre_pos = aax+ app_alloc.width/2 win_x = centre_pos - (win_w / 2) win_y = applet_y - panel_space - win_h # adjust win_x in case we're off the left of the screen... if win_x < panel_space: win_x = panel_space # adjust win_x if case the window list extends beyond the end of the panel .. if (win_x + win_w) > screen_w: win_x = screen_w - win_w - panel_space # setup a new mouse area covering the window (minus a border) and extending # to the panel self.app_win_list.add_mouse_area(Gdk.Rectangle(win_x - win_border, win_y - win_border, win_w + (2*win_border), applet_y + applet_h)) self.app_win_list.move (win_x, win_y) self.app_win_list.show_all() self.win_list_timer = None return False def hide_win_list(self): """ Hide the window list """ if self.app_win_list is not None: self.app_win_list.hide() def minimize_or_restore_windows(self, app, event): """ Minimize or restore an app's windows in response to a left click of it's dock icon the action to perform (minimizing, moving workspace, activating) is decided as follows: if (the app's windows are all minimized) or (the app s one or more unminimized window but is not the active app) then restore the app's last active window or all windows (based on the user's settings). If the active window is on a different workspace then activate that workspace else: the app is currently the active app so all of the app windows will be minimized but first, hide any app window list that is being shown and stop the window list timer Note: As of MATE 1.12 wnck_window.activate does not seem to work properly if we specify our own event time. However, if an event time of 0 (i.e. now) is specfied all works as expected. However, when the applet is run from the command line, lots of these messages 'Wnck-WARNING **: Received a timestamp of 0; window activation may not function properly' appear, so another solution may need to be found in the future Args: app: the docked app whose windows are to be minimized or restored event : the mouse click event """ self.stop_win_list_timer() self.hide_win_list() win_list = app.get_wnck_windows() restore_win = (not app.has_unminimized_windows()) or \ (app.has_unminimized_windows() and (app.is_active==False)) if restore_win: last_active_win = app.last_active_win # the last active window may be set to None (e.g. if the app's active window has been closed # and no other window has been made active afterwards). Therefore, if there is no active last window # activate the app's first normal window # (fix for https://bugs.launchpad.net/ubuntu/+source/mate-dock-applet/+bug/1550392) if last_active_win is None: for window in win_list: win_type = window.get_window_type() if ((win_type == Wnck.WindowType.NORMAL) or \ (win_type == Wnck.WindowType.DIALOG)) and \ (not window.is_skip_tasklist()): last_active_win = window break # if we're restoring all windows, do this now before we finally activate the last active window if self.click_restore_last_active == False: for window in win_list: win_type = window.get_window_type() if ((win_type == Wnck.WindowType.NORMAL) or \ (win_type == Wnck.WindowType.DIALOG)) and \ (not window.is_skip_tasklist()) and \ (window != last_active_win): window.activate(0) sleep(0.01) # allow the window manager time to activate # the window app.last_active_win = last_active_win wnck_screen = last_active_win.get_screen() wnck_aws = self.wnck_screen.get_active_workspace() wnck_ws = last_active_win.get_workspace() # the window's active workspace can be null if it is visible on all workspaces # or if it is not on any workspace (I'm looking at you caja-desktop!!!!!) # (fix for https://bugs.launchpad.net/ubuntu/+source/mate-dock-applet/+bug/1550392 and # https://bugs.launchpad.net/ubuntu-mate/+bug/1555336 (regarding software updater)) if wnck_aws is not None and wnck_ws is not None and (wnck_aws != wnck_ws): wnck_ws.activate(0) sleep(0.01) # rarely, the last active win does not end up as the active window if we # activate here, so instead a workaround which seems to do the trick # is use a timer as below GObject.timeout_add(20, win_activation_timer, [last_active_win, event.time]) else: #minimize all windows and do the last active window last of all last_active_win = app.last_active_win for window in win_list: win_type = window.get_window_type() if ((win_type == Wnck.WindowType.NORMAL) or \ (win_type == Wnck.WindowType.DIALOG)) and \ (not window.is_skip_tasklist()) and \ (window != last_active_win): window.minimize() sleep(0.01) app.last_active_win = last_active_win if last_active_win is not None: last_active_win.minimize() sleep(0.01) def do_window_scroll(self, scroll_dir, event_time): """ Scroll to the next/previous window of the currently active app This function is called in response to the mouse scroll event on the panel applet Depending on the scroll direction, make the next or previous window of the current app active. Scrolling will wrap around in both directions If the app only has one window or we don't know which window was last active (e.g. because the applet has only just started) then make the first window active If the new window is not on the current workspace, change to the relevant workspace Also, hide the app window list and stop any timer that might be running Args: scroll_dir : A GDK.ScrollDirection which indicates whether to go forwards or backwards through the window list event_time : the time scroll event occurred """ # we're only interested in scroll up and down events.... if (scroll_dir != Gdk.ScrollDirection.UP) and \ (scroll_dir != Gdk.ScrollDirection.DOWN): return if self.app_with_mouse is not None: app = self.app_with_mouse else: return # if the app isn't running, there's nothing to do... if app.is_running() == False: return windows = app.get_wnck_windows() if (app.last_active_win is None) or (len(windows) == 1): new_index = 0 else: # work out which window we want to activate try: index = windows.index (app.last_active_win) except ValueError: index = 0 # in case of error activate the first window if scroll_dir== Gdk.ScrollDirection.UP: if index == 0: new_index = len(windows)-1 else: new_index = index - 1 else: if index == len(windows)-1: new_index = 0 else: new_index = index +1 # hide the window list and stop any timer self.hide_win_list() self.stop_win_list_timer() # if the new window is on a different workspace, we need to switch workspace # wnck_screen = app.last_active_win.get_screen() wnck_aws = self.wnck_screen.get_active_workspace() wnck_ws = windows[new_index].get_workspace() if wnck_aws is not None and (wnck_aws != wnck_ws): wnck_ws.activate(0) sleep(0.01) #activate the new window windows[new_index].activate(0) def win_activation_timer(args): """ Timer function to be called by GObject.timeout_add and which will activate a specified wnck window Args: args - a tuple containing these items args[0] - the wnck window to activate args[1] - the event time at which the timer was activated Returns: False - to cancel the timer """ # as of MATE 1.12 it seems we need to use an event time of 0 (i.e. now) to the window to # activate properly args[0].activate(0) sleep(0.01) return (False) dock-applet-0.70/src/dock_about.in000077500000000000000000000076551267226117400171240ustar00rootroot00000000000000#!/usr/bin/env python3 """ Provide an about dialog for the MATE dock applet The applet displays the following: applet name and version number licensing info (GPL3) with a clickable link for the full version a close button """ # Copyright (C) 1997-2003 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301, USA. # # Author: # Robin Thompson # import sys import gi gi.require_version("Gtk", "2.0") from gi.repository import Gtk class AboutWindow(Gtk.Window): """Provides the About window""" def __init__(self): """Init for the About window class Create the window and its contents """ super().__init__(title="Dock Applet") self.__button = Gtk.Button(label="Close", stock=Gtk.STOCK_CLOSE) self.__button.connect("button-press-event", self.win_button_press) self.connect("delete-event", self.win_delete_event) self.set_border_width(5) self.__vbox = Gtk.VBox() self.__vbox.set_spacing(2) self.__hbx = Gtk.HButtonBox() self.__hbx.set_layout(Gtk.ButtonBoxStyle.END) self.__hbx.pack_start(self.__button, False, False, 4) self.__image = Gtk.Image() self.__image.set_from_stock(Gtk.STOCK_ABOUT, Gtk.IconSize.DIALOG) self.__lbl_title = Gtk.Label() self.__lbl_title.set_use_markup(True) self.__lbl_title.set_markup('' +'Dock Applet ' + 'V'+ \ "@VERSION@" + '') self.__lbl_blurb1 = \ Gtk.Label("An applet to provide an application dock for the MATE panel.") self.__lbl_blurb2 = \ Gtk.Label("This program comes with ABSOLUTELY NO WARRENTY.") self.__hbx_gpl = Gtk.HBox() self.__lbl_gpl1 = Gtk.Label("See the") self.__lb_gpl = \ Gtk.LinkButton("http://www.gnu.org/licenses/gpl-3.0.html", "GNU General Public License, version 3 or later") self.__lbl_gpl = Gtk.Label("for details") self.__hbx_gpl.pack_start(self.__lbl_gpl1, False, False, 2) self.__hbx_gpl.pack_start(self.__lb_gpl, False, False, 1) self.__hbx_gpl.pack_start(self.__lbl_gpl, False, False, 0) self.__vbox.pack_start(self.__image, False, False, 2) self.__vbox.pack_start(self.__lbl_title, False, False, 0) self.__vbox.pack_start(self.__lbl_blurb1, False, False, 8) self.__vbox.pack_start(self.__lbl_blurb2, False, False, 0) self.__vbox.pack_start(self.__hbx_gpl, False, False, 0) self.__vbox.pack_end(self.__hbx, False, False, 5) self.add(self.__vbox) def win_delete_event(self, widget, event, data=None): """Callback for the about window delete event Note: the window is not deleted, it is hidden instead so that it can be shown again if required later """ self.hide() return True def win_button_press(self, widget, event): """ callback for the Ok button on the About dialog The window is hidden so that it can be shown again """ self.hide() def main(): """ main function - debugging code goes here """ about_win = AboutWindow() about_win.show_all() Gtk.main() return if __name__ == "__main__": main() dock-applet-0.70/src/dock_applet.in000077500000000000000000000246371267226117400172760ustar00rootroot00000000000000#!/usr/bin/env python3 """Provide an application dock applet for the MATE panel Create a Mate panel applet and handle events generated by it Note: Functionality for docked apps is provided in docked_app.py Function for the dock is provided in dock.py """ # Copyright (C) 1997-2003 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301, USA. # # Author: # Robin Thompson import os import sys sys.path.insert(1, '@pythondir@') import gi gi.require_version("Gtk", "2.0") gi.require_version("MatePanelApplet", "4.0") from gi.repository import Gtk from gi.repository import MatePanelApplet from gi.repository import Gdk from gi.repository import Gio from gi.repository import GObject gi.require_version("Wnck", "1.0") from gi.repository import Wnck import xdg.DesktopEntry as DesktopEntry import docked_app import dock from dock_win_list import DockWinList from log_it import log_it as log_it def applet_button_press(widget, event, the_dock): """Button press event for the applet Handle right button press events only Find the app that was right clicked and make a record of it Args: widget : the widget that was clicked event : the event args the_dock : the Dock object """ # we don't get click events for the right mouse button presumably # because the panel hijacks them in order to produce the context menu # However, we do get button press event for the right mouse button, # so we can do what we need to do here .... if event.button == 3: #right click, so save the app that was clicked because # the_dock.app_with_mouse is going to be set to None when the # right click menu appears and we move the mouse over the menu to select # an option app = the_dock.get_app_at_mouse(event.x, event.y) the_dock.right_clicked_app = app # because the right click menu is about to be shown, we need to hide # the window list the_dock.hide_win_list() def applet_button_release(widget, event, the_dock): """Button press event for the applet Handle left button release events only If the button is released over a non-running app, start the app If the button is released over a runnning app that isn't on the current workspace, change workspace If the button is released over a running app on the current workspace: if it is the active app minmize all of its windows Otherwise, restore them all Args: widget : the widget that registered the release event event : the event args the_dock : the Dock object """ if event.button == 1: # hide the window list window the_dock.hide_win_list() app = the_dock.get_app_at_mouse(event.x, event.y) if app is not None: #TODO # move this code into docked_app.in ... # if the app is not running start the app # if the app is running and the shift key is being pressed, start another # instance of the app start_app = app.is_running() == False start_app = start_app | (event.state & Gdk.ModifierType.SHIFT_MASK) != 0 if start_app: app.start_app() else: the_dock.minimize_or_restore_windows(app, event) # See https://bugs.launchpad.net/ubuntu-mate/+bug/1554128 if event.button == 2: app = the_dock.get_app_at_mouse(event.x, event.y) if app is not None: the_dock.hide_win_list() app.start_app() def applet_enter_notify(widget, event, the_dock): """Enter notify event for the applet Brighten the icon of the app which the mouse is currently over If another app is currently brightened, darken it to normal Set up the right click menu for the dock based on the app which the mouse is currently over Start the timer for showing app window lists Args: widget : the widget that registered the event i.e. the applet event : the event args the_dock : the Dock object """ # get the app underneath the mouse cursor app = the_dock.get_app_at_mouse(event.x, event.y) # if an app is currently highlighted, de-highlight it if the_dock.app_with_mouse is not None: the_dock.app_with_mouse.has_mouse = False the_dock.app_with_mouse.queue_draw() the_dock.app_with_mouse = None # highlight the app under the mouse cursor if app is not None: app.has_mouse = True app.queue_draw() the_dock.app_with_mouse = app # set up the available options for the app the_dock.set_actions_for_app(app) the_dock.reset_win_list_timer() def applet_leave_notify(widget, event, the_dock): """Leave notifiy event handle for the applet Unbrighten any brightened app icon Args: widget : the widget that registered the event i.e. the applet event : the event args the_dock : the Dock object """ if the_dock.app_with_mouse is not None: the_dock.app_with_mouse.has_mouse = False the_dock.app_with_mouse.queue_draw() the_dock.app_with_mouse = None the_dock.stop_win_list_timer() def applet_motion_notify(widget, event, the_dock): """Motion notify event for the applet If the docked app under the mouse cursor does not have its icon brightened and another app has a brightened icon then darken the other app icon and reset the applet tooltip text Then, if the docked app under the mouse cursor does not have its icon brightened then brighten it and setup the applet right click menu Args: widget : the widget that registered the event i.e. the applet event : the event args the_dock : the Dock object """ app = the_dock.get_app_at_mouse(event.x, event.y) if (the_dock.app_with_mouse is not None) and (the_dock.app_with_mouse != app): the_dock.app_with_mouse.has_mouse = False the_dock.app_with_mouse.queue_draw() widget.queue_draw() # because a new app is highlighted reset the window list timer and hide any # currently open window list the_dock.hide_win_list() the_dock.reset_win_list_timer() if (app is not None): # reset the window list timer the_dock.reset_win_list_timer() if app.has_mouse == False: app.has_mouse = True app.queue_draw() the_dock.app_with_mouse = app the_dock.set_actions_for_app(app) def applet_change_orient(applet, orient, the_dock): """Handler for applet change orientation event Set the dock to the new orientation and re-show the applet Args: widget : the widget that registered the event i.e. the applet event : the event args the_dock : the Dock object """ the_dock.set_new_orientation(orient) the_dock.applet.show_all() the_dock.show_or_hide_app_icons() def applet_change_size(applet, size, the_dock): """Handler for the applet change size event Resize the icon and recalculate the minimize location of each app in the dock Args: widget : the widget that registered the event i.e. the applet event : the event args the_dock : the Dock object """ for app in the_dock.app_list: the_dock.set_app_icon(app, size) app.set_icon_geometry() def applet_scroll_event (applet, event, the_dock): """ Handler for the scroll event Call the dock's function to move forward/backward through the active app's windows """ the_dock.do_window_scroll(event.direction, event.time) def applet_fill(applet): """ Create the applet Register the events that we're interested in getting events for and connect event handlers for them Create a dock and add it V/HBox to the applet Args: applet : the applet """ os.chdir (os.path.expanduser("~")) applet.set_events(applet.get_events() | Gdk.EventMask.BUTTON_PRESS_MASK \ | Gdk.EventMask.BUTTON_RELEASE_MASK \ | Gdk.EventMask.POINTER_MOTION_MASK \ | Gdk.EventMask.KEY_PRESS_MASK \ | Gdk.EventMask.KEY_RELEASE_MASK \ | Gdk.EventMask.SCROLL_MASK \ | Gdk.EventMask.STRUCTURE_MASK) the_dock = dock.Dock(applet) the_dock.setup_dock() applet.add(the_dock.box) applet.show_all() applet.connect("enter-notify-event", applet_enter_notify, the_dock) applet.connect("leave-notify-event", applet_leave_notify, the_dock) applet.connect("motion-notify-event", applet_motion_notify, the_dock) applet.connect("button-press-event", applet_button_press, the_dock) applet.connect("button-release-event", applet_button_release, the_dock) applet.connect("change-orient", applet_change_orient, the_dock) applet.connect("change-size", applet_change_size, the_dock) applet.connect("scroll-event", applet_scroll_event, the_dock) applet.set_background_widget(applet) # hack for panel transparency def applet_factory(applet, iid, data): """Factory routine called when an applet needs to be created Create a dock applet if necessary Args: applet : the applet iid : the id of the applet that needs to be created Returns: True if we created a dock applet, False otherwise """ if iid != "DockApplet": return False applet_fill(applet) return True MatePanelApplet.Applet.factory_main("DockAppletFactory", True, MatePanelApplet.Applet.__gtype__, applet_factory, None) def main(): """Main function. Debugging code can go here """ pass if __name__ == "__main__": main() dock-applet-0.70/src/dock_color_changer.in000077500000000000000000000305141267226117400206050ustar00rootroot00000000000000#!/usr/bin/env python3 """ Provide notification when the desktop background changes to a new picture and, if necessary, change the color of the MATE panel(s) to the dominant color of the new image """ # Copyright (C) 1997-2003 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301, USA. # # Author: # Robin Thompson import gi gi.require_version("Gtk", "2.0") from gi.repository import Gtk from gi.repository import Gio import cairo import os import threading import dom_color from collections import namedtuple from time import sleep from log_it import log_it as log_it ChangeTup = namedtuple('ChangeTup', ['settings', 'ored', 'ogreen', 'oblue', 'step_red', 'step_green', 'step_blue']) class PanelColorChanger(object): """ Class to change the color of the MATE panel(s) to the dominant color of the wallpaper image Provide support for wallpaper images only - gradients, solid colours and slideshows will be ignored Change the panel color whenever the wallpaper image is changed Allow only a specific panel's color to be changed, rather than all panels Attributes: single_panel : the toplevel id of a panel which is the only one whose colour is to be changed """ def __init__(self, update_callback=None): """ Init for the PanelColorChanger class Use Gio.Settings to monitor the MATE desktop wallpaper setting Args: updated_callback - a function to be called after the panel colors have been updated """ super().__init__() self.update_cb = update_callback self.__red = self.__green = self.__blue = 0 # will hold rgb of the dom color # of the wallpaper self.__bg_settings = Gio.Settings.new("org.mate.background") self.__pf = self.__bg_settings.get_string("picture-filename") self.__panel_settings = Gio.Settings.new("org.mate.panel") self.__event_handler_id = 0 self.__toplevel_id = "" def enable_color_change(self): """ Enable panel color changing Monitor the MATE wallpaper setting and create an event handler to be called whenever it changes """ self.__event_handler_id = self.__bg_settings.connect("changed", self.background_changed) def disable_color_change(self): """ Disable panel color changing Disconnect the event handler linked to wallpaper changes """ self.__bg_settings.disconnect(self.__event_handler_id) def do_change_panel_color(self): """ Change the panel colour Call the event handler ourselves in order to change the panel colour """ self.background_changed(None, "picture-filename") def set_single_panel(self, toplevel_id): """ Store the id of the panel which is the only one whose colour is to be changed Args: toplevel_id : the toplevel_id of the panel. If this is an empty string all panels are to have their colour changed """ self.__toplevel_id = toplevel_id def background_changed(self, settings, key): """ Callback for when the desktop wallpaper settings are changed """ if key == "picture-filename": new_pf = self.__bg_settings.get_string("picture-filename") self.__pf = new_pf # we're only interested if the wallpaper is an image file if (new_pf is not None) and (new_pf != ""): pic_ext = os.path.splitext(new_pf)[1] if pic_ext.upper() != ".XML": worker_thread = threading.Thread(target=self.change_panel_colors) worker_thread.start() def get_dom_color(self): """ Get the dominant color of the current desktop image """ colstr = dom_color.get_dom_color(self.__pf) # cols = colorz (self.__pf) #for c in cols: # colstr = c # break self.__red = int(colstr[0:2], 16) self.__green = int(colstr[2:4], 16) self.__blue = int(colstr[4:6], 16) def change_panel_colors(self): """ Change panel colors to the rgb of the current dominant color Change the colour smoothly over an interval of 0.5 seconds """ self.get_dom_color() change_list = [] # initialise list of panels & settings we need to change # get the list of panels panel_list = self.__panel_settings.get_value("toplevel-id-list").unpack() for panel in panel_list: # do we want to change this panel? do_change = (self.__toplevel_id == "") or (self.__toplevel_id == panel) if do_change: # get the settings path for the current panel settings_path = "/org/mate/panel/toplevels/%s/background/" %panel # get this panel's settings psettings = Gio.Settings.new_with_path("org.mate.panel.toplevel.background", \ settings_path) # get the panel's original colour rgb components colstr = psettings.get_string("color") pr = int(colstr[1:3], 16) pg = int(colstr[3:5], 16) pb = int(colstr[5:7], 16) # we're going to change the panel's color in 25 discrete steps, so # get the difference we need to apply to each color component each step if pr > self.__red: rs = -(pr - self.__red) else: rs = self.__red - pr if pg > self.__green: gs = -(pg - self.__green) else: gs = self.__green - pg if pb > self.__blue: bs = -(pb - self.__blue) else: bs = self.__blue - pb rs /= 25 gs /= 25 bs /= 25 change_list.append(ChangeTup (settings = psettings, ored = pr, ogreen = pg, oblue = pb, step_red = rs, step_blue = bs, step_green = gs)) # now do the colour change for loop in range(1, 25): for change_item in change_list: if loop==1: # make sure the panel in question is set to be a colour change_item.settings.set_string("type", "color") # work out new rgb values for this interval new_red = int(change_item.ored + (loop * change_item.step_red)) & 0xff new_blue = int(change_item.oblue + (loop * change_item.step_blue)) & 0xff new_green = int(change_item.ogreen + (loop * change_item.step_green)) change_item.settings.set_string("color", "#%.2x%.2x%.2x" %(new_red, new_green, new_blue)) # all panels have been changed for this step, so pause for a bit sleep(0.02) # now that we've had a smooth transition, set panel colours to the final value for change_item in change_list: change_item.settings.set_string("color", "#%.2x%.2x%.2x" %(self.__red, self.__green, self.__blue)) # finally, call the callback function if self.update_cb is not None: self.update_cb() def wallpaper_filename(self): """ Get the desktop wallpaper image filename """ return self.__pf def panel_rgb(self): """ Get the rgb values of the colour we set the panel(s) to Returns: red, green, blue: integers """ return self.__red, self.__green, self.__blue class TestWindow(Gtk.Window): """Testing window for the color changer code""" def __init__(self): """Init for the Test window class Create the window and its contents """ super().__init__(title="Dock color changer test") self.__button = Gtk.Button(label="Close", stock=Gtk.STOCK_CLOSE) self.__button.connect("button-press-event", self.win_button_press) self.connect("delete-event", self.win_delete_event) self.pcc = PanelColorChanger(self.RefreshUi) self.set_border_width(5) self.__vbox = Gtk.VBox() self.__vbox.set_spacing(2) self.__vbox1 = Gtk.VBox() self.__vbox1.set_spacing(2) self.__hbox = Gtk.HBox() self.__hbox.set_spacing(2) self.__hbx = Gtk.HButtonBox() self.__hbx.set_layout(Gtk.ButtonBoxStyle.END) self.__hbx.pack_start(self.__button, False, False, 4) self.__da = Gtk.DrawingArea() self.__da.set_size_request(128, 128) self.__da.connect("expose-event", self.da_expose_event) self.__lbl_desktop_img = Gtk.Label() self.__lbl_desktop_img.set_use_markup(True) self.__red = self.__green = self.__blue = 0 self.__lbl_red = Gtk.Label("red:") self.__lbl_green = Gtk.Label("green:") self.__lbl_blue = Gtk.Label("blue:") self.__vbox1.pack_start(self.__lbl_red, False, False, 2) self.__vbox1.pack_start(self.__lbl_green, False, False, 2) self.__vbox1.pack_start(self.__lbl_blue, False, False, 2) self.__hbox.pack_start(self.__da, False, False, 2) self.__hbox.pack_start(self.__vbox1, False, False, 2) self.__vbox.pack_start(self.__hbox, False, False, 2) self.__vbox.pack_start(self.__lbl_desktop_img, False, False, 0) self.__vbox.pack_end(self.__hbx, False, False, 5) self.add(self.__vbox) def da_expose_event(self, widget, event): """ Draw a rectangle filled withe dominant color of the image """ # there are lots of drawing operations to be done, so do them to an offscreen # surface and when all is finished copy this to the window offscreen_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 64, 64) ctx = cairo.Context(offscreen_surface) # convert the highlight values to their cairo equivalents self.__red, self.__green, self.__blue = self.pcc.panel_rgb() pfn = os.path.split(self.pcc.wallpaper_filename())[1] self.__lbl_desktop_img.set_markup("%s" %pfn) self.__lbl_red.set_text("red: %s" %self.__red) self.__lbl_green.set_text("red: %s" %self.__green) self.__lbl_blue.set_text("red: %s" %self.__blue) red = self.__red / 255 green = self.__green / 255 blue = self.__blue / 255 ctx.rectangle(0, 0, 64, 64) ctx.set_source_rgb(red, green, blue) ctx.fill() screen_ctx = self.__da.window.cairo_create() screen_ctx.rectangle(event.area.x, event.area.y, \ event.area.width, event.area.height) screen_ctx.clip() screen_ctx.set_source_surface(offscreen_surface, 0, 0) screen_ctx.paint() ctx = None screen_ctx = None def RefreshUi(self): self.__da.queue_draw() def win_delete_event(self, widget, event, data=None): """Callback for the about window delete event """ Gtk.main_quit() return True def win_button_press(self, widget, event): """ callback for the Ok button on the About dialog """ Gtk.main_quit() def main(): """ main function - debugging code goes here """ test_win = TestWindow() test_win.show_all() Gtk.main() return if __name__ == "__main__": main() dock-applet-0.70/src/dock_custom_launcher.in000066400000000000000000000377001267226117400211740ustar00rootroot00000000000000#!/usr/bin/env python3 """Provide a dialog for the dock panel applet that allows a custom app launcher to be added to the dock. Note: This is primarily intended for apps that do not create a .desktop file, or get installed into non-standard locations Mimic the layout of the standard MATE custom launcher dialog, allowing the user to specify the following: App name The command used to launch the app The icon to be displayed in the dock A comment When the Ok button is clicked, a .desktop file will be created in the ~/.local/share/applications and its name will be in the format 'mda_.desktop' to allow the applet to recognise and if necessary give priority to self created custom launchers """ # Copyright (C) 1997-2003 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301, USA. # # Author: # Robin Thompson import gi gi.require_version("Gtk", "2.0") from gi.repository import Gtk, GdkPixbuf import os class DockCLWindow(Gtk.Window): """Class to provide the create custom launcher functionality Create and display the create custom launcher dialog Provide properties to get and set the custom laucher name, command, comment and icon """ def __init__(self, ok_callback): """ Constructor for the custom launchr window Create the window and its contents and display them set the callback for the ok button press Args: ok_callback : the method to be called when the ok button is is pressed """ super().__init__(title="Create Launcher") self.set_skip_taskbar_hint(True) self.__icon_filename = "" self.connect("delete-event", self.win_delete_event) # setup the window contents self.set_border_width(5) self.__hbox = Gtk.HBox() self.__hbox.set_spacing(2) self.__vbox = Gtk.VBox() self.__vbox.set_spacing(2) self.__btn_help = Gtk.Button(label="Help", stock=Gtk.STOCK_HELP) self.__btn_help.connect("button-press-event", self.help_btn_press) self.__btn_cancel = Gtk.Button(label="Cancel", stock=Gtk.STOCK_CANCEL) self.__btn_cancel.connect("button-press-event", self.win_cancel_button_press) self.__btn_ok = Gtk.Button(label="Ok", stock=Gtk.STOCK_OK) self.__btn_ok.connect("button-press-event", ok_callback) self.__hbox_btns = Gtk.HBox() self.__hbbx = Gtk.HButtonBox() self.__hbbx.set_spacing(4) self.__hbbx.set_layout(Gtk.ButtonBoxStyle.END) self.__hbbx.pack_start(self.__btn_cancel, False, False, 4) self.__hbbx.pack_end(self.__btn_ok, False, False, 4) self.__hbbx1 = Gtk.HButtonBox() self.__hbbx1.set_spacing(4) self.__hbbx1.set_layout(Gtk.ButtonBoxStyle.START) self.__hbbx1.pack_start(self.__btn_help, False, False, 4) self.__hbox_btns.pack_start(self.__hbbx1, False, False, 0) self.__hbox_btns.pack_end(self.__hbbx, False, False, 0) self.__btn_icon = Gtk.Button() self.__img_icon = Gtk.Image() self.__btn_icon.connect("button_press_event", self.img_button_press) self.__btn_icon.set_tooltip_text("Click to select an icon") self.__btn_icon.add(self.__img_icon) self.__vbox1 = Gtk.VBox() self.__vbox1.set_spacing(4) self.__vbox1.pack_start(self.__btn_icon, False, False, 4) self.__table_layout = Gtk.Table(rows=4, columns=2, homogeneous=False) self.__lbl_name = Gtk.Label() self.__lbl_name.set_use_markup(True) self.__lbl_name.set_label("" +"Name:" + "") self.__lbl_name.set_alignment(1, 0.5) self.__entry_name = Gtk.Entry() self.__hbox_cmd = Gtk.HBox() self.__hbox_cmd.set_spacing(2) self.__lbl_cmd = Gtk.Label() self.__lbl_cmd.set_use_markup(True) self.__lbl_cmd.set_label("" +"Command:" + "") self.__lbl_cmd.set_alignment(1, 0.5) self.__entry_cmd = Gtk.Entry() self.__entry_cmd.set_width_chars(40) self.__btn_cmd = Gtk.Button(label = "Browse...") self.__btn_cmd.connect("button-press-event", self.cmd_button_press) self.__hbox_cmd.pack_start(self.__entry_cmd, True, True, 0) self.__hbox_cmd.pack_end(self.__btn_cmd, False, False, 0) self.__lbl_term = Gtk.Label() self.__lbl_term.set_use_markup(True) self.__lbl_term.set_label("" + "Run in terminal:" + "") self.__lbl_term.set_alignment(1, 0.5) self.__cbtn_term = Gtk.CheckButton() self.__cbtn_term.set_alignment(1, 0.5) self.__lbl_comment = Gtk.Label() self.__lbl_comment.set_use_markup(True) self.__lbl_comment.set_label("" +"Comment:" + "") self.__entry_comment = Gtk.Entry() self.__lbl_wm_class = Gtk.Label() self.__lbl_wm_class.set_use_markup(True) self.__lbl_wm_class.set_label("Window Class") self.__entry_wm_class = Gtk.Entry() self.__entry_wm_class.set_sensitive(False) self.__table_layout.attach(self.__lbl_name, 0, 1, 0, 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_layout.attach(self.__entry_name, 1, 2, 0, 1, Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_layout.attach(self.__lbl_cmd, 0, 1, 1, 2, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_layout.attach(self.__hbox_cmd, 1, 2, 1, 2, Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.SHRINK, 2, 2) # Code below can be uncommented if adding terminal apps to the dock ever # becomes a needed thing #self.__table_layout.attach(self.__lbl_term, 0, 1, 2, 3, # Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.SHRINK, # 2, 2) # #self.__table_layout.attach(self.__cbtn_term, 1, 2, 2, 3, # Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.SHRINK, # 2, 2) self.__table_layout.attach(self.__lbl_comment, 0, 1, 2, 3, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_layout.attach(self.__entry_comment, 1, 2, 2, 3, Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_layout.attach(self.__lbl_wm_class, 0, 1, 3, 4, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_layout.attach(self.__entry_wm_class, 1, 2, 3, 4, Gtk.AttachOptions.FILL | Gtk.AttachOptions.EXPAND, Gtk.AttachOptions.SHRINK, 2, 2) self.__hbox.pack_start(self.__vbox1, False, False, 0) self.__hbox.pack_end(self.__table_layout, True, True, 0) self.__vbox.pack_start(self.__hbox, True, True, 0) self.__vbox.pack_start(Gtk.HSeparator(), False, False, 4) self.__vbox.pack_end(self.__hbox_btns, False, False, 0) self.add (self.__vbox) self.set_default_values() self.show_all() def set_default_values(self): """ Set the window to its default state Clear all text entry fields Set the icon to Gtk.STOCK_EXECUTE """ self.__img_icon.set_from_stock(Gtk.STOCK_EXECUTE, Gtk.IconSize.DIALOG) self.__entry_comment.set_text("") self.__entry_cmd.set_text("") self.__entry_name.set_text("") self.__entry_wm_class.set_text("") def win_delete_event(self, widget, event, data=None): """Callback for the preferences window delete event Do not delete the window, hide it instead so that it can be shown again later if needed """ self.hide() return True def win_cancel_button_press(self, widget, event): """Callback for the preferences window Cancel button press Hide the window """ self.hide() def set_cmd (self, cmd): """ Set the command line used by the launcher Args: cmd : the command line to use """ self.__entry_cmd.set_text(cmd) def get_cmd (self): """ Get the command line used by the launcher Returns: A string containing the command line """ return self.__entry_cmd.get_text() command = property(get_cmd, set_cmd) def cmd_button_press (self, widget, event): """Callback for the browse commands button Show a FileChooserDialog to allow the user to select a command to be associated with the laucher """ fdc_cmd = Gtk.FileChooserDialog (title = "Choose an application...", action = Gtk.FileChooserAction.OPEN) # set the working directory of the dialog cmd = self.get_cmd() if cmd is not None: path, filename = os.path.split(cmd) if path is not None: fdc_cmd.set_current_folder(path) btn_cancel = fdc_cmd.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) fdc_cmd.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK) btn_cancel.grab_default() response = fdc_cmd.run() if response == Gtk.ResponseType.OK: self.set_cmd(fdc_cmd.get_filename()) fdc_cmd.destroy() def set_icon_filename (self, filename): """ Set the filename of the icon to be used with the launcher and update the image on the icon selection button Args: filename - the filename """ pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) pixbuf = pixbuf.scale_simple(48, 48, GdkPixbuf.InterpType.BILINEAR) self.__img_icon.set_from_pixbuf(pixbuf) self.__icon_filename = filename def get_icon_filename (self): """ Get the filename of the icon to be used with the launcher Returns: A string containing the filename """ return self.__icon_filename icon_filename = property(get_icon_filename, set_icon_filename) def img_button_press(self, widget, event): """ Callback for the icon button press Show a FileChooerDialog allowing the user to select an icon for the launcher """ fdc_icon = Gtk.FileChooserDialog (title = "Choose an application...", action = Gtk.FileChooserAction.OPEN) ff_graphic = Gtk.FileFilter() ff_graphic.set_name("Image files") ff_graphic.add_pattern("*.svg") ff_graphic.add_pattern("*.png") ff_graphic.add_pattern("*.SVG") ff_graphic.add_pattern("*.PNG") ff_graphic.add_pattern("*.xpm") ff_graphic.add_pattern("*.XPM") fdc_icon.add_filter(ff_graphic) # set the working directory of the dialog icon_fn = self.get_icon_filename() if icon_fn is not None: path, filename = os.path.split(icon_fn) if path is not None: fdc_icon.set_current_folder(path) btn_cancel = fdc_icon.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL) fdc_icon.add_button(Gtk.STOCK_OPEN, Gtk.ResponseType.OK) btn_cancel.grab_default() response = fdc_icon.run() if response == Gtk.ResponseType.OK: self.set_icon_filename(fdc_icon.get_filename()) fdc_icon.destroy() def get_comment(self): """ Get the comment associated with the launcher Returns: string """ return self.__entry_comment.get_text() def set_comment(self, comment): """ Set the comment associated with the launcher Args: comment - a string containing the comment """ self.__entry_comment.set_text(comment) comment = property(get_comment, set_comment) def get_name(self): """ Get the name associated with the launcher Returns: a string containing the name """ return self.__entry_name.get_text() def set_name(self, name): """ Set the name associated with the launcher Args: name - a string """ self.__entry_name.set_text(name) name = property(get_name, set_name) def get_wm_class(self): """ Get the window wm_class_name Returns: A string containing the wm_class_name """ return self.__entry_wm_class.get_text() def set_wm_class(self, wm_class): """ Set the text of the launcher's wm_class entry widget Args: wm_class : The wm_class name """ self.__entry_wm_class.set_text(wm_class) wm_class = property(get_wm_class, set_wm_class) def get_is_term(self): """ Gets whether or not the app is meant to be run in a terminal Returns: True if the app is to be run in a terminal, False otherwise """ return self.__cbtn_term.get_active() def set_is_term(self, is_term): """ Sets whether or not the app is to be run in a terminal Args: is_term: boolean """ self.__cbtn_term.set_active(is_term) is_terminal_app = property(get_is_term, set_is_term) def help_btn_press(self, widget, event): """ Event handler for the Help button press event Display an explanation of the usages of custom launchers in a dialog box """ md = Gtk.MessageDialog(None, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, None) md.set_markup ('Custom Launchers') info_text = "Custom launchers are an advanced feature meant to be used only with apps " +\ "that the dock does not recognise (i.e. they display the wrong name or icon). \n\n" + \ "Normally, this will only happen when the apps have been installed " + \ "to a non standard location within the file system, so for the vast majority of " + \ "apps this feature is not needed.\n\n" + \ "Note: if an app is running when a custom launcher is created for it, the app will " + \ "need to be closed and restarted for the dock to recognise it." md.format_secondary_text(info_text) md.run() md.destroy() def main(): """main function - debug code can go here""" dclw = DockCLWindow(Gtk.main_quit) dclw.set_skip_taskbar_hint(False) dclw.name = "My test launcher" dclw.comment = "This is a comment" dclw.icon_filename = "/usr/share/pixmaps/ericWeb.png" dclw.command = "/usr/bin/gvim" dclw.wm_class = "Dock_custom_launcher.py" dclw.is_terminal_app = False Gtk.main() if __name__ == "__main__": main() dock-applet-0.70/src/dock_prefs.in000066400000000000000000000500731267226117400171160ustar00rootroot00000000000000#!/usr/bin/env python3 """Provide a configuration dialog for the dock panel applet Allow the user to specify whether the application running indicator is light or dark coloured, so that it can always be easily seen no matter what colour the panel is """ # Copyright (C) 1997-2003 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301, USA. # # Author: # Robin Thompson import gi gi.require_version("Gtk", "2.0") from gi.repository import Gtk import cairo import math class IndicatorType: """Class to define the indicator types""" LIGHT = 0 DARK = 1 NONE = 2 class DockPrefsWindow(Gtk.Window): """Class to provide the preferences window functionality Create and display the preferences window Provide methods to get and set: the type of indicator to be used by the dock applet whether pinned/unpinned apps are to be displayed from all workspaces or just the current workspace whether the colour of the MATE panels is to be set to the dominant colour of the current wallpaper image """ def __init__(self, ok_callback): """ Constructor for the preferences window Create the window and its contents and display them set the callback for the ok button press Args: ok_callback : the method to be called when the ok button is is pressed """ super().__init__(title="Preferences") self.DA_SIZE = 32 # the size of the drawing areas for the light and dark # indicators self.connect("delete-event", self.win_delete_event) # setup the window contents self.set_border_width(5) self.__hbox = Gtk.HBox() self.__hbox.set_spacing(2) self.__hbox1 = Gtk.HBox() self.__hbox1.set_spacing(2) self.__vbox = Gtk.VBox() self.__vbox.set_spacing(2) self.__cancel_btn = Gtk.Button(label=("Cancel1"), stock=Gtk.STOCK_CANCEL) self.__cancel_btn.connect("button-press-event", self.win_cancel_button_press) self.__ok_btn = Gtk.Button(label="Ok", stock=Gtk.STOCK_OK) self.__ok_btn.connect("button-press-event", ok_callback) self.__hbbx = Gtk.HButtonBox() self.__hbbx.set_spacing(4) self.__hbbx.set_layout(Gtk.ButtonBoxStyle.END) self.__hbbx.pack_start(self.__ok_btn, False, False, 4) self.__hbbx.pack_start(self.__cancel_btn, False, False, 4) self.__notebook = Gtk.Notebook() self.__frame_ind_type = self.create_frame("Indicator Type") self.__rb_light_ind = Gtk.RadioButton(label="Light (for dark panels)") self.__rb_dark_ind = Gtk.RadioButton(group=self.__rb_light_ind, \ label="Dark (for light panels)") self.__rb_no_ind = Gtk.RadioButton(group=self.__rb_light_ind, \ label="None - don't use indicators") self.__rb_no_ind.connect("toggled", self.rb_no_ind_toggled) self.__da_light_ind = Gtk.DrawingArea() self.__da_light_ind.set_size_request(self.DA_SIZE, self.DA_SIZE) self.__table_ind_type = Gtk.Table(rows=3, columns=2, homogeneous=False) # connect an event handler to draw the light indicator self.__da_light_ind.connect("expose-event", self.draw_light_ind) self.__da_dark_ind = Gtk.DrawingArea() self.__da_dark_ind.set_size_request(self.DA_SIZE, self.DA_SIZE) # connect an event handler to draw the dark indicator self.__da_dark_ind.connect("expose-event", self.draw_dark_ind) # create ui elements for multiple indicators for open windows self.__cb_multi_ind = Gtk.CheckButton(label="Display an indicator for each open window") self.__cb_multi_ind.set_tooltip_text("Display an indicator (max 4) for each open window") # add the indicator type ui elements to a table self.__table_ind_type.attach(self.__rb_light_ind, 0, 1, 0, 1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_ind_type.attach(self.__da_light_ind, 1, 2, 0, 1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_ind_type.attach(self.__rb_dark_ind, 0, 1, 1, 2, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_ind_type.attach(self.__da_dark_ind, 1, 2, 1, 2, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_ind_type.attach(self.__rb_no_ind, 0, 1, 2, 3, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) # self.__table_ind_type.attach(self.__cb_multi_ind, 0, 2, 2, 3, # Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, # 2, 4) # make sure the indicator type ui elements are aligned properly self.__frame_ind_type_align = Gtk.Alignment(xalign=0.5, yalign=0.5, xscale=1.0, yscale=1.0) self.__frame_ind_type_align.set_padding(0, 0, 12, 0) self.__frame_ind_type_align.add(self.__table_ind_type) self.__frame_ind_type.add(self.__frame_ind_type_align) self.__ind_vbox = Gtk.VBox() self.__ind_vbox.set_spacing(2) self.__ind_vbox.pack_start(self.__frame_ind_type, False, False, 4) self.__ind_vbox.pack_start(self.__cb_multi_ind, False, False, 4) self.__frame_unpinned_apps = self.create_frame("Unpinned applications") self.__rb_unpinned_all_ws = Gtk.RadioButton\ (label="Display unpinned apps from all workspaces") self.__rb_unpinned_cur_ws = Gtk.RadioButton(group=self.__rb_unpinned_all_ws, \ label="Display unpinned apps only from current workspace") self.__table_unpinned_apps = Gtk.Table(rows=2, columns=1, homogeneous=False) self.__table_unpinned_apps.attach(self.__rb_unpinned_all_ws, 0, 1, 0, 1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_unpinned_apps.attach(self.__rb_unpinned_cur_ws, 0, 1, 1, 2, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__frame_unpinned_apps_align = Gtk.Alignment(xalign=0.5, yalign=0.5, xscale=1.0, yscale=1.0) self.__frame_unpinned_apps_align.set_padding(0, 0, 12, 0) self.__frame_unpinned_apps_align.add(self.__table_unpinned_apps) self.__frame_unpinned_apps.add(self.__frame_unpinned_apps_align) self.__frame_pinned_apps = self.create_frame("Pinned applications") lbl = self.__frame_pinned_apps.get_label_widget() lbl.set_use_markup(True) lbl.set_label("" +"Pinned applications" + "") self.__frame_pinned_apps.set_shadow_type(Gtk.ShadowType.NONE) self.__rb_pinned_all_ws = Gtk.RadioButton(label="Display pinned apps from all workspaces") self.__rb_pinned_cur_ws = Gtk.RadioButton(group=self.__rb_pinned_all_ws, \ label="Display pinned apps only from current workspace") self.__table_pinned_apps = Gtk.Table(rows=2, columns=1, homogeneous=False) self.__table_pinned_apps.attach(self.__rb_pinned_all_ws, 0, 1, 0, 1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_pinned_apps.attach(self.__rb_pinned_cur_ws, 0, 1, 1, 2, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__frame_pinned_apps_align = Gtk.Alignment(xalign=0.5, yalign=0.5, xscale=1.0, yscale=1.0) self.__frame_pinned_apps_align.set_padding(0, 0, 12, 0) self.__frame_pinned_apps_align.add(self.__table_pinned_apps) self.__frame_pinned_apps.add(self.__frame_pinned_apps_align) self.__ws_vbox = Gtk.VBox() self.__ws_vbox.set_spacing(2) self.__ws_vbox.pack_start(self.__frame_unpinned_apps, True, True, 4) self.__ws_vbox.pack_start(self.__frame_pinned_apps, True, True, 4) self.__frame_click_action = self.create_frame("Click restoring windows") self.__rb_restore_last_active = Gtk.RadioButton \ (label="Restore clicked app's last active window only") self.__rb_restore_all = Gtk.RadioButton(group=self.__rb_restore_last_active, \ label="Restore all of clicked app's windows") self.__table_click_action = Gtk.Table(rows=2, columns=1, homogeneous=False) self.__table_click_action.attach(self.__rb_restore_last_active, 0, 1, 0, 1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_click_action.attach(self.__rb_restore_all, 0, 1, 1, 2, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__frame_click_action_align = Gtk.Alignment(xalign=0.5, yalign=0.5, xscale=1.0, yscale=1.0) self.__frame_click_action_align.set_padding(0, 0, 12, 0) self.__frame_click_action_align.add(self.__table_click_action) self.__frame_click_action.add(self.__frame_click_action_align) self.__click_vbox = Gtk.VBox() self.__click_vbox.set_spacing(2) self.__click_vbox.pack_start(self.__frame_click_action, False, False, 4) self.__frame_color_change = self.create_frame("Change panel colour to match wallpaper") self.__cb_panel_color_change = Gtk.CheckButton(label="Change panel colour") self.__cb_panel_color_change.connect("toggled", self.color_change_toggled) self.__cb_dock_panel_only = Gtk.CheckButton(label="Change colour of dock's panel only") self.__table_color_change = Gtk.Table(rows=2, columns=1, homogeneous=False) self.__table_color_change.attach(self.__cb_panel_color_change, 0, 1, 0, 1, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__table_color_change.attach(self.__cb_dock_panel_only, 0, 1, 1, 2, Gtk.AttachOptions.FILL, Gtk.AttachOptions.SHRINK, 2, 2) self.__frame_color_change_align = Gtk.Alignment(xalign=0.5, yalign=0.5, xscale=1.0, yscale=1.0) self.__frame_color_change_align.set_padding(0, 0, 12, 0) self.__frame_color_change_align.add(self.__table_color_change) self.__frame_color_change.add(self.__frame_color_change_align) self.__color_vbox = Gtk.VBox() self.__color_vbox.set_spacing(2) self.__color_vbox.pack_start(self.__frame_color_change, False, False, 4) # self.__vbox.pack_start(self.__frame_ind_type, True, True, 4) # self.__vbox.pack_start(self.__frame_unpinned_apps, True, True, 4) # self.__vbox.pack_start(self.__frame_click_action, True, True, 4) # self.__vbox.pack_start(self.__cb_multi_ind, True, True, 4) self.__vbox.pack_start(self.__notebook, True, True, 4) self.__notebook.append_page(self.__ind_vbox, Gtk.Label("Indicators")) self.__notebook.append_page(self.__ws_vbox, Gtk.Label("Workspaces")) self.__notebook.append_page(self.__click_vbox, Gtk.Label("Windows")) self.__notebook.append_page(self.__color_vbox, Gtk.Label("Panel Colour")) self.__vbox.pack_start(Gtk.HSeparator(), True, True, 4) self.__vbox.pack_start(self.__hbbx, False, False, 0) self.add(self.__vbox) self.show_all() def create_frame(self, caption): """ Convenience function to create a Gtk.Frame with a desired caption in bold text Returns: frame - the Gtk.Frame we created """ frame = Gtk.Frame(label="aaaa") lbl = frame.get_label_widget() lbl.set_use_markup(True) lbl.set_label("%s" %caption) frame.set_shadow_type(Gtk.ShadowType.NONE) return frame def win_delete_event(self, widget, event, data=None): """Callback for the preferences window delete event Do not delete the window, hide it instead so that it can be shown again later if needed """ self.hide() return True def win_cancel_button_press(self, widget, event): """Callback for the preferences window Cancel button press Hide the window """ self.hide() def draw_light_ind(self, drawing_area, event): """Draw a light indicator on a dark background""" ctx = self.__da_light_ind.window.cairo_create() ctx.set_source_rgb(0.1, 0.5, 0.5) ctx.rectangle(0, 0, self.DA_SIZE, self.DA_SIZE) ctx.fill() ctx.set_source_rgb(0.05, 0.05, 0.3) ctx.rectangle(1, 1, self.DA_SIZE-2, self.DA_SIZE-2) ctx.fill() ind_x = self.DA_SIZE/2 ind_y = ind_x rad_patt = cairo.RadialGradient(ind_x, ind_y, 2, ind_x, ind_y, 4) rad_patt.add_color_stop_rgba(0, 0.9, 0.9, 0.9, 1) rad_patt.add_color_stop_rgba(1, 0.0, 0.0, 0.0, 0) ctx.set_source(rad_patt) ctx.arc(ind_x, ind_y, 6, 0, 2*math.pi) ctx.fill() def draw_dark_ind(self, drawing_area, event): """Draw a dark indicator on a dark background.""" ctx = self.__da_dark_ind.window.cairo_create() ctx.set_source_rgb(0.5, 0.5, 0.5) ctx.rectangle(0, 0, self.DA_SIZE, self.DA_SIZE) ctx.fill() ctx.set_source_rgb(0.1, 0.8, 0.8) ctx.rectangle(1, 1, self.DA_SIZE-2, self.DA_SIZE-2) ctx.fill() ind_x = self.DA_SIZE/2 ind_y = ind_x rad_patt = cairo.RadialGradient(ind_x, ind_y, 2, ind_x, ind_y, 4) rad_patt.add_color_stop_rgba(0, 0.0, 0.0, 0.0, 1) rad_patt.add_color_stop_rgba(1, 0.9, 0.9, 0.9, 0) ctx.set_source(rad_patt) ctx.arc(ind_x, ind_y, 6, 0, 2*math.pi) ctx.fill() def color_change_toggled(self, widget): """Handler for the panel color change checkbox toggled event If the panel colour change option is selected, enable the checkbox that specifies whether or not all panels are to change colour """ self.__cb_dock_panel_only.set_sensitive(self.__cb_panel_color_change.get_active()) def rb_no_ind_toggled(self, widget): """ Handler for the no indicator radio button toggled event If the no indicator option is selected, disable the multiple indicator checkbox """ self.__cb_multi_ind.set_sensitive(self.__rb_no_ind.get_active() != True) def get_indicator_type(self): """Get the indicator type specified in the preferences window. Returns : IndicatorType """ if self.__rb_light_ind.get_active(): return IndicatorType.LIGHT elif self.__rb_dark_ind.get_active(): return IndicatorType.DARK else: return IndicatorType.NONE def set_indicator(self, indicator): """Set the indicator type Args : indicator - an IndicatorType """ if indicator == IndicatorType.LIGHT: self.__rb_light_ind.set_active(True) elif indicator == IndicatorType.DARK: self.__rb_dark_ind.set_active(True) else: self.__rb_no_ind.set_active(True) def get_multi_ind(self): """Gets whether or not to use display an indicator for each open window that a docked app has Returns: boolean """ return self.__cb_multi_ind.get_active() def set_multi_ind(self, use_multi_ind): """Sets whether or not to display multiple indicators Args: use_multi_ind - boolean """ self.__cb_multi_ind.set_active(use_multi_ind) def get_show_unpinned_apps_on_all_ws(self): """Gets whether unpinned apps are displayed in the dock on all workspaces Returns: boolean """ return self.__rb_unpinned_all_ws.get_active() def set_show_unpinned_apps_on_all_ws(self, show_on_all): """Sets whether unpinned apps are displayed in the dock on all workspaces Args: show_on_all - boolean """ if show_on_all: self.__rb_unpinned_all_ws.set_active(True) else: self.__rb_unpinned_cur_ws.set_active(True) def get_show_pinned_apps_on_all_ws(self): """Gets whether pinned apps are displayed in the dock on all workspaces Returns: boolean """ return self.__rb_pinned_all_ws.get_active() def set_show_pinned_apps_on_all_ws(self, show_on_all): """Sets whether pinned apps are displayed in the dock on all workspaces Args: show_on_all - boolean """ if show_on_all: self.__rb_pinned_all_ws.set_active(True) else: self.__rb_pinned_cur_ws.set_active(True) def get_click_restore_last_active(self): """Gets whether to restore only a running app's last active window when its dock icon is clicked and all windows are minimized Returns: boolean """ return self.__rb_restore_last_active.get_active() def set_click_restore_last_active(self, restore_last_active): """Sets whether to restore only a running app's last active window when its dock icon is clicked and all windows are minimized Args: restore_last_active - boolean """ if restore_last_active: self.__rb_restore_last_active.set_active(True) else: self.__rb_restore_all.set_active(True) def get_change_panel_color(self): """ Get whether the panel colour is to be changed according to the current wallpaper Returns: boolean """ return self.__cb_panel_color_change.get_active() def set_change_panel_color(self, change_color): """ Sets whether the panel color is to be changed according to the current wallpaper Args: change_color - boolean """ self.__cb_panel_color_change.set_active(change_color) def get_change_dock_color_only(self): """ Get whether only the panel containing the dock is to be changed when settung the panel colour according to the current wallpaper Returns: boolean """ return self.__cb_dock_panel_only.get_active() def set_change_dock_color_only(self, dock_only): """ Sets whether only the panel containing the dock is to be changed when settings the panel colour according to the current wallpaper Args: dock_only - boolean """ self.__cb_dock_panel_only.set_active(dock_only) def main(): """main function - debug code can go here""" dpw = DockPrefsWindow(Gtk.main_quit) Gtk.main() if __name__ == "__main__": main() dock-applet-0.70/src/dock_win_list.in000066400000000000000000000460631267226117400176330ustar00rootroot00000000000000#!/usr/bin/env python3 """ Provide a window showing a list of an app's open windows and also allow the user to select a window from the list and switch to it. Each item in the list will include the app's icon, an indicator to show if the window is currently active, the window's title, and a close icon allowing the window to be closed. The window will function in a similar way to a tooltip i.e. it will appear when the mouse hovers over a dock icon and will disappear if the mouse moves away from the window or the dock applet. It will also disappear when if dock icon is clicked of an item in the window is clicked """ # # Copyright (C) 1997-2003 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301, USA. # # Author: # Robin Thompson import gi gi.require_version("Gtk", "2.0") gi.require_version("Wnck", "1.0") from gi.repository import Gtk from gi.repository import Wnck from gi.repository import GdkPixbuf from gi.repository import Gio from gi.repository import Gdk from gi.repository import GObject import os import cairo import tempfile from time import sleep import docked_app CONST_TIMER_DELAY = 500 CONST_CLOSE_ICON_SIZE = 16 class DockWinList(Gtk.Window): """ Attributes : (aside from ui elements) __mouse_areas : a list containing Gdk.Rectangle objects - used when the window has been shown and defines the on screen areas in which the mouse pointer must stay, otherwise the window list will be hidden. The rectangles should therefore include the the applet, the area between the window and the applet, and the window itself with a suitable buffer area around it __timer_id : a ref to a timer used for priodically checking the mouse cursor position to see if it is within the areas specified in __mouse_areas __the_app : the docked_app to which the window list relates __icontheme : used for drawing app icons in the window list. This is set from from the Gtk.IconIcon used by the dock, and will therefore track changes to the icon theme whilst the dock is running """ def __init__(self, wnck_screen): """ create the window and its contents Args: wnck_screen: the wnck_screen of the applet """ super().__init__(title="", type=Gtk.WindowType.POPUP) self.wnck_screen = wnck_screen self.set_name("gtk-tooltips") # allows the window to inherit tooltip colours self.set_decorated(False) # we don't want a titlebar.. self.set_skip_taskbar_hint(True) # we don't want to be in the taskbar self.__icon_size = 32 # size of icons in the window self.__the_app = None self.__icontheme = None self.__timer_id = None self.__dismissed = False # create ui self.__vbox = Gtk.VBox() self.__vbox.set_spacing(0) # debug stuff # self.__button = Gtk.Button(label="Close", stock=Gtk.STOCK_CLOSE) # self.__button.connect("button-press-event", self.win_button_press) # self.connect("delete-event", Gtk.main_quit) # self.__mbutton = Gtk.Button(label="Menu...", stock=Gtk.STOCK_REFRESH) # self.__mbutton.connect("button-press-event", self.mbutton_press) # self.__hbx = Gtk.HButtonBox() # self.__hbx.set_layout(Gtk.ButtonBoxStyle.END) # self.__hbx.pack_start(self.__button, False, False, 4) # self.__hbx.pack_start(self.__mbutton, False, False, 4) # self.__vbox.pack_start(self.__hbx, True, True, 0) self.__lbl_app_name = Gtk.Label() self.__lbl_app_name.set_use_markup(True) self.__hbox_title = Gtk.HBox() self.__vbox.pack_start(self.__lbl_app_name, True, True, 0) # we use a treeview to list each window, so initialise it and its liststore self.__tree_view = Gtk.TreeView() self.__tree_view.set_headers_visible(False) # turn grid lines off, although they still seem to appear in some themes e.g. Menta self.__tree_view.set_grid_lines(Gtk.TreeViewGridLines.NONE) self.__tree_view.set_hover_selection(True) # the list contains the window icon, active indicator, window title, close icon and # the window itself self.__list_store = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str, GdkPixbuf.Pixbuf, Wnck.Window) self.__active_renderer = Gtk.CellRendererText() self.__icon_renderer = Gtk.CellRendererPixbuf() self.__title_renderer = Gtk.CellRendererText() self.__close_renderer = Gtk.CellRendererPixbuf() self.__close_renderer.set_alignment(1, 0.0) # align to to topright of the cell # create columns for the treeview self.__col_icon = Gtk.TreeViewColumn("", self.__icon_renderer, pixbuf=0) self.__col_icon.set_sizing(Gtk.TreeViewColumnSizing.FIXED) self.__col_icon.set_fixed_width(self.__icon_size+4) self.__col_active = Gtk.TreeViewColumn("", self.__active_renderer, text=1) self.__col_active.set_sizing(Gtk.TreeViewColumnSizing.FIXED) self.__col_active.set_fixed_width(20) self.__col_title = Gtk.TreeViewColumn("", self.__title_renderer, text=2) self.__col_title.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) self.__col_close = Gtk.TreeViewColumn("", self.__close_renderer, pixbuf=3) self.__col_close.set_sizing(Gtk.TreeViewColumnSizing.FIXED) self.__col_close.set_fixed_width(40) # add the columns self.__tree_view.set_model(self.__list_store) self.__tree_view.append_column(self.__col_icon) self.__tree_view.append_column(self.__col_active) self.__tree_view.append_column(self.__col_title) self.__tree_view.append_column(self.__col_close) self.__vbox.pack_start(self.__tree_view, True, True, 0) self.__vbox.show_all() self.add(self.__vbox) self.__mouse_areas = [] self.__tree_view.set_has_tooltip(True) self.__tree_view.connect("query-tooltip", self.query_tooltip) self.__tree_view.connect("button-release-event", self.button_release) # connect handlers for the show and hide events self.connect("show", self.win_shown) self.connect("hide", self.win_hidden) self.__pb_close = None def create_close_pixbuf(self): """ Create a 'close' icon (based on the stock close icon) for use in the treeview """ # create a pixbuf for holding the 'close' icon pb_close = self.render_icon(Gtk.STOCK_CLOSE, Gtk.IconSize.MENU, None) surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, CONST_CLOSE_ICON_SIZE, CONST_CLOSE_ICON_SIZE) ctx = cairo.Context(surface) Gdk.cairo_set_source_pixbuf(ctx, pb_close, 0, 0) ctx.paint() # we now need to copy the cairo surface to a pixbuf. The best way to do this would # be by calling GdkPixbuf.Pixbuf.new_from_data as in these comments. Unfortunately # this function does not seem to be introspectable and therefore doesn't work. # #self.__pb_close = GdkPixbuf.Pixbuf.new_from_data(surface.get_data(), # GdkPixbuf.Colorspace.RGB, # True, 8, pb_close.get_width(), # pb_close.get_height(), # 64) # Therefore we have to resort to writing the surface to a temporary .png file # and then loading it into our pixbuf ... handle, tempfn = tempfile.mkstemp() surface.write_to_png(tempfn) self.__pb_close = GdkPixbuf.Pixbuf.new_from_file(tempfn) os.remove(tempfn) def win_shown(self, widget): """ Event handler for the window's show event Instantiate a timer to periodically check the mouse cursor position Args : widget : the widget the caused the event (i.e. self) """ self.__timer_id = GObject.timeout_add(CONST_TIMER_DELAY, self.do_timer) def win_hidden(self, widget): """ Event handler for the window's hide event Delete the timer object """ if self.__timer_id != None: GObject.source_remove(self.__timer_id) self.__timer_id = None def query_tooltip(self, widget, x, y, keyboard_mode, tooltip): """ Handler for the query-tooltip event to determine whether or not to show the 'Close window' tooltip If the tooltip was triggered by the keyboard, don't do anything Get the mouse coords, and if the mouse if located over the close icon show the tooltip Args: widget - the widget that received the event i.e. our treeview x - mouse x coord y - mouse y coord keyboard_mode - was the tooltip triggered by the keyboard? tooltip - the tooltip object that will be displayed Returns: True to display the tooltip, False otherwise """ if keyboard_mode: return False path, col, xrel, yrel = self.__tree_view.get_path_at_pos(x, y) if col == self.__col_close: cell_area = self.__tree_view.get_cell_area(path, col) if (x >= cell_area.x + cell_area.width - CONST_CLOSE_ICON_SIZE) and \ (y <= cell_area.y + CONST_CLOSE_ICON_SIZE): tooltip.set_text("Close window") return True else: return False else: return False def button_release(self, widget, event): """ Handler for the button release event If the middle or right mouse button was pressed, do nothing If the mouse button was released over the close icon, close the associated window If the mouse button was released over any other part of the tree view item, activate the associated window Finally, hide the window list Args: widget : the widget the received the signal i.e. our treeview event : the event parameters Returns: True: to stop any other handlers from being invoked """ if event.button != 1: return False # let other handlers run path, col, xrel, yrel = self.__tree_view.get_path_at_pos(event.x, event.y) sel_iter = self.__list_store.get_iter(path) win = self.__list_store.get_value(sel_iter, 4) if col == self.__col_close: cell_area = self.__tree_view.get_cell_area(path, col) if (event.x >= cell_area.x + cell_area.width - CONST_CLOSE_ICON_SIZE) and \ (event.y <= cell_area.y + CONST_CLOSE_ICON_SIZE): win.close(event.time) self.hide() return True self.hide() # if the window to be activated is not on the current workspace, switch to that workspace wnck_aws = self.wnck_screen.get_active_workspace() wnck_ws = win.get_workspace() # the windows's current workspace can be None if it is pinned to all workspaces # or it is not on any at all... # (fix for https://bugs.launchpad.net/ubuntu/+source/mate-dock-applet/+bug/1550392 and # https://bugs.launchpad.net/ubuntu-mate/+bug/1555336 (regarding software updater)) if (wnck_aws is not None) and (wnck_ws is not None) and (wnck_aws != wnck_ws): wnck_ws.activate(0) sleep(0.01) win.activate(0) return True def do_timer(self): """ Check the current mouse position and if it is not within any of the rectangles in self.__mouse_area hide the window """ # get the mouse x y root_win, x, y, mask = self.get_screen().get_root_window().get_pointer() if not self.point_is_in_mouse_areas(x, y): self.hide() self.__timer_id = None return False return True def add_to_list(self, icon, is_active, title, window): """ Add an item to the window list Args: icon - the app's icon in pixbuf format is_active - True if the window is active, False otherwise title - the windows's title window - the wnck window relating to the app """ # set the active indicator if is_active: active_text = "*" else: active_text = "" if icon is None: icon_pb = self.render_icon(Gtk.STOCK_EXECUTE, Gtk.IconSize.MENU, None) else: icon_pb = icon self.__list_store.append([icon_pb, active_text, title, self.__pb_close, window]) def clear_win_list(self): """ Clear the list of open windows """ self.__list_store.clear() def set_app_name(self, app_name): """ Sets the app name in the window Args - app name """ # use pango markup to make the app name bold and large self.__lbl_app_name.set_markup('%s' %(app_name)) def win_button_press(self, widget, event): """ this is for debug puposes only""" Gtk.main_quit() def setup_list(self): """ Setup the app list Set the app name Re-create the close icon in case the icon theme has changed For every window the app has open add an entry containing the app icon, window title, an indicator if the window is the active window, and a close icon """ self.create_close_pixbuf() # reduce the size of the window - it will autosize to fit the contents... self.__col_title.set_sizing(Gtk.TreeViewColumnSizing.FIXED) self.__col_title.set_fixed_width(150) self.resize(100, 10) self.__col_title.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) self.set_app_name(self.__the_app.app_name) win_list = self.__the_app.get_wnck_windows() for win in win_list: if self.__icontheme.has_icon(self.__the_app.icon_name): # draw the app icon at the size we want icon_info = self.__icontheme.choose_icon([self.__the_app.icon_name, None], self.__icon_size, 0) try: pixbuf = icon_info.load_icon() except GLib.GError: # default to a stock icon if we couldn't load the app icon pixbuf = self.render_icon(Gtk.STOCK_EXECUTE, Gtk.IconSize.DND, None) else: pixbuf = self.__the_app.app_pb.scale_simple(self.__icon_size, self.__icon_size, GdkPixbuf.InterpType.BILINEAR) is_active = (len(win_list) == 1) or \ (win == self.__the_app.last_active_win) self.add_to_list(pixbuf, is_active, win.get_name(), win) def mbutton_press(self, widget, event): """ this is for debug purposes only and demonstrates that menu.popup does not work with Gtk 2 """ self.menu = Gtk.Menu() menu_item = Gtk.MenuItem("A menu item") self.menu.append(menu_item) menu_item.show() self.menu.popup(None, None, None, event.button, event.time) def clear_mouse_areas(self): """ Clear the mouse areas list """ self.__mouse_areas = [] def add_mouse_area(self, rect): """ Add a rectangle to the __mouse_area_list Args: rect - a Gdk.Rectangle """ self.__mouse_areas.append(rect) def point_is_in_mouse_areas(self, x, y): """ Checks to see if a specified position on the screen is within any of the self.__mouse_areas rectangle list Args: x : the x position y : the y position Returns: True if the position is within one of the rectangles in self.__mouse_areas, False otherwise """ for rect in self.__mouse_areas: if ((x >= rect.x) and (x <= rect.x + rect.width)) and \ ((y >= rect.y) and (y <= rect.y + rect.height)): return True return False def get_app(self): """ Return the docked app the window list refers to Returns: A docked_app """ return self.__the_app def set_app(self, app): """ Set the docked app the window list refers to Args : app - a docked_app """ self.__the_app = app the_app = property(get_app, set_app) def get_icontheme(self): """ Return the icontheme Returns: A Gtk.Icontheme """ return self.__icontheme def set_icontheme(self, the_icontheme): """ Sets the icontheme currently being used Args : the_icontheme """ self.__icontheme = the_icontheme icontheme = property(get_icontheme, set_icontheme) the_app = property(get_app, set_app) def main(): """ main function - debugging code goes here """ # thewin = DockWinList() # thewin.set_app_name("Testing....") # thewin.add_to_list(None, False, "Win 1") # thewin.add_to_list(None, True, "Win 2 is active") # thewin.add_to_list(None, False, "Win 3") # thewin.show_all() # thewin.move(100, 110) # pos = thewin.get_position() # size = thewin.get_size() # print("pos %d %d" %(pos[0], pos[1])) # print("size %d %d" %(size[0], size[1])) # thewin.add_mouse_area(Gdk.Rectangle(pos[0]-15, pos[1]-15, size[0]+30, size[1]+30)) # thewin.add_mouse_area(Gdk.Rectangle(0, 0, 48, 500)) # thewin.add_mouse_area(Gdk.Rectangle(48, 110, 100, size[1])) # Gtk.main() return if __name__ == "__main__": main() dock-applet-0.70/src/dock_xml.in000066400000000000000000000175311267226117400166010ustar00rootroot00000000000000#!/usr/bin/env python3 """Provide functionality allowing the dock applets configuration to be saved and loaded Store the configuration in a specified XML file The file will contain the following information: : a list of all pinned app's .desktop files : the indicator type (light or dark) : whether unpinned apps from all workspaces are to be displayed : whether an indicator for each open window is to be displayed """ # Copyright (C) 1997-2003 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301, USA. # # Author: # Robin Thompson import xml.etree.ElementTree as ET def write_xml (filename, desktop_files, light_ind, unpinned_from_all, multi_ind, click_restore_last_active, pinned_from_all, panel_change_color, dock_panel_change_only): """ Write the xml file using the specified information The xml file will be in the following format: etc... int True or False True or False True or False True or False True or False True or False Args: filename - the filename to use. If the file already exists it will be overwritten. desktop_files: a list containing the names of the applet's pinned app's .desktop files e.g. ['pluma.desktop'] light_ind : boolean - Whether or not a light indicator is to be used unpinned_from_all : boolean - Whethr or not unpinned apps from all workspaces are to be shown multi_ind : Whether indicators for each of an app's open windows are to be shown click_restore_last_active: whether clicking a running app's dock icon restores only the last active window or all windows pinned_from_all : boolean - whether or not pinned apps from all workspaces are to be shown (new with V0.66) panel_change_color : boolean - Whether or not MATE panels are to change color according to the desktop wallpaper (new with V0.66) dock_panel_change_only : boolean - whether panel colour changes are to limited to only the panel containing the dock applet (new with V0.66) Returns: Boolean - True if the file was written successfully, False otherwise """ root = ET.Element("root") pa_el = ET.Element ("pinned_apps") for df in desktop_files: df_el = ET.Element("desktop_file", name = df) pa_el.append(df_el) ind_el = ET.Element("ind_type", light = "%d" %light_ind) ufa_el = ET.Element("unpinned_from_all", show_all = "%s" %unpinned_from_all) mi_el = ET.Element("multi_ind", show_multi = "%s" %multi_ind) crla_el = ET.Element("click_restore_last_active", restore_last_active_only = "%s" %click_restore_last_active) pfa_el = ET.Element("pinned_from_all", pfa = "%s" %pinned_from_all) pcc_el = ET.Element("panel_change_color", pcc = "%s" %panel_change_color) dcc_el = ET.Element("dock_panel_change_only", dcc = "%s" %dock_panel_change_only) root.append (pa_el) root.append (ind_el) root.append (ufa_el) root.append (mi_el) root.append (crla_el) root.append (pfa_el) root.append (pcc_el) root.append (dcc_el) try: ET.ElementTree(root).write(filename, xml_declaration=True) except FileNotFoundError: return False # invalid file or path name return True def read_xml (filename): """ Reads an xml file created using the write_xml method Args: filename - the filename to read. Returns: boolean : True if the file was read successfully, False otherwise A tuple containing the following: a list of the .desktop files in the file (i.e. the pinned apps) an integer - the indicator setting a boolean - the unpinned from all setting a boolean - the multiple indicators setting a boolean - the restore last active settings a boolean - the pinned from all setting a boolean - the change panel colour setting a boolean - the change dock panel color only setting """ try: root = ET.parse(filename) except FileNotFoundError: return [False] df_list = [] pinned_apps = root.find("pinned_apps") if pinned_apps is not None: for df in pinned_apps: df_list.append(df.get("name")) # note - values may be missing from the config file e.g. if a new version of the applet # adds a new configuration settings. If this happens, we just assume a default option # rather than reporting an error # ind_el = root.find("ind_type") if ind_el is not None: light_ind = int(ind_el.get("light")) else: light_ind = 0 ufa_el = root.find("unpinned_from_all") if ufa_el is not None: show_all = ufa_el.get("show_all") == "True" else: show_all = True mi_el = root.find("multi_ind") if mi_el is not None: multi_ind = mi_el.get("show_multi") == "True" else: multi_ind = False crla_el = root.find("click_restore_last_active") if crla_el is not None: click_restore_last_active = crla_el.get("restore_last_active_only") == "True" else: click_restore_last_active = True crla_el = root.find("pinned_from_all") if crla_el is not None: pinned_from_all = crla_el.get("pfa") == "True" else: pinned_from_all = True crla_el = root.find("panel_change_color") if crla_el is not None: panel_change_color = crla_el.get("pcc") == "True" else: panel_change_color = False crla_el = root.find("dock_panel_change_only") if crla_el is not None: dock_panel_change_only = crla_el.get("dcc") == "True" else: dock_panel_change_only = False return [True, df_list, light_ind, show_all, multi_ind, click_restore_last_active, \ pinned_from_all, panel_change_color, dock_panel_change_only] def main(): """Main function. Debugging code can go here """ # write_xml ("/home/robin/tmp/text.xml", ["thing.desktop","blah.desktop"], 99, False, True, False, False, True, False) results = read_xml ("/home/robin/tmp/text.xml") if results[0] == True: for df in results[1]: print ("Desktop file found: %s" %df) print ("Use light ind = %d" %results[2]) print ("Show unpinned on all = %s" %results[3]) print ("Multi ind = %s" %results[4]) print ("Click restore all = %s" %results[5]) print ("pinned on all = %s" %results[6]) print ("panel change color = %s" %results[7]) print ("dock panel only = %s" %results[8]) else: print ("Error reading file....") if __name__ == "__main__": main() dock-applet-0.70/src/docked_app.in000066400000000000000000001271631267226117400170750ustar00rootroot00000000000000#!/usr/bin/env python3 """Provide functionality relating to an app in a dock. Allow info relating to a running app (e.g. icon, command line, .desktop file location, running processes, open windows etc) to be obtained from the information that libWnck provides Provide a surface on which the application's icon and the running indicator can be drawn Ensure that the app's icon and indicator are always drawn correctly according to the size and orienation of the panel Provide visual feedback to the user when an app is lauched by pulsating the app's icon Draw a highlight around the app's icon if it is the foreground app Maintain a list of all of the app's running processes and their windows Ensure that the application's windows visually minimise to the application's icon on the dock """ # # Copyright (C) 1997-2003 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation; either version 2 of the # License, or (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, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301, USA. # # Author: # Robin Thompson import gi gi.require_version("Gtk", "2.0") from gi.repository import Gtk from gi.repository import MatePanelApplet from gi.repository import Gdk gi.require_version("Wnck", "1.0") from gi.repository import Wnck #from gi.repository import GdkPixbuf from gi.repository import Gio #from gi.repository import GLib from gi.repository import GObject import cairo import math import xdg.DesktopEntry as DesktopEntry import os import os.path import subprocess from collections import namedtuple import dock_prefs from log_it import log_it as log_it ColorTup = namedtuple('ColorTup', ['r', 'g', 'b']) AppInfoTup = namedtuple('AppInfoTup', ['app', 'pid', 'windows']) ActionTup = namedtuple('ActionTup', ['name', 'command']) def im_get_comp_color(filename): """Find the complimentary colour of the average colour of an image. Uses ImageMagick to read and process the image Args: filename : the filename of the image Returns: a tuple of r,g,b values (0-255) """ cmdstr = "convert "+filename +" -colors 16 -depth 8 -format ""%c"" " + \ "histogram:info:|sort -rn|head -n 1| grep -oe '#[^\s]*'" cmd = subprocess.Popen(cmdstr, shell=True, stdout=subprocess.PIPE) for line in cmd.stdout: pass ll1 = str(line) astr = ll1[2:9] cmdstr = "convert xc:'" + astr + "' -modulate 100,100,0 -depth 8 txt:" cmd = subprocess.Popen(cmdstr, shell=True, stdout=subprocess.PIPE) for line in cmd.stdout: pass l1p = str(line) lll = l1p.split(" ") astr = lll astr = lll[3].lstrip("#") if len(astr) == 8: #remove alpha channel astr = astr[0:6] red = int(astr[0:2], 16) green = int(astr[2:4], 16) blue = int(astr[4:6], 16) return red, green, blue def get_avg_color(pixbuf): """calculate the average colour of a pixbuf. Read all of the pixel values in a pixbuf (excluding those which are below a certain alpha value) and calculate the average colour of all the contained colours Args: pixbuf : a pixbuf object containing the image Returns: a tuple of r,g,b values (0-255) """ width = pixbuf.props.width rowstride = pixbuf.props.rowstride height = pixbuf.props.height has_alpha = pixbuf.get_has_alpha() pixels = pixbuf.get_pixels() nchannels = pixbuf.get_n_channels() ## convert the pixels into an rgb array with alpha channel data = [] for y_count in range(height-1): x_count = 0 while x_count < (width * nchannels): pix_red = pixels[x_count+(rowstride*y_count)] pix_green = pixels[x_count+1+(rowstride*y_count)] pix_blue = pixels[x_count+2+(rowstride*y_count)] if has_alpha: pix_alpha = pixels[x_count+3+(rowstride*y_count)] else: pix_alpha = 255 data.append([pix_red, pix_green, pix_blue, pix_alpha]) x_count += nchannels red = 0 green = 0 blue = 0 num_counted = 0 for pixels in range(len(data)): if data[pixels][3] > 200: # only count pixel if alpha above this level red += data[pixels][0] green += data[pixels][1] blue += data[pixels][2] num_counted += 1 if num_counted > 0: ravg = int(red/num_counted) gavg = int(green/num_counted) bavg = int(blue/num_counted) else: # in case of a bad icon assume a grey average colour # this should fix a division by zero error that occurred at this point in the # code, but which I've only seen once and never been able to duplicate ravg = gavg = bavg = 128 return ravg, gavg, bavg CONST_PULSE_STEPS = 20 CONST_PULSE_DELAY = 40 class PulseTimer(object): """Class to help provide feedback when a user launches an app from the dock. Instantiates a timer which periodically redraws an application in the dock at various transparency levels until the timer has been run a certain number of times Attributes: app = the DockedApp object which we want to pulsate timer_id = the id of the timer that is instantiated """ def __init__(self, app): """Init for the PulseTimer class. Sets everything up by creating the timer, setting a reference to the DockedApp and telling the app that it is pulsing Arguments: app : the DockedApp object """ self.app = app self.app.pulse_step = 0 self.app.is_pulsing = True self.timer_id = GObject.timeout_add(CONST_PULSE_DELAY, self.do_timer) def do_timer(self): """The timer function. Increments the number of times the time function has been called. If it hasn't reached the maximum number, increment the app's pulse counter. If the maximum number has been reached, stop the app pulsing and delete the timer. Redraw the app's icon """ if self.app.pulse_step != CONST_PULSE_STEPS: self.app.pulse_step += 1 else: self.app.is_pulsing = False GObject.source_remove(self.timer_id) self.app.queue_draw() return True CONST_FLASH_DELAY = 330 class FlashTimer(object): """Class to help provide visual feedback when an app requries user attention. Instantiates a timer which periodically causes the app's dock icon to flash on and off until the app no longer needs attention Attributes: app = the DockedApp object which we want to flashh timer_id = the id of the timer that is instantiated """ def __init__(self, app): """Init for the FlashTimer class. Sets everything up by creating the timer, setting a reference to the DockedApp and setting the inital flash state to off Arguments: app : the DockedApp object """ self.app = app self.app.is_flashing = True self.app.flash_on = False self.timer_id = GObject.timeout_add(CONST_FLASH_DELAY, self.do_timer) # make the app redraw itself app.queue_draw() def do_timer(self): """The timer function. If the app no longer needs attention, stop it flashing and delete the timer. Otherwise, invert the flash. Finally,Redraw the app's icon """ if self.app.is_flashing == True: self.app.flash_on = not self.app.flash_on else: GObject.source_remove(self.timer_id) self.app.queue_draw() return True class DockedApp(object): """Provide a docked app class Attributes: app_info : list of AppInfoTups to hold details of all running processes the app has wnck_class : the WnckClass object relating to the app app_name : e.g. Google Chrome, used for tooltips and the applet right click menu etc rc_actions : list of ActionTups to hold details of right click actions the app supports cmd_line : the command line and arguments used to start the app icon_name : name of the app icon icon_filename : the filename of the app icon desktop_file : the filename of the app's .desktop file wm_class_name: icon_geometry_set : boolean - indicates whether the app windows have been set to minimise to the dock applet_win : the Gdk.Window of the panel applet applet_orient : the applet orientation drawing_area: Gtk.Label- provides a surface on which the app icon can be drawn drawing_area_size : the size in pixels (height AND width) that we have to draw in is_pulsing : boolean - True if the app is pulsing pulse_step : a count of how far we are through the pulse animation app_pb : a pixbuf of the app's icon highlight_colour : ColorTup of the colurs used to highlight the app when it is foreground is_active : boolean - True = the app is the foreground app has_mouse : boolean - True = the mouse is over the app's icon is_pinned : boolean - Whether or not the app is pinned to the dock indicator : the type of indictor (light or dark) to draw under running apps last_active_win : the wnck_window of the app's last active window """ def __init__(self): """ Init for the DockApplet class. Create a surface to draw the app icon on Set detault values """ super().__init__() self.app_info = [] self.wnck_class = None self.app_name = "" self.rc_actions = [] self.cmd_line = "" self.icon_name = "" self.icon_filename = "" self.desktop_file = "" self.wm_class_name = "" self.icon_geometry_set = False self.applet_win = None self.applet_orient = None # all drawing is done to a Gtk.Label rather than e.g. a drawing area or event box # this allows panel transparency/custom backgrounds to be honoured # However, the downside of this is that mouse events cannot be handled by # this class and instead have to be done by the applet itself self.drawing_area = Gtk.Label() self.drawing_area.set_app_paintable(True) self.drawing_area_size = 0 self.is_pulsing = False self.pulse_step = 0 self.is_flashing = False self.flash_on = False self.app_pb = None self.highlight_color = ColorTup(r=0.0, g=0.0, b=0.0) self.is_active = False self.has_mouse = False self.is_pinned = False self.indicator = dock_prefs.IndicatorType.LIGHT # default to a light indicator self.multi_ind = False # default to single indicator self.last_active_win = None # the app's last active wnck_window self.drawing_area.connect("expose-event", self.do_expose_event) # the draw event def has_wnck_app (self, wnck_app): """ see if this app has a process with the specified wnck_app Returns True if the wnck_app is found, False otherwise """ ret_val = False for aai in self.app_info: if aai.app == wnck_app: ret_val = True break return ret_val def setup_from_wnck(self, wnck_app, wnck_class): """Set up for an already running app, obtaining info from libWnck. Will attempt to get the app path, icon path, .desktop file, currently open windows and pids. Args: wnck_app : a WnckApplication object relating to the app wnck_class : a WnckClass obect relating to the app Returns: True if successful, False otherwise """ new_app_info = AppInfoTup(app=wnck_app, pid=wnck_app.get_pid(), windows=[]) self.wnck_class = wnck_class self.set_app_name(wnck_app.get_name()) self.wm_class_name = wnck_class.get_res_class() # afaict this is Gtk 2 only # in Gtk 3 it will need to be # replaced with .get_id() instead # get the currently open windows for win in wnck_app.get_windows(): win.connect("state-changed", self.win_state_changed) win_class = win.get_class_group() win_class_name = win_class.get_res_class() if ((win.get_window_type() == Wnck.WindowType.NORMAL) or \ (win.get_window_type() == Wnck.WindowType.DIALOG)) and \ (self.wm_class_name == win_class_name) and \ (win.is_skip_tasklist() == False): new_app_info.windows.append(win.get_xid()) self.app_info.append(new_app_info) if new_app_info.pid != 0: self.get_cmdline_from_pid(new_app_info.pid) if not self.get_desktop_from_custom_launcher(os.path.expanduser("~/.local/share/applications/")): if not self.get_desktop_from_app_info(os.path.expanduser("~/.local/share/applications/")): if not self.brute_force_find_desktop_file(os.path.expanduser("~/.local/share/applications/")): if not self.get_desktop_from_app_info("/usr/share/applications/"): if not self.get_desktop_from_app_info("/usr/local/share/applications/"): if not self.brute_force_find_desktop_file("/usr/share/applications/"): if not self.brute_force_find_desktop_file("/usr/local/share/applications/"): return False # now that we have the .desktop file, we can get the icon etc return self.read_info_from_desktop_file() def set_app_name(self, app_name): """sets the app name. Stores the entire name, which may or may not also contain a document title or other app specific info. This will need to be parsed when necessary to obtain the actual app name Args: The app name """ self.app_name = app_name def get_cmdline_from_pid(self, pid): """ Find the command line and arguments used to launch the app Use the ps command to return the command line and arguments for the specified pid Set self.path to the full command line Args: pid - a process id """ cmdstr = "xargs -0 < /proc/%d/cmdline" %pid cmd = subprocess.Popen(cmdstr, shell=True, stdout=subprocess.PIPE) for line in cmd.stdout: pass if line is not None: self.cmd_line = line.decode("utf-8") def get_num_windows(self): """Return the number of windows this app and all of its processes have open Returns: the number of windows """ num_win = 0 for app_list in self.app_info: for wnck_win in app_list.app.get_windows(): win_type = wnck_win.get_window_type() win_cg = wnck_win.get_class_group() win_wm_class_name = win_cg.get_res_class() if ((win_type == Wnck.WindowType.NORMAL) or \ (win_type == Wnck.WindowType.DIALOG)) and \ (win_wm_class_name.lower() == self.wm_class_name.lower()) and \ (wnck_win.is_skip_tasklist() == False): num_win += 1 return num_win def get_wnck_windows(self): """Return a list of all of the wnck_windows the app has open""" win_list = [] for app_list in self.app_info: for wnck_win in app_list.app.get_windows(): win_type = wnck_win.get_window_type() win_cg = wnck_win.get_class_group() win_wm_class_name = win_cg.get_res_class() if ((win_type == Wnck.WindowType.NORMAL) or \ (win_type == Wnck.WindowType.DIALOG)) and \ (win_wm_class_name.lower() == self.wm_class_name.lower()) and \ (wnck_win.is_skip_tasklist() == False): win_list.append(wnck_win) return win_list def has_windows_on_workspace(self, wnck_workspace): """ test whether the app has at least one window open on a specified workspace Args: wnck_workspace - the workspace to check for Returns: boolean """ win_list = self.get_wnck_windows() for win in win_list: win_ws = win.get_workspace() if win_ws == wnck_workspace: return True return False def has_unminimized_windows(self): """ test whether the app has at least one unminimized window Returns: boolean """ win_list = self.get_wnck_windows() for win in win_list: if not win.is_minimized(): return True return False def hide_icon(self): """ Hides the app's icon""" self.drawing_area.set_visible(False) def show_icon(self): """ Shows the app's icon""" self.drawing_area.set_visible(True) def is_visible(self): """ Method which returns whether or not the app's icon is visible Returns: boolean """ return self.drawing_area.get_visible() def get_desktop_from_app_info(self, srch_dir): """Attempt to find the .desktop file for the app based on the app wm_class and app name. Will search for files in srch_dir and its sub directories. In addition to .desktop looks for the following variations of name for the desktop flie: lowercase.desktop uppercase.desktop .desktop lowercase.desktop with space characters replaced by '-'.desktop lowercase with space characters replaced by "-",desktop .desktop If the destop file is found, self.desktop_file is set accordingly, otherwise self.desktop_file is set to None Args: srch_dir : The directory to be searced for the desktop file Returns: True if the desktop file was found, False otherwise """ for the_dir, dir_list, file_list in os.walk(srch_dir): dfname = the_dir + self.wm_class_name + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True dfname = the_dir + self.wm_class_name.lower() + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True dfname = the_dir + self.wm_class_name.upper() + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True dfname = the_dir + self.app_name + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True dfname = the_dir + self.app_name.lower() + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True dfname = the_dir + self.app_name.replace(" ", "-") + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True dfname = the_dir + self.app_name.replace(" ", "-").lower() + ".desktop" if os.path.isfile(dfname): self.desktop_file = dfname return True self.desktop_file = None return False def brute_force_find_desktop_file(self, srch_dir): """Attempt to find the .desktop file for an app by examining all of the .desktop files in a specified directory and its subdirectories. A match occurs when any of the following condition are met: the name field in the .desktop file is the same as self.app_name the uppercased first word of self.app_name is the same as the the uppercased name field of the .desktop file the Exec field is found within self.cmd_line the StartupWMClass is the same as self.wm_class_name If a match is found, self.desktop_file is set accordingly Note - the app must be running, and self.pid, self.cmd_line and self.app_name must have been set up Args: srch_dir - the directory to search Returns: True if the .desktop file was found, False otherwise """ # if the search dir doesn't exist, don't do anything if os.path.isdir(srch_dir) == False: return False # split the app name into it's various parts using ' - ' as a # delimeter name_parts = self.app_name.upper().split(" - ") for name in name_parts: name = name.strip() # split the command line into the command and various arguments # note: strings that contain spaces will obviously be split into # more than one part, but that's not a problem as we don't care # about string aguments cmd_parts=self.cmd_line.split() # search search_dir and any sub-directories it contains for .desktop files for the_dir, dir_list, file_list in os.walk(srch_dir): for the_file in file_list: if the_file.endswith(".desktop"): the_de = DesktopEntry.DesktopEntry(os.path.join(the_dir, the_file)) # if the app name in the desktop file matches any of the parts # of the app name, we have a match try: unused_var = name_parts.index(the_de.getName().upper()) name_part_found = True except ValueError: name_part_found = False # remove command line args from the Exec field of the .desktop de_exec = the_de.getExec() exec_found = False if (de_exec is not None) and (de_exec != ""): de_exec = de_exec.split() # if the exec field of the .desktop does not specify a path for the # the executeable we can simply check that the app command line ends # with the same command as the .desktop file contains. So... if len(cmd_parts) > 0: exec_found = cmd_parts[0].endswith(de_exec[0]) # we can now search within self.cmd_line for de_exec # Note: we don't test for equality because some apps are launched via # another (e.g. python apps) and therefore will be one of the command # line args. An example of this is Ubuntu Software Center - in the # .desktop file the Exec field is '/usr/bin/software-center' whilst # it's command line while running is 'usr/bin/python /usr/bin/software-center' if not exec_found: for exec_part in de_exec: try: unused_var = cmd_parts.index(exec_part) exec_found = True except ValueError: pass if exec_found: break # check that the wm_classes match wm_class_found = False de_wm_class = the_de.getStartupWMClass() if ((de_wm_class is not None) and (de_wm_class != "")) and \ ((self.wm_class_name is not None) and (self.wm_class_name != "")): # if both the .desktop and the app have a wm_class then they # MUST match for the app to be found, and nm_part_found and # exec_found become irrelevant if (de_wm_class.lower() == self.wm_class_name.lower()): wm_class_found = True exec_found = wm_class_found name_part_found = wm_class_found if (name_part_found == True) or \ (exec_found == True) or \ (wm_class_found == True): self.desktop_file = os.path.join(the_dir, the_file) return True self.desktop_file = None return False def get_desktop_from_custom_launcher(self, srch_dir): """ Search the custom launchers in a specified directory for one where the Exec field is found within self.cmd_line If a match is found found, self.desktop_file is set accordingly Note: All custom launchers .desktop filenames start with "mda_" Args: srch_dir : the directory to search Returns: True if a match was found, False otherwise """ # if the search dir doesn't exist, don't do anything if os.path.isdir(srch_dir) == False: return False for the_file in os.listdir(srch_dir): if (the_file.startswith("mda_")) and (the_file.endswith(".desktop")): the_de = DesktopEntry.DesktopEntry(srch_dir+the_file) # remove command line args from the Exec field of the .desktop de_exec = the_de.getExec().split(None, 1)[0] if (self.cmd_line.find(de_exec) != -1) and \ (self.wm_class_name.upper()==the_de.getStartupWMClass().upper()): self.desktop_file = srch_dir+the_file return True def set_all_windows_icon_geometry(self, root_x, root_y): """Set the location on screen where all of the app's windows will be minimised to. Args: root_x : The X position in root window coordinates root_y : The Y position in root window coordinates """ # get the height/width of the drawing area alloc = self.drawing_area.get_allocation() # iterate through of the windows this process has open and set their minimize location for window in self.get_wnck_windows(): win_type = window.get_window_type() if ((win_type == Wnck.WindowType.NORMAL) or \ (win_type == Wnck.WindowType.DIALOG)) and \ (window.is_skip_tasklist() == False): window.set_icon_geometry(root_x, root_y, alloc.width, alloc.height) return True def set_drawing_area_size(self, size): """Set the size request of the app's drawing area. The drawing area is always presumeed to be square Args : size : the size in pixels """ self.drawing_area_size = size self.drawing_area.set_size_request(size, size) def queue_draw(self): """Queue the app's icon to be redrawn. """ self.drawing_area.queue_draw() def set_indicator(self, indicator): """Set the running indicator type to the value specified Args: indicator - the indicator type """ self.indicator = indicator def set_multi_ind (self, multi_ind): """ Set whether to use an indicator for each open window Args: multi_ind - boolean """ self.multi_ind = multi_ind def set_icon_geometry(self): """Ensure that the minimize location is for all of the app's open windows is recalculated. """ self.icon_geometry_set = False self.queue_draw() def is_running(self): """Is the app running or not? Returns: True if the app is running, False if not """ ret_val = False for ainf in self.app_info: if ainf.pid != -1: ret_val = True break return ret_val def has_desktop_file(self): """ Does the app have a .desktop file? Returns: True if there is a desktop file, False otherwise """ return self.desktop_file is not None def read_info_from_desktop_file(self): """Attempt to read from read the app's desktop file. Will try to read the icon name and app name from the desktop file Will also get the executeable path if we don't already have this Will read the details of any right click menu options the .desktop file defines Returns: True if successful, False otherwise """ if self.desktop_file: dfile = DesktopEntry.DesktopEntry(self.desktop_file) self.app_name = dfile.getName() self.icon_name = dfile.getIcon() # if the desktop file does not specify an icon name, use the app name instead if (self.icon_name is None) or (self.icon_name == ""): self.icon_name = self.app_name.lower() if self.cmd_line == "": self.cmd_line = dfile.getExec() # now read the right click actions the app supports. These can be specified in # two ways - by a key named 'X-Ayatana-Desktop-Shortcuts' or by an Actions key # xads = dfile.get('X-Ayatana-Desktop-Shortcuts') if (xads is not None) and (xads != ""): #the shortcut ids are in the the form of a semi-colon separated string for rca in xads.split(";"): rcname = dfile.get("Name", group = "%s Shortcut Group" %rca) rcexec = dfile.get("Exec", group = "%s Shortcut Group" %rca) if (rcname != "") and (rcexec !=""): new_action = ActionTup(name=rcname, command= rcexec) self.rc_actions.append(new_action) for action in dfile.getActions(): rcname = dfile.get("Name", group ="Desktop Action %s" %action) rcexec = dfile.get("Exec", group = "Desktop Action %s" %action) if (rcname != "") and (rcexec !=""): new_action = ActionTup(name=rcname, command= rcexec) self.rc_actions.append(new_action) return True return False def app_has_custom_launcher(self): """ Determines whether the docked app has a custom launcher Examine the .desktop filename. If it starts with "~/.local/share/applications/mda_" the app has a custom launcher Returns : True if the app has a custom launcher, False otherwise """ cl_start = os.path.expanduser("~/.local/share/applications/mda_") return os.path.expanduser(self.desktop_file).beginswith(cl_start) def win_state_changed(self, wnck_win, changed_mask, new_state): """Handler for the wnck_window state-changed event If the app needs attention and we're not already flashing the icon start it flashing. If the app icon is not visible, make it visible If the app doesn't need attention and its icon is flashing, stop it flashing """ if ((new_state & Wnck.WindowState.DEMANDS_ATTENTION) != 0) or\ ((new_state & Wnck.WindowState.URGENT) != 0): if not self.is_flashing: self.is_flashing = True self.flash_on = False # initial flash state = off Flasher = FlashTimer(self) if not self.is_visible(): self.show_icon() self.set_icon_geometry else: if self.is_flashing == True: # we need to turn flashing off self.is_flashing = False self.queue_draw() # the timer will handle the rest .... # hiding the icon (if necessary) will be taken care of next time the user # changes workspace def do_expose_event(self, drawing_area, event): """The main drawing event for the docked app. Does the following: draw the app icon if the mouse is over the app icon, highlight the icon if the is running draw the app running indicators(according to the applet orientation) if the app is the foreground app, highlight the background with a gradient fill if the app is pulsing, draw the icon with a variable level of transparency according to the pulse count if the app is flashing, draw the icon either fully opaque or completely transparent according to its flash state Additionally, if necessary, calculate and set the app's on screen minimise location (this is done here as it can only be done after the drawing area has been created, set to the required size and then shown...) Args: drawing_area : the drawing area that related to the event. Will always be the same as self.drawing area event : the event arguments """ if self.icon_geometry_set == False: # the minimize location needs to be set .. if event.window is not None: # get the minimise coordinates dxc, dyc = event.window.get_root_coords(event.area.x, event.area.y) # set the coordinates if self.set_all_windows_icon_geometry(dxc, dyc): self.icon_geometry_set = True # there are lots of drawing operations to be done, so do them to an offscreen # surface and when all is finished copy this to the docked app offscreen_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.drawing_area_size, self.drawing_area_size) ctx = cairo.Context(offscreen_surface) # convert the highlight values to their cairo equivalents red = self.highlight_color.r / 255 green = self.highlight_color.g / 255 blue = self.highlight_color.b / 255 if self.is_active: # draw a background gradient according to the applet orientation if self.applet_orient == MatePanelApplet.AppletOrient.RIGHT: pattern = cairo.LinearGradient(0, 0, self.drawing_area_size, 0) pattern.add_color_stop_rgba(0.0, red, green, blue, 1) pattern.add_color_stop_rgba(1.0, red, green, blue, 0) elif self.applet_orient == MatePanelApplet.AppletOrient.LEFT: pattern = cairo.LinearGradient(self.drawing_area_size, 0, 0, 0) pattern.add_color_stop_rgba(0.0, red, green, blue, 1) pattern.add_color_stop_rgba(1.0, red, green, blue, 0) elif self.applet_orient == MatePanelApplet.AppletOrient.DOWN: pattern = cairo.LinearGradient(0, 0, 0, self.drawing_area_size) pattern.add_color_stop_rgba(0.0, red, green, blue, 1) pattern.add_color_stop_rgba(1.0, red, green, blue, 0) else: pattern = cairo.LinearGradient(0, self.drawing_area_size, 0, 0) pattern.add_color_stop_rgba(0.0, red, green, blue, 1) pattern.add_color_stop_rgba(1.0, red, green, blue, 0) ctx.rectangle(0, 0, self.drawing_area_size, self.drawing_area_size) ctx.set_source(pattern) ctx.fill() # draw the app icon Gdk.cairo_set_source_pixbuf(ctx, self.app_pb, 3, 3) ctx.rectangle(0, 0, self.drawing_area_size, self.drawing_area_size) if self.is_pulsing: # draw the icon semi-transparently according to how far through the animation # we are half_way = CONST_PULSE_STEPS/2 if self.pulse_step <= half_way: alpha = 1.0 - (self.pulse_step / half_way) else: alpha = 0.0 + (self.pulse_step - half_way) / half_way ctx.paint_with_alpha(alpha) elif self.is_flashing: if self.flash_on: ctx.paint() # draw normally if in the flash on state else: ctx.paint() if self.has_mouse: # lighten the icon ctx.set_operator(cairo.OPERATOR_ADD) ctx.paint_with_alpha(0.2) ctx.set_operator(cairo.OPERATOR_OVER) # draw the app running indicators if (self.is_running()) and (self.indicator != dock_prefs.IndicatorType.NONE) : # work out how many indicators to draw - either a single one or # one for each open window up to a maximum of 4 if self.multi_ind == False: num_ind = 1 else: num_ind = self.get_num_windows() if num_ind > 4: num_ind = 4 if self.applet_orient == MatePanelApplet.AppletOrient.RIGHT: ind_x = 2 ind_y = (self.drawing_area_size-4)/(num_ind+1) + 2 elif self.applet_orient == MatePanelApplet.AppletOrient.LEFT: ind_x = self.drawing_area_size - 1 ind_y = (self.drawing_area_size - 4)/(num_ind+1) + 2 elif self.applet_orient == MatePanelApplet.AppletOrient.DOWN: ind_x = (self.drawing_area_size - 4)/(num_ind+1) + 2 ind_y = 2 else: ind_x = (self.drawing_area_size - 4)/(num_ind+1) + 2 ind_y = self.drawing_area_size - 1 this_ind = 1 while this_ind <= num_ind: rad_patt = cairo.RadialGradient(ind_x, ind_y, 2, ind_x, ind_y, 4) # do a light or dark indicator as necessary if self.indicator == dock_prefs.IndicatorType.LIGHT: rad_patt.add_color_stop_rgba(0, 0.9, 0.9, 0.9, 1) rad_patt.add_color_stop_rgba(1, 0.0, 0.0, 0.0, 0) else: rad_patt.add_color_stop_rgba(0, 0.0, 0.0, 0.0, 1) rad_patt.add_color_stop_rgba(1, 0.9, 0.9, 0.9, 0) ctx.set_source(rad_patt) ctx.arc(ind_x, ind_y, 6, 0, 2*math.pi) if num_ind > 1: if(self.applet_orient == MatePanelApplet.AppletOrient.RIGHT) or \ (self.applet_orient == MatePanelApplet.AppletOrient.LEFT): ind_y += (self.drawing_area_size - 6)/(num_ind+1) else: ind_x += (self.drawing_area_size - 6)/(num_ind+1) this_ind += 1 ctx.fill() # now draw to the screen screen_ctx = self.drawing_area.window.cairo_create() screen_ctx.rectangle(event.area.x, event.area.y, \ event.area.width, event.area.height) screen_ctx.clip() alloc = self.drawing_area.get_allocation() if (self.applet_orient == MatePanelApplet.AppletOrient.UP) or \ (self.applet_orient == MatePanelApplet.AppletOrient.DOWN): screen_ctx.set_source_surface(offscreen_surface, alloc.x, 0) else: screen_ctx.set_source_surface(offscreen_surface, 0, alloc.y) screen_ctx.paint() ctx = None screen_ctx = None def set_pixbuf(self, pixbuf): """Set the app pixbuf and calculate its average colour. """ self.app_pb = pixbuf rht, ght, bht = self.highlight_color = get_avg_color(pixbuf) self.highlight_color = ColorTup(r=rht, g=ght, b=bht) def start_app(self): """Start the app or open a new window if it's already running """ the_de = DesktopEntry.DesktopEntry(self.desktop_file) run_it = the_de.getExec() if run_it is not None: # hack for Linux Mint: # Mint has several shortcuts for starting caja so that it can # be started in a specific directory e.g. home, /, etc # However, the main caja.desktop is responsible for starting the # user's desktop and this is the .desktop file the applet finds first. # When the caja icon on the applet is clicked, caja is run as a desktop # window and no new file browser appears. # To get around this, we can simply check the command that is going to be # run and change it so that a caja window opens in the user's home directory, # which is the behaviour they'll probably be expecting.... if run_it == "/usr/bin/startcaja": run_it = "caja" # remove any command line arguments if "%" in run_it: i = run_it.rfind("%") run_it = run_it[0:i-1] # start the app self.run_cmd_line (run_it) def run_cmd_line(self, cmd_line): """Run a command line. To be used when starting an app for the first time or when running an action/shortcut specfied in the app's .desktop file Args: cmd_line - the command to run """ # the command line may contain escape sequences, so unescape them.... cmd_line =bytearray(cmd_line, "UTF-8") cmd_line = cmd_line.decode("unicode-escape") # if any of the directories in cmd_line contain a " ", they need to be escaped head,tail = os.path.split(cmd_line) if " " in head: head = head.replace(" ", "\ ") cmd_line = head + "/" + tail app_info = Gio.AppInfo.create_from_commandline(cmd_line, None, Gio.AppInfoCreateFlags.NONE) alc = Gdk.AppLaunchContext() alc.set_desktop(-1) # use default screen & desktop app_info.launch() # set the app icon pulsing throbber = PulseTimer(self) def run_rc_action(self, act_no): """ run the right click action specified by act_no Args: act_no - integer, the action number to run """ if len(self.rc_actions) >= act_no: self.run_cmd_line(self.rc_actions[act_no-1].command) def get_rc_action(self, act_no): """ return a specified right click action's details Args: act_no - integer, the specified action number Returns: bool - True if the action exists, False otherwise string - the name of the action (i.e. the text to appear in the right click menu) string - the command line to the be run """ if len(self.rc_actions) >= act_no: return (True, self.rc_actions[act_no-1].name, self.rc_actions[act_no-1].command) else: return (False, "", "") def start_pulsing(self): """ start the dock icon pulsing """ throbber = PulseTimer(self) def main(): """Main function. Debugging code can go here """ pass if __name__ == "__main__": main() dock-applet-0.70/src/dom_color.in000066400000000000000000000017351267226117400167550ustar00rootroot00000000000000#!/usr/bin/env python3 """ Calculate the dominant color of an image Code obtained from: https://stackoverflow.com/questions/34084142/python-3-most-common-color-in-image-kmeans-data-type-match """ try: import Image except ImportError: from PIL import Image import scipy.misc import scipy.cluster NUM_CLUSTERS = 5 def get_dom_color(filename): im = Image.open(filename) im = im.resize((150, 150)) # optional, to reduce time ar = scipy.misc.fromimage(im) shape = ar.shape ar = ar.reshape(scipy.product(shape[:2]), shape[2]) codes, dist = scipy.cluster.vq.kmeans(ar.astype(float), NUM_CLUSTERS) vecs, dist = scipy.cluster.vq.vq(ar, codes) # assign codes counts, bins = scipy.histogram(vecs, len(codes)) # count occurrences index_max = scipy.argmax(counts) # find most frequent peak = codes[index_max] peak = peak.astype(int) colour = ''.join(format(c, '02x') for c in peak) return colour dock-applet-0.70/src/log_it.in000066400000000000000000000011471267226117400162520ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path import time def log_it(thing, newfile = False): """Provide a quick and dirty logging facility Args: thing : the string to be written to the log file newfile : boolean - if True the log file is created, if False it is appended to """ filename = os.path.expanduser("~/tmp/log") if os.path.isdir(os.path.expanduser("~/tmp")): if newfile: thefile = open(filename, 'w') else: thefile = open(filename, 'a') thefile.write(time.strftime("%d %b %X: "+ thing + "\n")) thefile.close() dock-applet-0.70/src/org.mate.panel.DockApplet.mate-panel-applet.in000066400000000000000000000003601267226117400251350ustar00rootroot00000000000000[Applet Factory] Id=DockAppletFactory InProcess=false Location=@LOCATION@ Name=Dock Applet Factory Description=An application dock for the MATE panel [DockApplet] Name=Dock Description=An application dock for the MATE panel Icon=desktop dock-applet-0.70/src/org.mate.panel.applet.DockAppletFactory.service.in000066400000000000000000000001431267226117400261020ustar00rootroot00000000000000[D-BUS Service] Name=org.mate.panel.applet.DockAppletFactory Exec=/usr/bin/env python3 @LOCATION@ dock-applet-0.70/src/org.mate.panel.applet.dock.gschema.xml000066400000000000000000000056601267226117400236160ustar00rootroot00000000000000 [] the apps which have been pinned to the dock A string array containing the names of the apps which have been pinned to the dock. 0 The type of indicator (light, dark or none) The type of indicator (light or dark, or no indicator) which is displayed next to running apps. false Whether to display an indicator for each open window Whether to display an indicator for each open window (maximum 4) that an application has. true Whether to show unpinned apps from all workspaces Whether to show running unpinned apps from all workspaces true Whether to show pinned apps from all workspaces Whether to show pinned apps from all workspaces true Whether this is the first time the applet has been run Whether this is the first time this particular instance of the applet has been run true Specifies what to do when a running app's dock icon is click If set to true, the app's last running window is made active again. If false all of the app's windows are restored/unminimizes and the last active window is made active again false Specifies whether MATE panels are to change colour according to the desktop wallpaper If set to true, the colour of the MATE panel will change whenever the desktop wallpaper is changed, and will be set to the dominant colour of the wallpaper image false When changing MATE panel colours, specfies whether or not all panels are to changed If set to false, the colour of all MATE panels are changed. If set to true only the color of the panel containing the dock will be changed will be set to the dominant colour of the wallpaper image