pax_global_header 0000666 0000000 0000000 00000000064 12672261174 0014522 g ustar 00root root 0000000 0000000 52 comment=5cefe829dec46c0480214b92b0e60b931280cc20
dock-applet-0.70/ 0000775 0000000 0000000 00000000000 12672261174 0013653 5 ustar 00root root 0000000 0000000 dock-applet-0.70/.gitignore 0000664 0000000 0000000 00000000225 12672261174 0015642 0 ustar 00root root 0000000 0000000 .*.*~
*.*~
*~
*.py
*.swp
*.pyc
__pycache__
*.valid
autom4te.cache
configure
configure.ac
INSTALL
install.sh
Makefile
Makefile.in
missing
py-compile
dock-applet-0.70/AUTHORS 0000664 0000000 0000000 00000000017 12672261174 0014721 0 ustar 00root root 0000000 0000000 Robin Thompson
dock-applet-0.70/COPYING 0000664 0000000 0000000 00000104513 12672261174 0014712 0 ustar 00root root 0000000 0000000 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/ChangeLog 0000664 0000000 0000000 00000017440 12672261174 0015433 0 ustar 00root root 0000000 0000000 V0.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/INSTALL 0000664 0000000 0000000 00000001316 12672261174 0014705 0 ustar 00root root 0000000 0000000
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.am 0000664 0000000 0000000 00000000016 12672261174 0015704 0 ustar 00root root 0000000 0000000 SUBDIRS = src
dock-applet-0.70/NEWS 0000664 0000000 0000000 00000002514 12672261174 0014354 0 ustar 00root root 0000000 0000000 15 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/README 0000664 0000000 0000000 00000001326 12672261174 0014535 0 ustar 00root root 0000000 0000000 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
For installation instructions and screenshots, see README.md
dock-applet-0.70/README.md 0000664 0000000 0000000 00000004577 12672261174 0015147 0 ustar 00root root 0000000 0000000 # 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

Running on Ubuntu with a Windows 7 style layout

Running on a Raspberry Pi 2 with Ubuntu MATE

dock-applet-0.70/configure.ac 0000664 0000000 0000000 00000001430 12672261174 0016137 0 ustar 00root root 0000000 0000000 AC_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/ 0000775 0000000 0000000 00000000000 12672261174 0014442 5 ustar 00root root 0000000 0000000 dock-applet-0.70/src/Makefile.am 0000664 0000000 0000000 00000010662 12672261174 0016503 0 ustar 00root root 0000000 0000000
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.in 0000664 0000000 0000000 00000227231 12672261174 0015721 0 ustar 00root root 0000000 0000000 #!/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.in 0000775 0000000 0000000 00000007655 12672261174 0017124 0 ustar 00root root 0000000 0000000 #!/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.in 0000775 0000000 0000000 00000024637 12672261174 0017276 0 ustar 00root root 0000000 0000000 #!/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.in 0000775 0000000 0000000 00000030514 12672261174 0020605 0 ustar 00root root 0000000 0000000 #!/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.in 0000664 0000000 0000000 00000037700 12672261174 0021174 0 ustar 00root root 0000000 0000000 #!/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.in 0000664 0000000 0000000 00000050073 12672261174 0017116 0 ustar 00root root 0000000 0000000 #!/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.in 0000664 0000000 0000000 00000046063 12672261174 0017633 0 ustar 00root root 0000000 0000000 #!/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.in 0000664 0000000 0000000 00000017531 12672261174 0016601 0 ustar 00root root 0000000 0000000 #!/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.in 0000664 0000000 0000000 00000127163 12672261174 0017075 0 ustar 00root root 0000000 0000000 #!/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.in 0000664 0000000 0000000 00000001735 12672261174 0016755 0 ustar 00root root 0000000 0000000 #!/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.in 0000664 0000000 0000000 00000001147 12672261174 0016252 0 ustar 00root root 0000000 0000000 #!/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.in 0000664 0000000 0000000 00000000360 12672261174 0025135 0 ustar 00root root 0000000 0000000 [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.in 0000664 0000000 0000000 00000000143 12672261174 0026102 0 ustar 00root root 0000000 0000000 [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.xml 0000664 0000000 0000000 00000005660 12672261174 0023616 0 ustar 00root root 0000000 0000000
[]
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