From 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 Mon Sep 17 00:00:00 2001 From: AlexanderCurl Date: Sat, 18 Apr 2026 17:46:06 +0100 Subject: Replaced file structures for themes in the correct format --- .../dash-to-dock@micxgx.gmail.com/COPYING | 339 +++ .../dash-to-dock@micxgx.gmail.com/README.md | 53 + .../dash-to-dock@micxgx.gmail.com/Settings.ui | 3100 ++++++++++++++++++++ .../appIconIndicators.js | 1305 ++++++++ .../dash-to-dock@micxgx.gmail.com/appIcons.js | 1635 +++++++++++ .../appIconsDecorator.js | 168 ++ .../dash-to-dock@micxgx.gmail.com/appSpread.js | 235 ++ .../dash-to-dock@micxgx.gmail.com/dash.js | 1177 ++++++++ .../dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js | 280 ++ .../dependencies/gi.js | 14 + .../dependencies/shell/extensions/extension.js | 1 + .../dependencies/shell/misc.js | 5 + .../dependencies/shell/ui.js | 18 + .../desktopIconsIntegration.js | 170 ++ .../dash-to-dock@micxgx.gmail.com/docking.js | 2698 +++++++++++++++++ .../dash-to-dock@micxgx.gmail.com/extension.js | 18 + .../fileManager1API.js | 201 ++ .../dash-to-dock@micxgx.gmail.com/imports.js | 17 + .../dash-to-dock@micxgx.gmail.com/intellihide.js | 337 +++ .../dash-to-dock@micxgx.gmail.com/launcherAPI.js | 287 ++ .../locale/ar/LC_MESSAGES/dashtodock.mo | Bin 0 -> 16188 bytes .../locale/be/LC_MESSAGES/dashtodock.mo | Bin 0 -> 17759 bytes .../locale/bg/LC_MESSAGES/dashtodock.mo | Bin 0 -> 3761 bytes .../locale/cs/LC_MESSAGES/dashtodock.mo | Bin 0 -> 10315 bytes .../locale/de/LC_MESSAGES/dashtodock.mo | Bin 0 -> 14194 bytes .../locale/el/LC_MESSAGES/dashtodock.mo | Bin 0 -> 12042 bytes .../locale/es/LC_MESSAGES/dashtodock.mo | Bin 0 -> 12854 bytes .../locale/eu/LC_MESSAGES/dashtodock.mo | Bin 0 -> 10171 bytes .../locale/fr/LC_MESSAGES/dashtodock.mo | Bin 0 -> 14414 bytes .../locale/gl/LC_MESSAGES/dashtodock.mo | Bin 0 -> 8811 bytes .../locale/hu/LC_MESSAGES/dashtodock.mo | Bin 0 -> 14071 bytes .../locale/id/LC_MESSAGES/dashtodock.mo | Bin 0 -> 8549 bytes .../locale/it/LC_MESSAGES/dashtodock.mo | Bin 0 -> 11038 bytes .../locale/ja/LC_MESSAGES/dashtodock.mo | Bin 0 -> 12116 bytes .../locale/ka/LC_MESSAGES/dashtodock.mo | Bin 0 -> 21983 bytes .../locale/kk/LC_MESSAGES/dashtodock.mo | Bin 0 -> 18116 bytes .../locale/ko/LC_MESSAGES/dashtodock.mo | Bin 0 -> 14009 bytes .../locale/lv/LC_MESSAGES/dashtodock.mo | Bin 0 -> 13719 bytes .../locale/nb/LC_MESSAGES/dashtodock.mo | Bin 0 -> 9191 bytes .../locale/nl/LC_MESSAGES/dashtodock.mo | Bin 0 -> 10065 bytes .../locale/pl/LC_MESSAGES/dashtodock.mo | Bin 0 -> 10837 bytes .../locale/pt/LC_MESSAGES/dashtodock.mo | Bin 0 -> 9758 bytes .../locale/pt_BR/LC_MESSAGES/dashtodock.mo | Bin 0 -> 11998 bytes .../locale/ro/LC_MESSAGES/dashtodock.mo | Bin 0 -> 14377 bytes .../locale/ru/LC_MESSAGES/dashtodock.mo | Bin 0 -> 18114 bytes .../locale/sk/LC_MESSAGES/dashtodock.mo | Bin 0 -> 8918 bytes .../locale/sl/LC_MESSAGES/dashtodock.mo | Bin 0 -> 13120 bytes .../locale/sr/LC_MESSAGES/dashtodock.mo | Bin 0 -> 11174 bytes .../locale/sr@latin/LC_MESSAGES/dashtodock.mo | Bin 0 -> 8812 bytes .../locale/sv/LC_MESSAGES/dashtodock.mo | Bin 0 -> 13656 bytes .../locale/tr/LC_MESSAGES/dashtodock.mo | Bin 0 -> 14912 bytes .../locale/uk_UA/LC_MESSAGES/dashtodock.mo | Bin 0 -> 18074 bytes .../locale/zh_CN/LC_MESSAGES/dashtodock.mo | Bin 0 -> 12245 bytes .../locale/zh_TW/LC_MESSAGES/dashtodock.mo | Bin 0 -> 12073 bytes .../dash-to-dock@micxgx.gmail.com/locations.js | 1535 ++++++++++ .../locationsWorker.js | 91 + .../dash-to-dock@micxgx.gmail.com/media/glossy.svg | 139 + .../media/highlight_stacked_bg.svg | 82 + .../media/highlight_stacked_bg_h.svg | 82 + .../dash-to-dock@micxgx.gmail.com/media/logo.svg | 528 ++++ .../dash-to-dock@micxgx.gmail.com/metadata.json | 18 + .../notificationsMonitor.js | 121 + .../dash-to-dock@micxgx.gmail.com/prefs.js | 1168 ++++++++ .../schemas/gschemas.compiled | Bin 0 -> 8725 bytes ...gnome.shell.extensions.dash-to-dock.gschema.xml | 644 ++++ .../dash-to-dock@micxgx.gmail.com/stylesheet.css | 1478 ++++++++++ .../dash-to-dock@micxgx.gmail.com/theming.js | 572 ++++ .../dash-to-dock@micxgx.gmail.com/utils.js | 735 +++++ .../dash-to-dock@micxgx.gmail.com/windowPreview.js | 678 +++++ 69 files changed, 19929 insertions(+) create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/COPYING create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/README.md create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/Settings.ui create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIconIndicators.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIcons.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIconsDecorator.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appSpread.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dash.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/gi.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/extensions/extension.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/misc.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/ui.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/desktopIconsIntegration.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/docking.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/extension.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/fileManager1API.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/imports.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/intellihide.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/launcherAPI.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ar/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/be/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/bg/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/cs/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/de/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/el/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/es/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/eu/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/fr/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/gl/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/hu/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/id/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/it/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ja/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ka/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/kk/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ko/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/lv/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/nb/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/nl/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pl/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pt/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pt_BR/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ro/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ru/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sk/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sl/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sr/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sr@latin/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sv/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/tr/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/uk_UA/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/zh_CN/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/zh_TW/LC_MESSAGES/dashtodock.mo create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locations.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locationsWorker.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/glossy.svg create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/highlight_stacked_bg.svg create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/highlight_stacked_bg_h.svg create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/logo.svg create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/metadata.json create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/notificationsMonitor.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/prefs.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/schemas/gschemas.compiled create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/stylesheet.css create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/theming.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/utils.js create mode 100644 raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/windowPreview.js (limited to 'raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com') diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/COPYING b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 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 Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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. diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/README.md b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/README.md new file mode 100644 index 0000000..b0dc634 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/README.md @@ -0,0 +1,53 @@ +# Dash to Dock +![screenshot](https://github.com/micheleg/dash-to-dock/raw/master/media/screenshot.jpg) + +## A dock for the GNOME Shell +This extension enhances the dash moving it out of the overview and transforming it in a dock for an easier launching of applications and a faster switching between windows and desktops without having to leave the desktop view. + +[](https://extensions.gnome.org/extension/307/dash-to-dock) + +For additional installation instructions and more information visit [https://micheleg.github.io/dash-to-dock/](https://micheleg.github.io/dash-to-dock/). + +## Installation from source + +The extension can be installed directly from source, either for the convenience of using git or to test the latest development version. Clone the desired branch with git + +### Build Dependencies + +To compile the stylesheet you'll need an implementation of SASS. Dash to Dock supports `dart-sass` (`sass`), `sassc`, and `ruby-sass`. Every distro should have at least one of these implementations, we recommend using `dart-sass` (`sass`) or `sassc` over `ruby-sass` as `ruby-sass` is deprecated. + +By default, Dash to Dock will attempt to build with `sassc`. To change this behavior set the `SASS` environment variable to either `dart` or `ruby`. + +```bash +export SASS=dart +# or... +export SASS=ruby +``` + +### Building + +Clone the repository or download the branch from github. A simple Makefile is included. + +Next use `make` to install the extension into your home directory. A Shell reload is required Alt + F2 r Enter under Xorg or under Wayland you may have to logout and login. The extension has to be enabled with *gnome-extensions-app* (GNOME Extensions) or with *dconf*. + +```bash +git clone https://github.com/micheleg/dash-to-dock.git +make -C dash-to-dock install +``` + +If `msgfmt` is not available on your system, you will see an error message like the following: + +```bash +make: msgfmt: No such file or directory +``` + +In this case install the `gettext` package from your distribution's repository. + + +## Bug Reporting + +Bugs should be reported to the Github bug tracker [https://github.com/micheleg/dash-to-dock/issues](https://github.com/micheleg/dash-to-dock/issues). + +## License +Dash to Dock Gnome Shell extension is distributed under the terms of the GNU General Public License, +version 2 or later. See the COPYING file for details. diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/Settings.ui b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/Settings.ui new file mode 100644 index 0000000..b16ca59 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/Settings.ui @@ -0,0 +1,3100 @@ + + + + + 1 + 0.050000000000000003 + 0.25 + + + 0 + 12 + 12 + 12 + 12 + vertical + + + 0 + + + 0 + 1 + 1 + none + + + 100 + 80 + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + When set to minimize, double clicking minimizes all the windows of the application. + 1 + 0 + 40 + + + + 0 + 1 + + + + + + 0 + 1 + Shift+Click action + 0 + + + 0 + 0 + + + + + + 0 + center + + Raise window + Minimize window + Launch new instance + Cycle through windows + Minimize or overview + Show window previews + Minimize or show previews + Focus or show previews + Focus or app spread + Focus, minimize or show previews + Focus, minimize or app spread + Quit + + + 1 + 0 + 2 + + + + + + + + + + 100 + 80 + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + Behavior for Middle-Click. + 1 + 0 + 40 + + + + 0 + 1 + + + + + + 0 + 1 + Middle-Click action + 0 + + + 0 + 0 + + + + + + 0 + center + + Raise window + Minimize window + Launch new instance + Cycle through windows + Minimize or overview + Show window previews + Minimize or show previews + Focus or show previews + Focus or app spread + Focus, minimize or show previews + Focus, minimize or app spread + Quit + + + 1 + 0 + 2 + + + + + + + + + + 100 + 80 + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + Behavior for Shift+Middle-Click. + 1 + 0 + 40 + + + + 0 + 1 + + + + + + 0 + 1 + Shift+Middle-Click action + 0 + + + 0 + 0 + + + + + + 0 + center + + Raise window + Minimize window + Launch new instance + Cycle through windows + Minimize or overview + Show window previews + Minimize or show previews + Focus or show previews + Focus or app spread + Focus, minimize or show previews + Focus, minimize or app spread + Quit + + + 1 + 0 + 2 + + + + + + + + + + + + + + + + + 1 + 0.01 + 0.10 + + + 0.33 + 1 + 0.01 + 0.10 + + + 10 + 1 + 5 + + + 1 + 1 + 0 + 12 + 12 + 12 + 12 + vertical + + + 0 + + + + 0 + none + + + 100 + 80 + + + 0 + 12 + 12 + 12 + 12 + vertical + 12 + + + 0 + 32 + + + 1 + 0 + 0 + center + Enable Unity7 like glossy backlit items + + + + + + center + + + + + + + Apply glossy effect. + + + + + 0 + + + 1 + 0 + 0 + start + Use dominant color + + + + + + + + + + 0 + 32 + + + + 1 + 0 + + + + + + 0 + 1 + 0 + Customize indicator style + fill + + + 0 + 0 + + + + + + + + 0 + 1 + vertical + 12 + + + 0 + 32 + + + 1 + 0 + 0 + Color + + + + + + 1 + + + + + + + 0 + 32 + + + 1 + 0 + 0 + Border color + + + + + + 1 + + + + + + + 0 + 32 + + + 1 + 0 + 0 + Border width + + + + + + dot_border_width_adjustment + + + + + + + + + + + + + + + + + + + + 1 + 0.050000000000000003 + 0.25 + + + 16 + 128 + 1 + 10 + + + 0.0 + 1 + 0.01 + 0.1 + + + 6 + 6 + 6 + 6 + + + + + 0 + 24 + 24 + 24 + 24 + vertical + 24 + + + 0 + + + + 0 + none + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + start + Show the dock on + + 0 + 0 + + + + + + 0 + 400 + center + + + 1 + 0 + + + + + + Show on all monitors + 12 + + + 0 + 2 + 2 + + + + + + + + + + + + + + + + 100 + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + 0 + Position on screen + + + + + + 0 + 32 + + + Left + end + center + + + + + + + Bottom + center + + position_left_button + + + + + + Top + center + + + position_left_button + + + + + + Right + center + + position_left_button + + + + + + + + + + + + + + + + + + + 0 + + + + 0 + none + + + 100 + 80 + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + start + Hide the dock when it obstructs a window of the current application. More refined settings are available. + 1 + 0 + + + 0 + 1 + + + + + + 0 + 1 + start + Intelligent autohide + + + 0 + 0 + + + + + + 0 + 6 + + + 1 + center + center + + + + 0 + emblem-system-symbolic + + + + + + + + end + center + + + + 1 + 0 + 2 + + + + + + + + + + + + + + + + + 0 + + + + 0 + none + + + 100 + 80 + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + Dock size limit + + + 0 + 0 + + + + + + 1 + baseline + 1 + dock_size_adjustment + 0 + 2 + right + + + + + + Panel mode: extend to the screen edge + 12 + + + 0 + 1 + 2 + + + + + + Place icons to the center + + 0 + 2 + 2 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + Icon size limit + + + 0 + 0 + + + + + + 1 + baseline + 1 + icon_size_adjustment + 1 + 0 + right + + + 1 + 0 + + + + + + Fixed icon size: scroll to reveal other icons + 12 + + + 0 + 1 + 2 + + + + + + + + + + True + + + False + 12 + 12 + 12 + 12 + 32 + + + False + Preview size scale + 0 + + 0 + 0 + + + + + + 1 + 1 + preview_size_adjustment + 0 + 0 + 0 + 2 + right + + + + + + 0 + 1 + + + + + + + + + + + + + + + + + 0 + Position and size + + + + + + + 1 + + + 0 + 24 + 24 + 24 + 24 + vertical + 24 + + + 0 + + + + 0 + none + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + + + + + + 0 + 1 + start + Show pinned applications + + + 0 + 0 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + + + + + + 0 + 1 + start + Show running applications + + + 0 + 0 + + + + + + Isolate workspaces + start + 12 + + + 0 + 2 + 2 + + + + + + Show urgent windows despite current workspace + start + 12 + + + 0 + 3 + 2 + + + + + + Isolate monitors + start + 12 + + + 0 + 4 + 2 + + + + + + 3 + Show open windows' previews + + 0 + 1 + 2 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + + + + + + 0 + 1 + start + Keep the focused application always visible in the dash + + + 0 + 0 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + 2 + + + + + + 0 + 1 + If disabled, these settings are accessible from gnome-tweak-tool or the extension website. + 1 + 0 + + + 0 + 1 + + + + + + 0 + 1 + start + Show <i>Applications</i> icon + 1 + + + 0 + 0 + + + + + + Move at beginning of the dock + start + 12 + + + 0 + 2 + 2 + + + + + + + 3 + + + 0 + start + Animate <i>Show Applications</i> + 1 + + + + 0 + 3 + 2 + + + + + + + 3 + + + 0 + start + Put <i>Show Applications</i> in a dock edge when using Panel mode + 1 + + + + 0 + 4 + 2 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + + + + + + 0 + 1 + start + Show trash can + + + 0 + 0 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + + + + + + 0 + 1 + start + Show volumes and devices + + + 0 + 0 + + + + + + Only if mounted + start + 12 + + + 0 + 1 + 1 + + + + + + Include network volumes + start + 12 + + + 0 + 2 + 2 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + + + + + + 0 + 1 + start + Isolate volumes, devices and trash windows from file manager + + + 0 + 0 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + + + + + + 0 + 1 + start + Wiggle urgent applications + + + 0 + 0 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + + + + + + 0 + 1 + start + Hide application tooltip + + + 0 + 0 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + + + + + + 0 + 1 + start + Show icons emblems + + + 0 + 0 + + + + + + + 0 + 1 + When enabled application icons will show notification counters and progress-bars (if Unity API is used). + 1 + 0 + + + 0 + 1 + + + + + + + Show the number of unread notifications + start + 12 + + + 0 + 2 + 2 + + + + + + + 3 + + + 0 + start + Application-provided counter overrides the notifications counter + + + + 0 + 3 + 2 + + + + + + + + + + + + + + + + + + + + 0 + Launchers + + + + + + + 2 + + + 0 + 24 + 24 + 24 + 24 + vertical + 24 + + + 0 + + + + 0 + none + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + start + 1 + Enable Super+(0-9) as shortcuts to activate apps. It can also be used together with Shift and Ctrl. + 1 + 1 + 0 + + + + 0 + 1 + + + + + + 0 + 1 + start + Use keyboard shortcuts to activate apps + + + 0 + 0 + + + + + + 0 + 6 + + + 1 + center + center + + + + 0 + emblem-system-symbolic + + + + + + + + end + center + + + + 1 + 0 + 2 + + + + + + + + + + + + + + + + + 0 + + + + 0 + none + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + start + Behaviour when clicking on the icon of a running application. + 1 + 0 + + + + 0 + 1 + + + + + + 0 + 1 + start + Click action + 0 + + + 0 + 0 + + + + + + 0 + 6 + + + 1 + center + + + 0 + emblem-system-symbolic + + + + + + + + 0 + center + + Raise window + Minimize + Launch new instance + Cycle through windows + Minimize or overview + Show window previews + Minimize or show previews + Focus or show previews + Focus or app spread + Focus, minimize or show previews + Focus, minimize or app spread + + + + + 1 + 0 + 2 + + + + + + + + + + + + + + + + + 0 + + + + 0 + none + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + start + Behaviour when scrolling on the icon of an application. + 1 + 0 + + + + 0 + 1 + + + + + + 0 + 1 + start + Scroll action + + + 0 + 0 + + + + + + 0 + 6 + + + 0 + center + + Do nothing + Cycle through windows + Switch workspace + + + + + 1 + 0 + 2 + + + + + + + + + + + + + + + + + + + 0 + start + Behavior + + + + + + + 3 + + + 0 + 24 + 24 + 24 + 24 + vertical + 24 + + + 0 + + + + none + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + 2 + + + + + + 0 + 1 + start + Save space reducing padding and border radius. + + + + 0 + 1 + + + + + + 0 + 1 + start + Shrink the dash + + + 0 + 0 + + + + + + + + + + 100 + 80 + + + 0 + 32 + 12 + 12 + 12 + 12 + + + 1 + 0 + center + start + Force straight corner + + + + + + center + 3 + + + + + + + 100 + 80 + + + 0 + 32 + 12 + 12 + 12 + 12 + + + 1 + 0 + center + start + Show overview on startup + + + + + center + 3 + + + + + + + + + + + + + + + + 0 + + + + vertical + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + start + Few customizations meant to integrate the dock with the default GNOME theme. Alternatively, specific options can be enabled below. + 1 + 0 + + + + 0 + 1 + + + + + + 0 + 1 + start + Use built-in theme + + + 0 + 0 + + + + + + end + center + + 1 + 0 + 2 + + + + + + + + + + 0 + none + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + start + Customize windows counter indicators + + + 0 + 0 + + + + + + 0 + 6 + + + 1 + center + center + + + + 0 + emblem-system-symbolic + + + + + + + + 0 + + Default + Dots + Squares + Dashes + Segmented + Solid + Ciliora + Metro + Binary + Dot + + + + + 1 + 0 + 2 + + + + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + start + Set the background color for the dash. + 1 + 0 + + + + 0 + 1 + + + + + + 0 + 1 + start + Customize the dash color + + + 0 + 0 + + + + + + 0 + 6 + + + 1 + center + center + + + + + + end + center + + + + 1 + 0 + 2 + + + + + + + + + + + + 0 + vertical + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + start + Tune the dash background opacity. + + + + 0 + 1 + + + + + + 0 + 1 + start + Customize opacity + + + 0 + 0 + + + + + + 0 + 6 + + + 1 + center + center + + + 0 + emblem-system-symbolic + + + + + + + + 0 + center + + Default + Fixed + Dynamic + + + + + 1 + 0 + 2 + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + Opacity + + + + + 1 + 1 + custom_opacity_adjustement + + 0 + 0 + 0 + 2 + right + + + + + + + + + + + + + + + + + + + + + + + 0 + Appearance + + + + + + + 4 + + + 0 + 0 + 24 + 24 + 1 + 1 + vertical + 5 + + + + + + 0 + <b>Dash to Dock</b> + 1 + + + + + 0 + center + + + 0 + end + version: + + + + + 0 + start + ... + + + + + + + 0 + Moves the dash out of the overview transforming it in a dock + center + 1 + 0 + + + + + 0 + center + 5 + + + 0 + Created by + + + + + Michele (<a href="mailto:micxgx@gmail.com">micxgx@gmail.com</a>) + 1 + + + + + + + Webpage + 1 + center + + https://micheleg.github.io/dash-to-dock/ + + + + + 1 + end + <span size="small">This program comes with ABSOLUTELY NO WARRANTY. +See the <a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.html">GNU General Public License, version 2 or later</a> for details.</span> + 1 + center + 1 + 0 + + + + + + + 0 + About + + + + + + + 1 + 0.01 + 0.10000000000000001 + + + 1 + 0.01 + 0.10000000000000001 + + + 1 + 1 + 0 + 12 + 12 + 12 + 12 + vertical + + + 0 + + + + 0 + none + + + 100 + 80 + + + 0 + 12 + 12 + 12 + 12 + vertical + 12 + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + Customize minimum and maximum opacity values + fill + 0 + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + Minimum opacity + + + + + 1 + 1 + min_opacity_adjustement + + 0 + 0 + 0 + 2 + right + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + Maximum opacity + + + + + 1 + 1 + max_opacity_adjustement + + 0 + 0 + 0 + 2 + right + + + + + + + + + + + + + + + + + + + 100 + 5 + 25 + + + 10 + 0.25 + 1 + + + 1 + 1 + 12 + 12 + 12 + 12 + vertical + + + + + none + + + 100 + 80 + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + 2 + + + + + + 0 + 1 + Number overlay + + + 0 + 0 + + + + + + 0 + Temporarily show the application numbers over the icons, corresponding to the shortcut. + 1 + 0 + 40 + + + + 0 + 1 + + + + + + + + + + 100 + 80 + + + 0 + 12 + 12 + 12 + 12 + 32 + + + end + center + + 1 + 0 + 2 + + + + + + 0 + 1 + start + Show the dock if it is hidden + + + 0 + 0 + + + + + + 0 + If using autohide, the dock will appear for a short time when triggering the shortcut. + 1 + 0 + 40 + + + + 0 + 1 + + + + + + + + + + 100 + 80 + + + 12 + 12 + 12 + 12 + 32 + + + center + 12 + + 1 + 0 + + + + + + 0 + 1 + Shortcut for the options above + + + 0 + 0 + + + + + + 0 + Syntax: <Shift>, <Ctrl>, <Alt>, <Super> + 1 + 0 + 40 + + + + 0 + 1 + + + + + + + + + + + + + + + 12 + 12 + 12 + 12 + 1 + 6 + 32 + + + end + shortcut_time_adjustment + 3 + + 1 + 0 + + + + + + 0 + 1 + Hide timeout (s) + + + 0 + 0 + + + + + + + + + + + + + + + + + 1 + 0.050000000000000003 + 0.25 + + + 1 + 1 + 0 + 12 + 12 + 12 + 12 + vertical + + + 0 + + + + 0 + none + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + start + Show the dock by mouse hover on the screen edge. + 1 + 0 + + + + 0 + 1 + + + + + + 0 + 1 + Autohide + + + 0 + 0 + + + + + + end + center + + 1 + 0 + 2 + + + + + + Push to show: require pressure to show the dock + + + 0 + 3 + 2 + + + + + + Enable in fullscreen mode + 12 + + + 0 + 2 + 2 + + + + + + Show dock for urgent notifications + 12 + + + 0 + 4 + 2 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 32 + + + 0 + 1 + start + Show the dock when it doesn't obstruct application windows. + 1 + 0 + + + + 0 + 1 + + + + + + 0 + 1 + Dodge windows + + + 0 + 0 + + + + + + end + center + + 1 + 0 + 2 + + + + + + 0 + vertical + + + All windows + 12 + + + + + + + Only focused application's windows + all_windows_radio_button + + + + + + Only maximized windows + all_windows_radio_button + + + + + + Always on top + all_windows_radio_button + + + + + 0 + 2 + 2 + + + + + + + + + + + + 0 + 12 + 12 + 12 + 12 + 1 + 6 + 32 + + + end + animation_time_adjustment + 3 + + 1 + 0 + + + + + + 0 + 1 + Animation duration (s) + + + 0 + 0 + + + + + + end + hide_timeout_adjustment + 3 + + 1 + 1 + + + + + + end + show_timeout_adjustment + 3 + + 1 + 2 + + + + + + 0.000 + pressure_threshold_adjustment + + 1 + 3 + + + + + + 0 + 1 + Hide timeout (s) + + + 0 + 1 + + + + + + 0 + 1 + Show timeout (s) + + + 0 + 2 + + + + + + 0 + 1 + Pressure threshold + + + 0 + 3 + + + + + + + + + + + + + + + + diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIconIndicators.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIconIndicators.js new file mode 100644 index 0000000..d981c0f --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIconIndicators.js @@ -0,0 +1,1305 @@ +import { + Clutter, + Cogl, + GdkPixbuf, + Gio, + GObject, + Pango, + St, +} from './dependencies/gi.js'; + +import {Main} from './dependencies/shell/ui.js'; + +import { + Docking, + Utils, +} from './imports.js'; + +const {cairo: Cairo} = imports; + +const RunningIndicatorStyle = Object.freeze({ + DEFAULT: 0, + DOTS: 1, + SQUARES: 2, + DASHES: 3, + SEGMENTED: 4, + SOLID: 5, + CILIORA: 6, + METRO: 7, + BINARY: 8, + DOT: 9, +}); + +const MAX_WINDOWS_CLASSES = 4; + + +/* + * This is the main indicator class to be used. The desired behavior is + * obtained by composing the desired classes below based on the settings. + * + */ +export class AppIconIndicator { + constructor(source) { + this._indicators = []; + + // Choose the style for the running indicators + let runningIndicator = null; + let runningIndicatorStyle; + + const {settings} = Docking.DockManager; + if (settings.applyCustomTheme) + runningIndicatorStyle = RunningIndicatorStyle.DOTS; + else + ({runningIndicatorStyle} = settings); + + if (settings.showIconsEmblems && + !Docking.DockManager.getDefault().notificationsMonitor.dndMode) { + const unityIndicator = new UnityIndicator(source); + this._indicators.push(unityIndicator); + } + + switch (runningIndicatorStyle) { + case RunningIndicatorStyle.DEFAULT: + runningIndicator = new RunningIndicatorDefault(source); + break; + + case RunningIndicatorStyle.DOTS: + runningIndicator = new RunningIndicatorDots(source); + break; + + case RunningIndicatorStyle.SQUARES: + runningIndicator = new RunningIndicatorSquares(source); + break; + + case RunningIndicatorStyle.DASHES: + runningIndicator = new RunningIndicatorDashes(source); + break; + + case RunningIndicatorStyle.SEGMENTED: + runningIndicator = new RunningIndicatorSegmented(source); + break; + + case RunningIndicatorStyle.SOLID: + runningIndicator = new RunningIndicatorSolid(source); + break; + + case RunningIndicatorStyle.CILIORA: + runningIndicator = new RunningIndicatorCiliora(source); + break; + + case RunningIndicatorStyle.METRO: + runningIndicator = new RunningIndicatorMetro(source); + break; + + case RunningIndicatorStyle.BINARY: + runningIndicator = new RunningIndicatorBinary(source); + break; + + case RunningIndicatorStyle.DOT: + runningIndicator = new RunningIndicatorDot(source); + break; + + default: + runningIndicator = new RunningIndicatorBase(source); + } + + this._indicators.push(runningIndicator); + } + + update() { + for (let i = 0; i < this._indicators.length; i++) { + const indicator = this._indicators[i]; + indicator.update(); + } + } + + destroy() { + for (let i = 0; i < this._indicators.length; i++) { + const indicator = this._indicators[i]; + indicator.destroy(); + } + } +} + +/* + * Base class to be inherited by all indicators of any kind +*/ +class IndicatorBase { + constructor(source) { + this._source = source; + this._signalsHandler = new Utils.GlobalSignalsHandler(this._source); + } + + update() { + } + + destroy() { + this._source = null; + this._signalsHandler.destroy(); + this._signalsHandler = null; + } +} + +/* + * A base indicator class for running style, from which all other RunningIndicators should derive, + * providing some basic methods, variables definitions and their update, css style classes handling. + * + */ +class RunningIndicatorBase extends IndicatorBase { + constructor(source) { + super(source); + + this._side = Utils.getPosition(); + this._dominantColorExtractor = new DominantColorExtractor(this._source.app); + this._signalsHandler.add(this._source, 'notify::running', () => this.update()); + this._signalsHandler.add(this._source, 'notify::focused', () => this.update()); + this._signalsHandler.add(this._source, 'notify::windows-count', () => this._updateCounterClass()); + this.update(); + } + + get _number() { + return Math.min(this._source.windowsCount, MAX_WINDOWS_CLASSES); + } + + update() { + this._updateCounterClass(); + this._updateDefaultDot(); + } + + _updateCounterClass() { + for (let i = 1; i <= MAX_WINDOWS_CLASSES; i++) { + const className = `running${i}`; + if (i !== this._number) + this._source.remove_style_class_name(className); + else + this._source.add_style_class_name(className); + } + } + + _updateDefaultDot() { + if (this._source.running) + this._source._dot.show(); + else + this._source._dot.hide(); + } + + _hideDefaultDot() { + // I use opacity to hide the default dot because the show/hide function + // are used by the parent class. + this._source._dot.opacity = 0; + } + + _restoreDefaultDot() { + this._source._dot.opacity = 255; + } + + _enableBacklight() { + const colorPalette = this._dominantColorExtractor._getColorPalette(); + + // Fallback + if (!colorPalette) { + this._source._iconContainer.set_style( + 'border-radius: 5px;' + + 'background-gradient-direction: vertical;' + + 'background-gradient-start: #e0e0e0;' + + 'background-gradient-end: darkgray;' + ); + + return; + } + + this._source._iconContainer.set_style( + `${'border-radius: 5px;' + + 'background-gradient-direction: vertical;' + + 'background-gradient-start: '}${colorPalette.original};` + + `background-gradient-end: ${colorPalette.darker};` + ); + } + + _disableBacklight() { + this._source._iconContainer.set_style(null); + } + + destroy() { + this._disableBacklight(); + // Remove glossy background if the children still exists + if (this._source._iconContainer.get_children().length > 1) + this._source._iconContainer.get_children()[1].set_style(null); + this._restoreDefaultDot(); + + super.destroy(); + } +} + +// We add a css class so third parties themes can limit their indicator customization +// to the case we do nothing +class RunningIndicatorDefault extends RunningIndicatorBase { + constructor(source) { + super(source); + this._source.add_style_class_name('default'); + } + + destroy() { + this._source.remove_style_class_name('default'); + super.destroy(); + } +} + +const IndicatorDrawingArea = GObject.registerClass( +class IndicatorDrawingArea extends St.DrawingArea { + vfunc_allocate(box) { + if (box.x1 !== 0 || box.y1 !== 0) + return super.vfunc_allocate(box); + + // We assume that the are is a rectangle in the operations below: + const size = Math.min(box.get_width(), box.get_height()); + box.x2 = size; + box.y2 = size; + this.set_allocation(box); + + return super.vfunc_allocate(box); + } +}); + +class RunningIndicatorDots extends RunningIndicatorBase { + constructor(source) { + super(source); + + this._hideDefaultDot(); + + this._area = new IndicatorDrawingArea({ + x_expand: true, + y_expand: true, + }); + + // We draw for the bottom case and rotate the canvas for other placements + // set center of rotations to the center + this._area.set_pivot_point(0.5, 0.5); + + switch (this._side) { + case St.Side.TOP: + this._area.rotation_angle_z = 180; + break; + + case St.Side.BOTTOM: + // nothing + break; + + case St.Side.LEFT: + this._area.rotation_angle_z = 90; + break; + + case St.Side.RIGHT: + this._area.rotation_angle_z = -90; + break; + } + + this._area.connectObject('repaint', this._updateIndicator.bind(this), this); + this._source._iconContainer.add_child(this._area); + + const keys = ['custom-theme-running-dots-color', + 'custom-theme-running-dots-border-color', + 'custom-theme-running-dots-border-width', + 'custom-theme-customize-running-dots', + 'unity-backlit-items', + 'apply-glossy-effect', + 'running-indicator-dominant-color']; + + keys.forEach(function (key) { + this._signalsHandler.add( + Docking.DockManager.settings, + `changed::${key}`, + this.update.bind(this) + ); + }, this); + + // Apply glossy background + // TODO: move to enable/disableBacklit to apply it only to the running apps? + // TODO: move to css class for theming support + const {extension} = Docking.DockManager; + this._glossyBackgroundStyle = `background-image: url('${extension.path}/media/glossy.svg');` + + 'background-size: contain;'; + } + + update() { + super.update(); + + // Enable / Disable the backlight of running apps + if (!Docking.DockManager.settings.applyCustomTheme && + Docking.DockManager.settings.unityBacklitItems) { + const [icon] = this._source._iconContainer.get_children(); + icon.set_style( + Docking.DockManager.settings.applyGlossyEffect + ? this._glossyBackgroundStyle : null); + if (this._source.running) + this._enableBacklight(); + else + this._disableBacklight(); + } else { + this._disableBacklight(); + this._source._iconContainer.get_children()[1].set_style(null); + } + + if (this._area) + this._area.queue_repaint(); + } + + _computeStyle() { + const [width, height] = this._area.get_surface_size(); + this._width = height; + this._height = width; + + // By default re-use the style - background color, and border width and color - + // of the default dot + const themeNode = this._source._dot.get_theme_node(); + this._borderColor = themeNode.get_border_color(this._side); + this._borderWidth = themeNode.get_border_width(this._side); + this._bodyColor = themeNode.get_background_color(); + + const {settings} = Docking.DockManager; + if (!settings.applyCustomTheme) { + // Adjust for the backlit case + const Color = Clutter.Color ?? Cogl.Color; + + if (settings.unityBacklitItems) { + // Use dominant color for dots too if the backlit is enables + const colorPalette = this._dominantColorExtractor._getColorPalette(); + + // Slightly adjust the styling + this._borderWidth = 2; + + if (colorPalette) { + [, this._borderColor] = Color.from_string(colorPalette.lighter); + [, this._bodyColor] = Color.from_string(colorPalette.darker); + } else { + // Fallback + [, this._borderColor] = Color.from_string('white'); + [, this._bodyColor] = Color.from_string('gray'); + } + } + + // Apply dominant color if requested + if (settings.runningIndicatorDominantColor) { + const colorPalette = this._dominantColorExtractor._getColorPalette(); + if (colorPalette) + [, this._bodyColor] = Color.from_string(colorPalette.original); + else + // Fallback + [, this._bodyColor] = Color.from_string(settings.customThemeRunningDotsColor); + } + + // Finally, use customize style if requested + if (settings.customThemeCustomizeRunningDots) { + [, this._borderColor] = Color.from_string(settings.customThemeRunningDotsBorderColor); + this._borderWidth = settings.customThemeRunningDotsBorderWidth; + [, this._bodyColor] = Color.from_string(settings.customThemeRunningDotsColor); + } + } + + // Define the radius as an arbitrary size, but keep large enough to account + // for the drawing of the border. + this._radius = Math.max(this._width / 22, this._borderWidth / 2); + this._padding = 0; // distance from the margin + this._spacing = this._radius + this._borderWidth; // separation between the dots + } + + _updateIndicator() { + const cr = this._area.get_context(); + + this._computeStyle(); + this._drawIndicator(cr); + cr.$dispose(); + } + + _drawIndicator(cr) { + // Draw the required numbers of dots + const n = this._number; + + cr.setLineWidth(this._borderWidth); + Utils.cairoSetSourceColor(cr, this._borderColor); + + // draw for the bottom case: + cr.translate( + (this._width - (2 * n) * this._radius - (n - 1) * this._spacing) / 2, + this._height - this._padding); + + for (let i = 0; i < n; i++) { + cr.newSubPath(); + cr.arc((2 * i + 1) * this._radius + i * this._spacing, + -this._radius - this._borderWidth / 2, + this._radius, 0, 2 * Math.PI); + } + + cr.strokePreserve(); + Utils.cairoSetSourceColor(cr, this._bodyColor); + cr.fill(); + } + + destroy() { + this._area.destroy(); + super.destroy(); + } +} + +// Adapted from dash-to-panel by Jason DeRose +// https://github.com/jderose9/dash-to-panel +class RunningIndicatorCiliora extends RunningIndicatorDots { + _drawIndicator(cr) { + if (this._source.running) { + const size = Math.max(this._width / 20, this._borderWidth); + const spacing = size; // separation between the dots + const lineLength = this._width - (size * (this._number - 1)) - (spacing * (this._number - 1)); + let padding = this._borderWidth; + // For the backlit case here we don't want the outer border visible + if (Docking.DockManager.settings.unityBacklitItems && + !Docking.DockManager.settings.customThemeCustomizeRunningDots) + padding = 0; + const yOffset = this._height - padding - size; + + cr.setLineWidth(this._borderWidth); + Utils.cairoSetSourceColor(cr, this._borderColor); + + cr.translate(0, yOffset); + cr.newSubPath(); + cr.rectangle(0, 0, lineLength, size); + for (let i = 1; i < this._number; i++) { + cr.newSubPath(); + cr.rectangle(lineLength + (i * spacing) + ((i - 1) * size), 0, size, size); + } + + cr.strokePreserve(); + Utils.cairoSetSourceColor(cr, this._bodyColor); + cr.fill(); + } + } +} + +// Adapted from dash-to-panel by Jason DeRose +// https://github.com/jderose9/dash-to-panel +class RunningIndicatorSegmented extends RunningIndicatorDots { + _drawIndicator(cr) { + if (this._source.running) { + const size = Math.max(this._width / 20, this._borderWidth); + const spacing = Math.ceil(this._width / 18); // separation between the dots + const dashLength = Math.ceil((this._width - ((this._number - 1) * spacing)) / this._number); + let padding = this._borderWidth; + // For the backlit case here we don't want the outer border visible + if (Docking.DockManager.settings.unityBacklitItems && + !Docking.DockManager.settings.customThemeCustomizeRunningDots) + padding = 0; + const yOffset = this._height - padding - size; + + cr.setLineWidth(this._borderWidth); + Utils.cairoSetSourceColor(cr, this._borderColor); + + cr.translate(0, yOffset); + for (let i = 0; i < this._number; i++) { + cr.newSubPath(); + cr.rectangle(i * dashLength + i * spacing, 0, dashLength, size); + } + + cr.strokePreserve(); + Utils.cairoSetSourceColor(cr, this._bodyColor); + cr.fill(); + } + } +} + +// Adapted from dash-to-panel by Jason DeRose +// https://github.com/jderose9/dash-to-panel +class RunningIndicatorSolid extends RunningIndicatorDots { + _drawIndicator(cr) { + if (this._source.running) { + const size = Math.max(this._width / 20, this._borderWidth); + let padding = this._borderWidth; + // For the backlit case here we don't want the outer border visible + if (Docking.DockManager.settings.unityBacklitItems && + !Docking.DockManager.settings.customThemeCustomizeRunningDots) + padding = 0; + const yOffset = this._height - padding - size; + + cr.setLineWidth(this._borderWidth); + Utils.cairoSetSourceColor(cr, this._borderColor); + + cr.translate(0, yOffset); + cr.newSubPath(); + cr.rectangle(0, 0, this._width, size); + + cr.strokePreserve(); + Utils.cairoSetSourceColor(cr, this._bodyColor); + cr.fill(); + } + } +} + +// Adapted from dash-to-panel by Jason DeRose +// https://github.com/jderose9/dash-to-panel +class RunningIndicatorSquares extends RunningIndicatorDots { + _drawIndicator(cr) { + if (this._source.running) { + const size = Math.max(this._width / 11, this._borderWidth); + const padding = this._borderWidth; + const spacing = Math.ceil(this._width / 18); // separation between the dots + const yOffset = this._height - padding - size; + + cr.setLineWidth(this._borderWidth); + Utils.cairoSetSourceColor(cr, this._borderColor); + + cr.translate( + Math.floor((this._width - this._number * size - (this._number - 1) * spacing) / 2), + yOffset); + + for (let i = 0; i < this._number; i++) { + cr.newSubPath(); + cr.rectangle(i * size + i * spacing, 0, size, size); + } + cr.strokePreserve(); + Utils.cairoSetSourceColor(cr, this._bodyColor); + cr.fill(); + } + } +} + +// Adapted from dash-to-panel by Jason DeRose +// https://github.com/jderose9/dash-to-panel +class RunningIndicatorDashes extends RunningIndicatorDots { + _drawIndicator(cr) { + if (this._source.running) { + const size = Math.max(this._width / 20, this._borderWidth); + const padding = this._borderWidth; + const spacing = Math.ceil(this._width / 18); // separation between the dots + const dashLength = Math.floor(this._width / 4) - spacing; + const yOffset = this._height - padding - size; + + cr.setLineWidth(this._borderWidth); + Utils.cairoSetSourceColor(cr, this._borderColor); + + cr.translate( + Math.floor((this._width - this._number * dashLength - (this._number - 1) * spacing) / 2), + yOffset); + + for (let i = 0; i < this._number; i++) { + cr.newSubPath(); + cr.rectangle(i * dashLength + i * spacing, 0, dashLength, size); + } + + cr.strokePreserve(); + Utils.cairoSetSourceColor(cr, this._bodyColor); + cr.fill(); + } + } +} + +// Adapted from dash-to-panel by Jason DeRose +// https://github.com/jderose9/dash-to-panel +class RunningIndicatorMetro extends RunningIndicatorDots { + constructor(source) { + super(source); + this._source.add_style_class_name('metro'); + } + + destroy() { + this._source.remove_style_class_name('metro'); + super.destroy(); + } + + _drawIndicator(cr) { + if (this._source.running) { + const size = Math.max(this._width / 20, this._borderWidth); + let padding = 0; + // For the backlit case here we don't want the outer border visible + if (Docking.DockManager.settings.unityBacklitItems && + !Docking.DockManager.settings.customThemeCustomizeRunningDots) + padding = 0; + const yOffset = this._height - padding - size; + + const n = this._number; + if (n <= 1) { + cr.translate(0, yOffset); + Utils.cairoSetSourceColor(cr, this._bodyColor); + cr.newSubPath(); + cr.rectangle(0, 0, this._width, size); + cr.fill(); + } else { + // need to scale with the SVG for the stacked highlight + const blackenedLength = (1 / 48) * this._width; + const darkenedLength = this._source.focused + ? (2 / 48) * this._width : (10 / 48) * this._width; + const blackenedColor = this._bodyColor.shade(.3); + const darkenedColor = this._bodyColor.shade(.7); + + cr.translate(0, yOffset); + + Utils.cairoSetSourceColor(cr, this._bodyColor); + cr.newSubPath(); + cr.rectangle(0, 0, this._width - darkenedLength - blackenedLength, size); + cr.fill(); + Utils.cairoSetSourceColor(cr, blackenedColor); + cr.newSubPath(); + cr.rectangle(this._width - darkenedLength - blackenedLength, 0, 1, size); + cr.fill(); + Utils.cairoSetSourceColor(cr, darkenedColor); + cr.newSubPath(); + cr.rectangle(this._width - darkenedLength, 0, darkenedLength, size); + cr.fill(); + } + } + } +} + +class RunningIndicatorBinary extends RunningIndicatorDots { + _drawIndicator(cr) { + // Draw the required numbers of dots + const n = Math.min(15, this._source.windowsCount); + + if (this._source.running) { + const size = Math.max(this._width / 11, this._borderWidth); + const spacing = Math.ceil(this._width / 18); + const yOffset = this._height - size; + const binaryValue = String(`0000${(n >>> 0).toString(2)}`).slice(-4); + + cr.setLineWidth(this._borderWidth); + Utils.cairoSetSourceColor(cr, this._borderColor); + + cr.translate(Math.floor((this._width - 4 * size - (4 - 1) * spacing) / 2), yOffset); + for (let i = 0; i < binaryValue.length; i++) { + if (binaryValue[i] === '1') { + cr.newSubPath(); + cr.arc((2 * i + 1) * this._radius + i * spacing, + -this._radius - this._borderWidth / 2, + this._radius, 0, 2 * Math.PI); + } else { + cr.newSubPath(); + cr.rectangle(i * size + i * spacing, + -this._radius - this._borderWidth / 2 - size / 5, + size, size / 3); + } + } + cr.strokePreserve(); + Utils.cairoSetSourceColor(cr, this._bodyColor); + cr.fill(); + } + } +} + +class RunningIndicatorDot extends RunningIndicatorDots { + _computeStyle() { + super._computeStyle(); + + this._radius = Math.max(this._width / 26, this._borderWidth / 2); + } + + _drawIndicator(cr) { + if (!this._source.running) + return; + + cr.setLineWidth(this._borderWidth); + Utils.cairoSetSourceColor(cr, this._borderColor); + + // draw from the bottom case: + cr.translate( + (this._width - 2 * this._radius) / 2, + this._height - this._padding); + cr.newSubPath(); + cr.arc(this._radius, + -this._radius - this._borderWidth / 2, + this._radius, 0, 2 * Math.PI); + + cr.strokePreserve(); + Utils.cairoSetSourceColor(cr, this._bodyColor); + cr.fill(); + } +} + +/* + * Unity like notification and progress indicators + */ +export class UnityIndicator extends IndicatorBase { + static defaultProgressBar = { + // default values for the progress bar itself + background: { + colorStart: {red: 204, green: 204, blue: 204, alpha: 255}, + colorEnd: null, + }, + border: { + colorStart: {red: 230, green: 230, blue: 230, alpha: 255}, + colorEnd: null, + }, + }; + + static defaultProgressBarTrack = { + // default values for the progress bar track + background: { + colorStart: {red: 64, green: 64, blue: 64, alpha: 255}, + colorEnd: {red: 89, green: 89, blue: 89, alpha: 255}, + offsetStart: 0.4, + offsetEnd: 0.9, + }, + border: { + colorStart: {red: 128, green: 128, blue: 128, alpha: 26}, + colorEnd: {red: 204, green: 204, blue: 204, alpha: 102}, + offsetStart: 0.5, + offsetEnd: 0.9, + }, + }; + + static notificationBadgeSignals = Symbol('notification-badge-signals'); + + constructor(source) { + super(source); + + const {remoteModel, notificationsMonitor} = Docking.DockManager.getDefault(); + const remoteEntry = remoteModel.lookupById(this._source.app.id); + this._remoteEntry = remoteEntry; + + this._signalsHandler.add([ + remoteEntry, + ['count-changed', 'count-visible-changed'], + () => this._updateNotificationsCount(), + ], [ + remoteEntry, + ['progress-changed', 'progress-visible-changed'], + (sender, {progress, progress_visible: progressVisible}) => + this.setProgress(progressVisible ? progress : -1), + ], [ + remoteEntry, + 'urgent-changed', + (sender, {urgent}) => this.setUrgent(urgent), + ], [ + remoteEntry, + 'updating-changed', + (sender, {updating}) => this.setUpdating(updating), + ], [ + notificationsMonitor, + 'changed', + () => this._updateNotificationsCount(), + ], [ + this._source, + 'style-changed', + () => this._updateIconStyle(), + ]); + + this._updateNotificationsCount(); + this.setProgress(this._remoteEntry.progress_visible + ? this._remoteEntry.progress : -1); + this.setUrgent(this._remoteEntry.urgent); + this.setUpdating(this._remoteEntry.updating); + } + + destroy() { + this._notificationBadgeBin?.destroy(); + this._notificationBadgeBin = null; + this._hideProgressOverlay(); + this.setUrgent(false); + this.setUpdating(false); + this._remoteEntry = null; + + super.destroy(); + } + + _updateNotificationBadgeStyle() { + const themeContext = St.ThemeContext.get_for_stage(global.stage); + const fontDesc = themeContext.get_font(); + const defaultFontSize = fontDesc.get_size() / 1024; + let fontSize = defaultFontSize * 0.9; + const {iconSize} = Main.overview.dash; + const defaultIconSize = Docking.DockManager.settings.get_default_value( + 'dash-max-icon-size').unpack(); + + if (!fontDesc.get_size_is_absolute()) { + // fontSize was expressed in points, so convert to pixel + fontSize /= 0.75; + } + + let sizeMultiplier; + if (iconSize < defaultIconSize) { + sizeMultiplier = Math.max(24, Math.min(iconSize + + iconSize * 0.3, defaultIconSize)) / defaultIconSize; + } else { + sizeMultiplier = iconSize / defaultIconSize; + } + + fontSize = Math.round(sizeMultiplier * fontSize); + const leftMargin = Math.round(sizeMultiplier * 3); + + this._notificationBadgeBin.child.set_style( + `font-size: ${fontSize}px;` + + `margin-left: ${leftMargin}px` + ); + } + + _notificationBadgeCountToText(count) { + if (count <= 9999) { + return count.toString(); + } else if (count < 1e5) { + const thousands = count / 1e3; + return `${thousands.toFixed(1).toString()}k`; + } else if (count < 1e6) { + const thousands = count / 1e3; + return `${thousands.toFixed(0).toString()}k`; + } else if (count < 1e8) { + const millions = count / 1e6; + return `${millions.toFixed(1).toString()}M`; + } else if (count < 1e9) { + const millions = count / 1e6; + return `${millions.toFixed(0).toString()}M`; + } else { + const billions = count / 1e9; + return `${billions.toFixed(1).toString()}B`; + } + } + + _updateNotificationsCount() { + const remoteCount = this._remoteEntry['count-visible'] + ? this._remoteEntry.count ?? 0 : 0; + + if (remoteCount > 0 && + Docking.DockManager.settings.applicationCounterOverridesNotifications) { + this.setNotificationCount(remoteCount); + return; + } + + const {notificationsMonitor} = Docking.DockManager.getDefault(); + const notificationsCount = notificationsMonitor.getAppNotificationsCount( + this._source.app.id); + + this.setNotificationCount(remoteCount + notificationsCount); + } + + _updateNotificationsBadge(text) { + if (this._notificationBadgeBin) { + this._notificationBadgeBin.child.text = text; + return; + } + + this._notificationBadgeBin = new St.Bin({ + child: new St.Label({ + styleClass: 'notification-badge', + text, + }), + xAlign: Clutter.ActorAlign.END, + yAlign: Clutter.ActorAlign.START, + xExpand: true, + yExpand: true, + }); + this._notificationBadgeBin.child.clutterText.ellipsize = + Pango.EllipsizeMode.MIDDLE; + + this._source._iconContainer.add_child(this._notificationBadgeBin); + this._updateNotificationBadgeStyle(); + + const themeContext = St.ThemeContext.get_for_stage(global.stage); + this._signalsHandler.addWithLabel(UnityIndicator.notificationBadgeSignals, [ + themeContext, + 'changed', + () => this._updateNotificationBadgeStyle(), + ], [ + themeContext, + 'notify::scale-factor', + () => this._updateNotificationBadgeStyle(), + ], [ + this._source._iconContainer, + 'notify::size', + () => this._updateNotificationBadgeStyle(), + ]); + } + + setNotificationCount(count) { + if (count > 0) { + const text = this._notificationBadgeCountToText(count); + this._updateNotificationsBadge(text); + } else if (this._notificationBadgeBin) { + this._signalsHandler.removeWithLabel(UnityIndicator.notificationBadgeSignals); + this._notificationBadgeBin.destroy(); + this._notificationBadgeBin = null; + } + } + + _showProgressOverlay() { + if (this._progressOverlayArea) { + this._updateProgressOverlay(); + return; + } + + this._progressOverlayArea = new St.DrawingArea({x_expand: true, y_expand: true}); + this._progressOverlayArea.add_style_class_name('progress-bar'); + this._progressOverlayArea.connect('repaint', () => { + this._drawProgressOverlay(this._progressOverlayArea); + }); + + this._source._iconContainer.add_child(this._progressOverlayArea); + this._updateProgressOverlay(); + } + + _hideProgressOverlay() { + this._progressOverlayArea?.destroy(); + this._progressOverlayArea = null; + } + + _updateProgressOverlay() { + this._progressOverlayArea?.queue_repaint(); + } + + _readGradientData(node, elementName, defaultValues) { + const output = { + colorStart: defaultValues.colorStart, + colorEnd: defaultValues.colorEnd, + offsetStart: defaultValues.offsetStart ?? 0.0, + offsetEnd: defaultValues.offsetEnd ?? 1.0, + }; + + const [hasElementName, elementNameValue] = node.lookup_color(elementName, false); + if (hasElementName) { + output.colorStart = elementNameValue; + output.colorEnd = null; + } else { + const [hasColorStart, colorStartValue] = node.lookup_color(`${elementName}-color-start`, false); + const [hasColorEnd, colorEndValue] = node.lookup_color(`${elementName}-color-end`, false); + if (hasColorStart && hasColorEnd) { + output.colorStart = colorStartValue; + output.colorEnd = colorEndValue; + } + } + + const [hasOffsetStart, offsetStartValue] = node.lookup_color(`${elementName}-offset-start`, false); + if (hasOffsetStart) + output.offsetStart = offsetStartValue; + + const [hasOffsetEnd, offsetEndValue] = node.lookup_color(`${elementName}-offset-end`, false); + if (hasOffsetEnd) + output.offsetEnd = offsetEndValue; + + return output; + } + + _readThemeDoubleValue(node, elementName, defaultValue) { + const [hasValue, value] = node.lookup_double(elementName, false); + return hasValue ? value : defaultValue; + } + + _readElementData(node, elementName, defaultValues) { + return { + background: this._readGradientData(node, `${elementName}-background`, defaultValues.background), + border: this._readGradientData(node, `${elementName}-border`, defaultValues.border), + lineWidth: this._readThemeDoubleValue(node, `${elementName}-line-width`, + defaultValues.lineWidth ?? 1.0), + }; + } + + _createGradient(values, x0, y0, x1, y1) { + if (values.colorEnd) { + const gradient = new Cairo.LinearGradient(x0, y0, x1, y1); + gradient.addColorStopRGBA(values.offsetStart, + values.colorStart.red / 255, + values.colorStart.green / 255, + values.colorStart.blue / 255, + values.colorStart.alpha / 255); + gradient.addColorStopRGBA(values.offsetEnd, + values.colorEnd.red / 255, + values.colorEnd.green / 255, + values.colorEnd.blue / 255, + values.colorEnd.alpha / 255); + return gradient; + } else { + const gradient = Cairo.SolidPattern.createRGBA(values.colorStart.red / 255, + values.colorStart.green / 255, + values.colorStart.blue / 255, + values.colorStart.alpha / 255); + return gradient; + } + } + + _drawProgressOverlay(area) { + const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage); + const [surfaceWidth, surfaceHeight] = area.get_surface_size(); + const cr = area.get_context(); + const node = this._progressOverlayArea.get_theme_node(); + const iconSize = this._source.icon.iconSize * scaleFactor; + + let x = Math.floor((surfaceWidth - iconSize) / 2); + let y = Math.floor((surfaceHeight - iconSize) / 2); + + const readThemeValue = element => + this._readThemeDoubleValue(node, `-progress-bar-${element}`); + + y = readThemeValue('top-offset') ?? y; + + const baseLineWidth = Math.floor(Number(scaleFactor)); + const horizontalPadding = iconSize * + Utils.clampDouble(readThemeValue('horizontal-padding') ?? 0.05); + const verticalPadding = iconSize * + Utils.clampDouble(readThemeValue('vertical-padding') ?? 0.05); + const heightFactor = + Utils.clampDouble(readThemeValue('height-factor') ?? 0.20); + + let width = iconSize - 2.0 * horizontalPadding; + let height = Math.floor(Math.min(18.0 * scaleFactor, heightFactor * iconSize)); + x += horizontalPadding; + + const valign = Utils.clampDouble(readThemeValue('valign') ?? 1); + y += (iconSize - height - verticalPadding) * valign; + + const progressBarTrack = this._readElementData(node, + '-progress-bar-track', + UnityIndicator.defaultProgressBarTrack); + + const progressBar = this._readElementData(node, + '-progress-bar', + UnityIndicator.defaultProgressBar); + + // Draw the track + let lineWidth = baseLineWidth * progressBarTrack.lineWidth; + cr.setLineWidth(lineWidth); + + x += lineWidth; + y += lineWidth; + width -= 2.0 * lineWidth; + height -= 2.0 * lineWidth; + + let fill = this._createGradient(progressBarTrack.background, 0, y, 0, y + height); + let stroke = this._createGradient(progressBarTrack.border, 0, y, 0, y + height); + Utils.drawRoundedLine(cr, x + lineWidth / 2.0, + y + lineWidth / 2.0, width, height, true, true, stroke, fill); + + // Draw the finished bar + lineWidth = baseLineWidth * progressBar.lineWidth; + cr.setLineWidth(lineWidth); + + x += lineWidth; + y += lineWidth; + width -= 2.0 * lineWidth; + height -= 2.0 * lineWidth; + + const finishedWidth = Math.ceil(this._progress * width); + fill = this._createGradient(progressBar.background, 0, y, 0, y + height); + stroke = this._createGradient(progressBar.border, 0, y, 0, y + height); + + if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL) { + Utils.drawRoundedLine(cr, + x + lineWidth / 2.0 + width - finishedWidth, y + lineWidth / 2.0, + finishedWidth, height, true, true, stroke, fill); + } else { + Utils.drawRoundedLine(cr, x + lineWidth / 2.0, y + lineWidth / 2.0, + finishedWidth, height, true, true, stroke, fill); + } + + cr.$dispose(); + } + + setProgress(progress) { + if (progress < 0) { + this._hideProgressOverlay(); + } else { + this._progress = Math.min(progress, 1.0); + this._showProgressOverlay(); + } + } + + setUrgent(urgent) { + if (urgent || this._isUrgent !== undefined) + this._source.urgent = urgent; + + if (urgent) + this._isUrgent = urgent; + else + delete this._isUrgent; + } + + setUpdating(updating) { + this._source.updating = updating; + } + + _updateIconStyle() { + const opacity = this._readThemeDoubleValue(this._source.get_theme_node(), + 'opacity') ?? (this._source.updating ? 0.5 : 1); + this._source.icon.set_opacity(255 * opacity); + } +} + + +// Global icon cache. Used for Unity7 styling. +const iconCacheMap = new Map(); +// Max number of items to store +// We don't expect to ever reach this number, but let's put an hard limit to avoid +// even the remote possibility of the cached items to grow indefinitely. +const MAX_CACHED_ITEMS = 1000; +// When the size exceed it, the oldest 'n' ones are deleted +const BATCH_SIZE_TO_DELETE = 50; +// The icon size used to extract the dominant color +const DOMINANT_COLOR_ICON_SIZE = 64; + +// Compute dominant color from the app icon. +// The color is cached for efficiency. +class DominantColorExtractor { + constructor(app) { + this._app = app; + } + + /** + * Try to get the pixel buffer for the current icon, if not fail gracefully + */ + _getIconPixBuf() { + let iconTexture = this._app.create_icon_texture(16); + const themeLoader = Docking.DockManager.iconTheme; + + // Unable to load the icon texture, use fallback + if (iconTexture instanceof St.Icon === false) + return null; + + + iconTexture = iconTexture.get_gicon(); + + // Unable to load the icon texture, use fallback + if (!iconTexture) + return null; + + if (iconTexture instanceof Gio.FileIcon) { + // Use GdkPixBuf to load the pixel buffer from the provided file path + return GdkPixbuf.Pixbuf.new_from_file(iconTexture.get_file().get_path()); + } else if (iconTexture instanceof Gio.ThemedIcon) { + // Get the first pixel buffer available in the icon theme + const iconNames = iconTexture.get_names(); + const iconInfo = themeLoader.choose_icon(iconNames, DOMINANT_COLOR_ICON_SIZE, 0); + + if (iconInfo) + return iconInfo.load_icon(); + else + return null; + } + + // Use GdkPixBuf to load the pixel buffer from memory + // iconTexture.load is available unless iconTexture is not an instance of Gio.LoadableIcon + // this means that iconTexture is an instance of Gio.EmblemedIcon, + // which may be converted to a normal icon via iconTexture.get_icon? + const [iconBuffer] = iconTexture.load(DOMINANT_COLOR_ICON_SIZE, null); + return GdkPixbuf.Pixbuf.new_from_stream(iconBuffer, null); + } + + /** + * The backlight color choosing algorithm was mostly ported to javascript from the + * Unity7 C++ source of Canonicals: + * https://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/launcher/LauncherIcon.cpp + * so it more or less works the same way. + */ + _getColorPalette() { + if (iconCacheMap.get(this._app.get_id())) { + // We already know the answer + return iconCacheMap.get(this._app.get_id()); + } + + const pixBuf = this._getIconPixBuf(); + if (!pixBuf) + return null; + + let pixels = pixBuf.get_pixels(); + + let total = 0, + rTotal = 0, + gTotal = 0, + bTotal = 0; + + let resampleX = 1; + let resampleY = 1; + + // Resampling of large icons + // We resample icons larger than twice the desired size, as the resampling + // to a size s + // DOMINANT_COLOR_ICON_SIZE < s < 2*DOMINANT_COLOR_ICON_SIZE, + // most of the case exactly DOMINANT_COLOR_ICON_SIZE as the icon size is + // typically a multiple of it. + const width = pixBuf.get_width(); + const height = pixBuf.get_height(); + + // Resample + if (height >= 2 * DOMINANT_COLOR_ICON_SIZE) + resampleY = Math.floor(height / DOMINANT_COLOR_ICON_SIZE); + + if (width >= 2 * DOMINANT_COLOR_ICON_SIZE) + resampleX = Math.floor(width / DOMINANT_COLOR_ICON_SIZE); + + if (resampleX !== 1 || resampleY !== 1) + pixels = this._resamplePixels(pixels, resampleX, resampleY); + + // computing the limit outside the for (where it would be repeated at each iteration) + // for performance reasons + const limit = pixels.length; + for (let offset = 0; offset < limit; offset += 4) { + const r = pixels[offset], + g = pixels[offset + 1], + b = pixels[offset + 2], + a = pixels[offset + 3]; + + const saturation = Math.max(r, g, b) - Math.min(r, g, b); + const relevance = 0.1 * 255 * 255 + 0.9 * a * saturation; + + rTotal += r * relevance; + gTotal += g * relevance; + bTotal += b * relevance; + + total += relevance; + } + + total *= 255; + + const r = rTotal / total, + g = gTotal / total, + b = bTotal / total; + + const hsv = Utils.ColorUtils.RGBtoHSV(r * 255, g * 255, b * 255); + + if (hsv.s > 0.15) + hsv.s = 0.65; + hsv.v = 0.90; + + const rgb = Utils.ColorUtils.HSVtoRGB(hsv.h, hsv.s, hsv.v); + + // Cache the result. + const backgroundColor = { + lighter: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, 0.2), + original: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, 0), + darker: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, -0.5), + }; + + if (iconCacheMap.size >= MAX_CACHED_ITEMS) { + // delete oldest cached values (which are in order of insertions) + let ctr = 0; + for (const key of iconCacheMap.keys()) { + if (++ctr > BATCH_SIZE_TO_DELETE) + break; + iconCacheMap.delete(key); + } + } + + iconCacheMap.set(this._app.get_id(), backgroundColor); + + return backgroundColor; + } + + /** + * Downscale large icons before scanning for the backlight color to + * improve performance. + * + * @param pixBuf + * @param pixels + * @param resampleX + * @param resampleY + * + * @returns []; + */ + _resamplePixels(pixels, resampleX, resampleY) { + const resampledPixels = []; + // computing the limit outside the for (where it would be repeated at each iteration) + // for performance reasons + const limit = pixels.length / (resampleX * resampleY) / 4; + for (let i = 0; i < limit; i++) { + const pixel = i * resampleX * resampleY; + + resampledPixels.push(pixels[pixel * 4]); + resampledPixels.push(pixels[pixel * 4 + 1]); + resampledPixels.push(pixels[pixel * 4 + 2]); + resampledPixels.push(pixels[pixel * 4 + 3]); + } + + return resampledPixels; + } +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIcons.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIcons.js new file mode 100644 index 0000000..6b0ac24 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIcons.js @@ -0,0 +1,1635 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import { + Clutter, + Gio, + GLib, + GObject, + Meta, + Mtk, + Shell, + St, +} from './dependencies/gi.js'; + +import { + AppDisplay, + AppFavorites, + BoxPointer, + Dash, + Main, + PopupMenu, +} from './dependencies/shell/ui.js'; + +import { + ParentalControlsManager, + Util, +} from './dependencies/shell/misc.js'; + +import { + AppIconIndicators, + DBusMenuUtils, + Docking, + Locations, + Theming, + Utils, + WindowPreview, +} from './imports.js'; + +import {Extension} from './dependencies/shell/extensions/extension.js'; + +// Use __ () and N__() for the extension gettext domain, and reuse +// the shell domain with the default _() and N_() +const {gettext: __, ngettext} = Extension; + +const DBusMenu = await DBusMenuUtils.haveDBusMenu(); + +const tracker = Shell.WindowTracker.get_default(); + +const Labels = Object.freeze({ + ISOLATE_MONITORS: Symbol('isolate-monitors'), + ISOLATE_WORKSPACES: Symbol('isolate-workspaces'), + URGENT_WINDOWS: Symbol('urgent-windows'), +}); + +const clickAction = Object.freeze({ + SKIP: 0, + MINIMIZE: 1, + LAUNCH: 2, + CYCLE_WINDOWS: 3, + MINIMIZE_OR_OVERVIEW: 4, + PREVIEWS: 5, + MINIMIZE_OR_PREVIEWS: 6, + FOCUS_OR_PREVIEWS: 7, + FOCUS_OR_APP_SPREAD: 8, + FOCUS_MINIMIZE_OR_PREVIEWS: 9, + FOCUS_MINIMIZE_OR_APP_SPREAD: 10, + QUIT: 11, +}); + +const scrollAction = Object.freeze({ + DO_NOTHING: 0, + CYCLE_WINDOWS: 1, + SWITCH_WORKSPACE: 2, +}); + +// module "Dash" did not export DASH_ITEM_LABEL_SHOW_TIME, so let's define it. +const DASH_ITEM_LABEL_SHOW_TIME = Dash.DASH_ITEM_LABEL_SHOW_TIME ?? 150; + +let recentlyClickedAppLoopId = 0; +let recentlyClickedApp = null; +let recentlyClickedAppWindows = null; +let recentlyClickedAppIndex = 0; +let recentlyClickedAppMonitor = -1; + +/** + * Extend AppIcon + * + * - Apply a css class based on the number of windows of each application (#N); + * - Customized indicators for running applications in place of the default "dot" style which is hidden (#N); + * a class of the form "running#N" is applied to the AppWellIcon actor. + * like the original .running one. + * - Add a .focused style to the focused app + * - Customize click actions. + * - Update minimization animation target + * - Update menu if open on windows change + */ +const DockAbstractAppIcon = GObject.registerClass({ + GTypeFlags: GObject.TypeFlags.ABSTRACT, + Properties: { + 'focused': GObject.ParamSpec.boolean( + 'focused', 'focused', 'focused', + GObject.ParamFlags.READWRITE, + false), + 'running': GObject.ParamSpec.boolean( + 'running', 'running', 'running', + GObject.ParamFlags.READWRITE, + false), + 'urgent': GObject.ParamSpec.boolean( + 'urgent', 'urgent', 'urgent', + GObject.ParamFlags.READWRITE, + false), + 'updating': GObject.ParamSpec.boolean( + 'updating', 'updating', 'updating', + GObject.ParamFlags.READWRITE, + false), + 'windows-count': GObject.ParamSpec.uint( + 'windows-count', 'windows-count', 'windows-count', + GObject.ParamFlags.READWRITE, + 0, GLib.MAXUINT32, 0), + }, +}, class DockAbstractAppIcon extends Dash.DashIcon { + // settings are required inside. + _init(app, monitorIndex, iconAnimator) { + super._init(app); + + // a prefix is required to avoid conflicting with the parent class variable + this.monitorIndex = monitorIndex; + this._signalsHandler = new Utils.GlobalSignalsHandler(this); + this.iconAnimator = iconAnimator; + this._indicator = new AppIconIndicators.AppIconIndicator(this); + this._urgentWindows = new Set(); + + // Monitor windows-changes instead of app state. + // Keep using the same Id and function callback (that is extended) + if (this._stateChangedId > 0) { + this.app.disconnect(this._stateChangedId); + this._stateChangedId = 0; + } + + this._signalsHandler.add(this.app, 'windows-changed', () => this._updateWindows()); + this._signalsHandler.add(this.app, 'notify::state', () => this._updateRunningState()); + this._signalsHandler.add(global.display, 'window-demands-attention', (_dpy, window) => + this._onWindowDemandsAttention(window)); + this._signalsHandler.add(global.display, 'window-marked-urgent', (_dpy, window) => + this._onWindowDemandsAttention(window)); + + // In Wayland sessions, this signal is needed to track the state of windows dragged + // from one monitor to another. As this is triggered quite often (whenever a new + // window of any application opened or moved to a different desktop), + // we restrict this signal to the case when Labels.ISOLATE_MONITORS is true, + // and if there are at least 2 monitors. + if (Docking.DockManager.settings.isolateMonitors && + Main.layoutManager.monitors.length > 1) { + this._signalsHandler.addWithLabel(Labels.ISOLATE_MONITORS, + global.display, + 'window-entered-monitor', + this._onWindowEntered.bind(this)); + } + + this.connect('notify::running', () => { + if (this.running) + this.add_style_class_name('running'); + else + this.remove_style_class_name('running'); + }); + this.notify('running'); + + this.connect('notify::focused', () => { + if (this.focused) + this.add_style_class_name('focused'); + else + this.remove_style_class_name('focused'); + }); + this.notify('focused'); + + this.connect('notify::updating', () => { + if (this.updating) + this.add_style_class_name('updating'); + else + this.remove_style_class_name('updating'); + }); + this.notify('updating'); + + const {notificationsMonitor} = Docking.DockManager.getDefault(); + + this.connect('notify::urgent', () => { + const icon = this.icon._iconBin; + this._signalsHandler.removeWithLabel(Labels.URGENT_WINDOWS); + if (this.urgent) { + if (Docking.DockManager.settings.danceUrgentApplications && + notificationsMonitor.enabled) { + icon.set_pivot_point(0.5, 0.5); + this.iconAnimator.addAnimation(icon, 'wiggle'); + } + if (this.running && !this._urgentWindows.size) { + const urgentWindows = this.getInterestingWindows(); + urgentWindows.forEach(w => (w._manualUrgency = true)); + this._updateUrgentWindows(urgentWindows); + } + } else { + this.iconAnimator.removeAnimation(icon, 'wiggle'); + icon.rotation_angle_z = 0; + this._urgentWindows.forEach(w => delete w._manualUrgency); + this._updateUrgentWindows(); + } + }); + this.notify('urgent'); + + this._progressOverlayArea = null; + this._progress = 0; + + [ + 'apply-custom-theme', + 'running-indicator-style', + 'show-icons-emblems', + 'show-icons-notifications-counter', + 'application-counter-overrides-notifications', + ].forEach(key => { + this._signalsHandler.add( + Docking.DockManager.settings, + `changed::${key}`, () => { + this._indicator.destroy(); + this._indicator = new AppIconIndicators.AppIconIndicator(this); + } + ); + }); + + this._signalsHandler.add(notificationsMonitor, 'state-changed', () => { + this._indicator.destroy(); + this._indicator = new AppIconIndicators.AppIconIndicator(this); + }); + + this._updateState(); + this._numberOverlay(); + + this._previewMenuManager = null; + this._previewMenu = null; + } + + _onDestroy() { + super._onDestroy(); + + // This is necessary due to an upstream bug + // https://bugzilla.gnome.org/show_bug.cgi?id=757556 + // It can be safely removed once it get solved upstream. + this._menu?.close(false); + delete this._menu; + } + + ownsWindow(window) { + return this.app === tracker.get_window_app(window); + } + + _onWindowEntered(metaScreen, monitorIndex, metaWin) { + if (this.ownsWindow(metaWin)) + this._updateWindows(); + } + + vfunc_scroll_event(scrollEvent) { + const {settings} = Docking.DockManager; + const isEnabled = settings.scrollAction === scrollAction.CYCLE_WINDOWS; + if (!isEnabled) + return Clutter.EVENT_PROPAGATE; + + // We only activate windows of running applications, i.e. we never open new windows + // We check if the app is running, and that the # of windows is > 0 in + // case we use workspace isolation, + if (!this.running) + return Clutter.EVENT_PROPAGATE; + + if (this._optionalScrollCycleWindowsDeadTimeId) { + return Clutter.EVENT_PROPAGATE; + } else { + this._optionalScrollCycleWindowsDeadTimeId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, 250, () => { + this._optionalScrollCycleWindowsDeadTimeId = 0; + }); + } + + let direction = null; + + switch (scrollEvent.direction) { + case Clutter.ScrollDirection.UP: + direction = Meta.MotionDirection.UP; + break; + case Clutter.ScrollDirection.DOWN: + direction = Meta.MotionDirection.DOWN; + break; + case Clutter.ScrollDirection.SMOOTH: { + const [, dy] = Clutter.get_current_event().get_scroll_delta(); + if (dy < 0) + direction = Meta.MotionDirection.UP; + else if (dy > 0) + direction = Meta.MotionDirection.DOWN; + } + break; + } + + if (!Main.overview.visible) { + const reversed = direction === Meta.MotionDirection.UP; + if (this.focused && !this._urgentWindows.size) { + this._cycleThroughWindows(reversed); + } else { + // Activate the first window + const windows = this.getInterestingWindows(); + if (windows.length > 0) { + const [w] = windows; + Main.activateWindow(w); + } + } + } else { + this.app.activate(); + } + return Clutter.EVENT_STOP; + } + + _updateWindows() { + if (this._menu && this._menu.isOpen) + this._menu.update(); + + this._updateState(); + this.updateIconGeometry(); + } + + _updateState() { + this._urgentWindows.clear(); + const interestingWindows = this.getInterestingWindows(); + this.windowsCount = interestingWindows.length; + this._updateRunningState(); + this._updateFocusState(); + this._updateUrgentWindows(interestingWindows); + + if (Docking.DockManager.settings.isolateWorkspaces) { + this._signalsHandler.removeWithLabel(Labels.ISOLATE_WORKSPACES); + interestingWindows.forEach(window => + this._signalsHandler.addWithLabel(Labels.ISOLATE_WORKSPACES, + window, 'workspace-changed', () => this._updateWindows())); + } + } + + _updateRunningState() { + this.running = (this.app.state === Shell.AppState.RUNNING) && this.windowsCount; + } + + _updateFocusState() { + this.focused = tracker.focus_app === this.app && this.running; + } + + _updateUrgentWindows(interestingWindows) { + this._signalsHandler.removeWithLabel(Labels.URGENT_WINDOWS); + this._urgentWindows.clear(); + if (interestingWindows === undefined) + interestingWindows = this.getInterestingWindows(); + interestingWindows.filter(isWindowUrgent).forEach(win => this._addUrgentWindow(win)); + this.urgent = !!this._urgentWindows.size; + } + + _onWindowDemandsAttention(window) { + if (this.ownsWindow(window) && isWindowUrgent(window)) + this._addUrgentWindow(window); + } + + _updateDotStyle() { + super._updateDotStyle(); + const themeNode = this._dot.get_theme_node(); + this._dot.translationX = themeNode.get_length('offset-x'); + } + + _addUrgentWindow(window) { + if (this._urgentWindows.has(window)) + return; + + if (window._manualUrgency && window.has_focus()) { + delete window._manualUrgency; + return; + } + + this._urgentWindows.add(window); + this.urgent = true; + + const onDemandsAttentionChanged = () => { + if (!isWindowUrgent(window)) + this._updateUrgentWindows(); + }; + + if (window.demandsAttention) { + this._signalsHandler.addWithLabel(Labels.URGENT_WINDOWS, window, + 'notify::demands-attention', () => onDemandsAttentionChanged()); + } + if (window.urgent) { + this._signalsHandler.addWithLabel(Labels.URGENT_WINDOWS, window, + 'notify::urgent', () => onDemandsAttentionChanged()); + } + if (window._manualUrgency) { + this._signalsHandler.addWithLabel(Labels.URGENT_WINDOWS, window, + 'focus', () => { + delete window._manualUrgency; + onDemandsAttentionChanged(); + }); + } + } + + /** + * Update target for minimization animation + */ + updateIconGeometry() { + // If (for unknown reason) the actor is not on the stage the reported size + // and position are random values, which might exceeds the integer range + // resulting in an error when assigned to the a rect. This is a more like + // a workaround to prevent flooding the system with errors. + if (!this.get_stage()) + return; + + const rect = new Mtk.Rectangle(); + + [rect.x, rect.y] = this.get_transformed_position(); + [rect.width, rect.height] = this.get_transformed_size(); + + let windows = this.getWindows(); + if (Docking.DockManager.settings.multiMonitor) { + const {monitorIndex} = this; + windows = windows.filter(w => w.get_monitor() === monitorIndex); + } + windows.forEach(w => w.set_icon_geometry(rect)); + } + + _updateRunningStyle() { + // The logic originally in this function has been moved to + // AppIconIndicatorBase._updateDefaultDot(). However it cannot be removed as + // it called by the parent constructor. + } + + popupMenu() { + this._removeMenuTimeout?.(); + this.fake_release(); + this._draggable.fakeRelease?.(); + + if (!this._menu) { + this._menu = new DockAppIconMenu(this); + this._menu.connect('activate-window', (menu, window) => { + if (window) { + Main.activateWindow(window); + } else { + Main.overview.hide(); + Main.panel.closeCalendar(); + } + }); + this._menu.connect('open-state-changed', (menu, isPoppedUp) => { + if (!isPoppedUp) { + this._onMenuPoppedDown(); + } else { + // Setting the max-height is s useful if part of the menu is + // scrollable so the minimum height is smaller than the natural height. + const monitorIndex = Main.layoutManager.findIndexForActor(this); + const workArea = Main.layoutManager.getWorkAreaForMonitor(monitorIndex); + const position = Utils.getPosition(); + const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage); + const isHorizontal = position === St.Side.TOP || position === St.Side.BOTTOM; + // If horizontal also remove the height of the dash + const {dockFixed: fixedDock} = Docking.DockManager.settings; + const additionalMargin = isHorizontal && !fixedDock ? Main.overview.dash.height : 0; + const verticalMargins = this._menu.actor.margin_top + this._menu.actor.margin_bottom; + const maxMenuHeight = workArea.height - additionalMargin - verticalMargins; + // Also set a max width to the menu, so long labels (long windows title) get truncated + this._menu.actor.style = 'max-width: 400px; ' + + `max-height: ${Math.round(maxMenuHeight / scaleFactor)}px;`; + } + }); + const id = Main.overview.connect('hiding', () => { + this._menu.close(); + }); + this._menu.actor.connect('destroy', () => { + Main.overview.disconnect(id); + }); + + this._menuManager.addMenu(this._menu); + } + + this.emit('menu-state-changed', true); + + this.set_hover(true); + this._menu.popup(); + // Removed in GNOME 50. + this._menuManager.ignoreRelease?.(); + this.emit('sync-tooltip'); + + return false; + } + + activate(button) { + const event = Clutter.get_current_event(); + let modifiers = event ? event.get_state() : 0; + + // Only consider SHIFT and CONTROL as modifiers (exclude SUPER, CAPS-LOCK, etc.) + modifiers &= Clutter.ModifierType.SHIFT_MASK | Clutter.ModifierType.CONTROL_MASK; + + // We don't change the CTRL-click behavior: in such case we just chain + // up the parent method and return. + if (modifiers & Clutter.ModifierType.CONTROL_MASK) { + // Keep default behavior: launch new window + // By calling the parent method I make it compatible + // with other extensions tweaking ctrl + click + super.activate(button); + return; + } + + // We check what type of click we have and if the modifier SHIFT is + // being used. We then define what buttonAction should be for this + // event. + let buttonAction = 0; + const {settings} = Docking.DockManager; + if (button && button === 2) { + if (modifiers & Clutter.ModifierType.SHIFT_MASK) + buttonAction = settings.shiftMiddleClickAction; + else + buttonAction = settings.middleClickAction; + } else if (button && button === 1) { + if (modifiers & Clutter.ModifierType.SHIFT_MASK) + buttonAction = settings.shiftClickAction; + else + buttonAction = settings.clickAction; + } + + switch (buttonAction) { + case clickAction.FOCUS_OR_APP_SPREAD: + if (!Docking.DockManager.getDefault().appSpread.supported) + buttonAction = clickAction.FOCUS_OR_PREVIEWS; + break; + + case clickAction.FOCUS_MINIMIZE_OR_APP_SPREAD: + if (!Docking.DockManager.getDefault().appSpread.supported) + buttonAction = clickAction.FOCUS_MINIMIZE_OR_PREVIEWS; + break; + } + + // We check if the app is running, and that the # of windows is > 0 in + // case we use workspace isolation. + const windows = this.getInterestingWindows(); + + // Some action modes (e.g. MINIMIZE_OR_OVERVIEW) require overview to remain open + // This variable keeps track of this + let shouldHideOverview = true; + + // We customize the action only when the application is already running + if (this.running) { + const hasUrgentWindows = !!this._urgentWindows.size; + const singleOrUrgentWindows = windows.length === 1 || hasUrgentWindows; + switch (buttonAction) { + case clickAction.MINIMIZE: + // In overview just activate the app, unless the action is explicitly + // requested with a keyboard modifier + if (!Main.overview.visible || modifiers) { + // If we have button=2 or a modifier, allow minimization even if + // the app is not focused + if (this.focused && !hasUrgentWindows || button === 2 || + modifiers & Clutter.ModifierType.SHIFT_MASK) { + // minimize all windows on double click and always in + // the case of primary click without additional modifiers + let clickCount = 0; + if (Clutter.EventType.CLUTTER_BUTTON_PRESS) + clickCount = event.get_click_count(); + const allWindows = (button === 1 && !modifiers) || clickCount > 1; + this._minimizeWindow(allWindows); + } else { + this._activateAllWindows(); + } + } else { + const [w] = windows; + Main.activateWindow(w); + } + break; + + case clickAction.MINIMIZE_OR_OVERVIEW: + // When a single window is present, toggle minimization + // If only one windows is present toggle minimization, but + // only when triggered with the simple click action + // (no modifiers, no middle click). + if (singleOrUrgentWindows && !modifiers && button === 1) { + const [w] = windows; + if (this.focused) { + if (buttonAction !== clickAction.FOCUS_OR_APP_SPREAD) { + // Window is raised, minimize it + this._minimizeWindow(w); + } + } else { + // Window is minimized, raise it + Main.activateWindow(w); + } + // Launch overview when multiple windows are present + // TODO: only show current app windows when gnome shell API will allow it + } else { + shouldHideOverview = false; + Main.overview.toggle(); + } + break; + + case clickAction.CYCLE_WINDOWS: + if (!Main.overview.visible) { + if (this.focused && !hasUrgentWindows) { + this._cycleThroughWindows(); + } else { + // Activate the first window + const [w] = windows; + Main.activateWindow(w); + } + } else { + this.app.activate(); + } + break; + + case clickAction.FOCUS_OR_PREVIEWS: + if (this.focused && !hasUrgentWindows && + (windows.length > 1 || modifiers || button !== 1)) { + this._windowPreviews(); + } else { + // Activate the first window + const [w] = windows; + Main.activateWindow(w); + } + break; + + case clickAction.FOCUS_MINIMIZE_OR_PREVIEWS: + if (this.focused && !hasUrgentWindows) { + if (windows.length > 1 || modifiers || button !== 1) + this._windowPreviews(); + else if (!Main.overview.visible) + this._minimizeWindow(); + } else { + // Activate the first window + const [w] = windows; + Main.activateWindow(w); + } + break; + + case clickAction.LAUNCH: + this.launchNewWindow(); + break; + + case clickAction.PREVIEWS: + if (!Main.overview.visible) { + // If only one windows is present just switch to it, + // but only when triggered with the simple click action + // (no modifiers, no middle click). + if (singleOrUrgentWindows && !modifiers && button === 1) { + const [w] = windows; + Main.activateWindow(w); + } else { + this._windowPreviews(); + } + } else { + this.app.activate(); + } + break; + + case clickAction.MINIMIZE_OR_PREVIEWS: + // When a single window is present, toggle minimization + // If only one windows is present toggle minimization, but only + // when triggered with the standard click action (no modifiers, + // no middle click). + if (!Main.overview.visible) { + if (singleOrUrgentWindows && !modifiers && button === 1) { + const [w] = windows; + if (this.focused) { + // Window is raised, minimize it + this._minimizeWindow(w); + } else { + // Window is minimized, raise it + Main.activateWindow(w); + } + } else { + // Launch previews when multiple windows are present + this._windowPreviews(); + } + } else { + this.app.activate(); + } + break; + + case clickAction.FOCUS_OR_APP_SPREAD: + if (this.focused && !singleOrUrgentWindows && !modifiers && button === 1) { + shouldHideOverview = false; + Docking.DockManager.getDefault().appSpread.toggle(this.app); + } else { + // Activate the first window + Main.activateWindow(windows[0]); + } + break; + + case clickAction.FOCUS_MINIMIZE_OR_APP_SPREAD: + if (this.focused && !singleOrUrgentWindows && !modifiers && button === 1) { + shouldHideOverview = false; + Docking.DockManager.getDefault().appSpread.toggle(this.app); + } else if (!this.focused) { + // Activate the first window + Main.activateWindow(windows[0]); + } else { + this._minimizeWindow(); + } + break; + + case clickAction.QUIT: + this.closeAllWindows(); + break; + + case clickAction.SKIP: + Main.activateWindow(windows[0]); + break; + } + } else { + this.launchNewWindow(); + } + + // Hide overview except when action mode requires it + if (shouldHideOverview) + Main.overview.hide(); + } + + shouldShowTooltip() { + return super.shouldShowTooltip() && !this._previewMenu?.isOpen && + !Docking.DockManager.settings.hideTooltip; + } + + _windowPreviews() { + if (!this._previewMenu) { + this._previewMenuManager = new PopupMenu.PopupMenuManager(this); + + this._previewMenu = new WindowPreview.WindowPreviewMenu(this); + + this._previewMenuManager.addMenu(this._previewMenu); + + this._previewMenu.connect('open-state-changed', (menu, isPoppedUp) => { + if (!isPoppedUp) + this._onMenuPoppedDown(); + }); + const id = Main.overview.connect('hiding', () => { + this._previewMenu.close(); + }); + this._previewMenu.actor.connect('destroy', () => { + Main.overview.disconnect(id); + }); + } + + this.emit('menu-state-changed', !this._previewMenu.isOpen); + + if (this._previewMenu.isOpen) + this._previewMenu.close(); + else + this._previewMenu.popup(); + + return false; + } + + // Try to do the right thing when attempting to launch a new window of an app. In + // particular, if the application doesn't allow to launch a new window, activate + // the existing window instead. + launchNewWindow() { + if (this.updating) { + const icon = Gio.Icon.new_for_string('action-unavailable-symbolic'); + Main.osdWindowManager.show(-1, icon, + _('%s is updating, try again later').format(this.name), + null); + return; + } + + if (this.app.state === Shell.AppState.RUNNING && + this.app.can_open_new_window()) { + this.animateLaunch(); + this.app.open_new_window(-1); + } else { + // Try to manually activate the first window. Otherwise, when the + // app is activated by switching to a different workspace, a launch + // spinning icon is shown and disappears only after a timeout. + const windows = this.getWindows(); + if (windows.length > 0) { + Main.activateWindow(windows[0]); + } else { + this.app.activate(); + this.animateLaunch(); + } + } + } + + _numberOverlay() { + // Add label for a Hot-Key visual aid + this._numberOverlayLabel = new St.Label(); + this._numberOverlayBin = new St.Bin({ + child: this._numberOverlayLabel, + x_align: Clutter.ActorAlign.START, + y_align: Clutter.ActorAlign.START, + x_expand: true, y_expand: true, + }); + this._numberOverlayLabel.add_style_class_name('number-overlay'); + this._numberOverlayOrder = -1; + this._numberOverlayBin.hide(); + + this._iconContainer.add_child(this._numberOverlayBin); + } + + updateNumberOverlay() { + // We apply an overall scale factor that might come from a HiDPI monitor. + // Clutter dimensions are in physical pixels, but CSS measures are in logical + // pixels, so make sure to consider the scale. + const scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; + // Set the font size to something smaller than the whole icon so it is + // still visible. The border radius is large to make the shape circular + const [minWidth_, natWidth] = this._iconContainer.get_preferred_width(-1); + const fontSize = Math.round(Math.max(12, 0.3 * natWidth) / scaleFactor); + const size = Math.round(fontSize * 1.2); + this._numberOverlayLabel.set_style( + `font-size: ${fontSize}px;` + + `border-radius: ${this.icon.iconSize}px;` + + `width: ${size}px; height: ${size}px;` + ); + } + + setNumberOverlay(number) { + this._numberOverlayOrder = number; + this._numberOverlayLabel.set_text(number.toString()); + } + + toggleNumberOverlay(activate) { + if (activate && this._numberOverlayOrder > -1) { + this.updateNumberOverlay(); + this._numberOverlayBin.show(); + } else { + this._numberOverlayBin.hide(); + } + } + + _minimizeWindow(param) { + // Param true make all app windows minimize + const windows = this.getInterestingWindows(); + const currentWorkspace = global.workspace_manager.get_active_workspace(); + for (let i = 0; i < windows.length; i++) { + const w = windows[i]; + if (w.get_workspace() === currentWorkspace && w.showing_on_its_workspace()) { + w.minimize(); + // Just minimize one window. By specification it should be the + // focused window on the current workspace. + if (!param) + break; + } + } + } + + // By default only non minimized windows are activated. + // This activates all windows in the current workspace. + _activateAllWindows() { + // First activate first window so workspace is switched if needed. + // We don't do this if isolation is on! + if (!Docking.DockManager.settings.isolateWorkspaces && + !Docking.DockManager.settings.isolateMonitors) { + if (!this.running) + this.animateLaunch(); + this.app.activate(); + } + + // then activate all other app windows in the current workspace + const windows = this.getInterestingWindows(); + const activeWorkspace = global.workspace_manager.get_active_workspace_index(); + + if (windows.length <= 0) + return; + + for (let i = windows.length - 1; i >= 0; i--) { + if (windows[i].get_workspace()?.index() === activeWorkspace) + Main.activateWindow(windows[i]); + } + } + + // This closes all windows of the app. + closeAllWindows() { + const windows = this.getInterestingWindows(); + const time = global.get_current_time(); + windows.forEach(w => w.delete(time)); + } + + _cycleThroughWindows(reversed) { + // Store for a little amount of time last clicked app and its windows + // since the order changes upon window interaction + const MEMORY_TIME = 3000; + + const appWindows = this.getInterestingWindows(); + + if (appWindows.length < 1) + return; + + if (recentlyClickedAppLoopId > 0) + GLib.source_remove(recentlyClickedAppLoopId); + recentlyClickedAppLoopId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, MEMORY_TIME, this._resetRecentlyClickedApp); + + // If there isn't already a list of windows for the current app, + // or the stored list is outdated, use the current windows list. + const monitorIsolation = Docking.DockManager.settings.isolateMonitors; + if (!recentlyClickedApp || + recentlyClickedApp.get_id() !== this.app.get_id() || + recentlyClickedAppWindows.length !== appWindows.length || + (recentlyClickedAppMonitor !== this.monitorIndex && monitorIsolation)) { + recentlyClickedApp = this.app; + recentlyClickedAppWindows = appWindows; + recentlyClickedAppMonitor = this.monitorIndex; + recentlyClickedAppIndex = 0; + } + + if (reversed) { + recentlyClickedAppIndex--; + if (recentlyClickedAppIndex < 0) + recentlyClickedAppIndex = recentlyClickedAppWindows.length - 1; + } else { + recentlyClickedAppIndex++; + } + const index = recentlyClickedAppIndex % recentlyClickedAppWindows.length; + const window = recentlyClickedAppWindows[index]; + + Main.activateWindow(window); + } + + _resetRecentlyClickedApp() { + if (recentlyClickedAppLoopId > 0) + GLib.source_remove(recentlyClickedAppLoopId); + recentlyClickedAppLoopId = 0; + recentlyClickedApp = null; + recentlyClickedAppWindows = null; + recentlyClickedAppIndex = 0; + recentlyClickedAppMonitor = -1; + + return false; + } + + getWindows() { + return this.app.get_windows(); + } + + // Filter out unnecessary windows, for instance + // nautilus desktop window. + getInterestingWindows() { + const interestingWindows = getInterestingWindows(this.getWindows(), + this.monitorIndex); + + if (!this._urgentWindows.size) + return interestingWindows; + + return [...new Set([...this._urgentWindows, ...interestingWindows])]; + } + + getSnapName() { + return this.app.appInfo?.get_string('X-SnapInstanceName'); + } +}); + +const DockAppIcon = GObject.registerClass({ +}, class DockAppIcon extends DockAbstractAppIcon { + _init(app, monitorIndex, iconAnimator) { + super._init(app, monitorIndex, iconAnimator); + + this._signalsHandler.add(tracker, 'notify::focus-app', () => this._updateFocusState()); + } +}); + +const DockLocationAppIcon = GObject.registerClass({ +}, class DockLocationAppIcon extends DockAbstractAppIcon { + _init(app, monitorIndex, iconAnimator) { + if (!(app.appInfo instanceof Locations.LocationAppInfo)) + throw new Error('Provided application %s is not a Location'.format(app)); + + super._init(app, monitorIndex, iconAnimator); + + if (Docking.DockManager.settings.isolateLocations) { + this._signalsHandler.add(tracker, 'notify::focus-app', () => this._updateFocusState()); + } else { + this._signalsHandler.add(global.display, 'notify::focus-window', + () => this._updateFocusState()); + } + + this._signalsHandler.add(this.app, 'notify::icon', () => this.icon.update()); + } + + get location() { + return this.app.location; + } + + _updateFocusState() { + if (Docking.DockManager.settings.isolateLocations) { + super._updateFocusState(); + return; + } + + this.focused = this.app.isFocused && this.running; + } +}); + +/** + * @param app + * @param monitorIndex + * @param iconAnimator + */ +export function makeAppIcon(app, monitorIndex, iconAnimator) { + if (app.appInfo instanceof Locations.LocationAppInfo) + return new DockLocationAppIcon(app, monitorIndex, iconAnimator); + + return new DockAppIcon(app, monitorIndex, iconAnimator); +} + +/** + * DockAppIconMenu + * + * - set popup arrow side based on dash orientation + * - Add close windows option based on quitfromdash extension + * (https://github.com/deuill/shell-extension-quitfromdash) + * - Add open windows thumbnails instead of list + * - update menu when application windows change + */ +const DockAppIconMenu = class DockAppIconMenu extends PopupMenu.PopupMenu { + constructor(source) { + super(source, 0.5, Utils.getPosition()); + + this._signalsHandler = new Utils.GlobalSignalsHandler(this); + + // We want to keep the item hovered while the menu is up + this.blockSourceEvents = true; + + this.actor.add_style_class_name('app-menu'); + this.actor.add_style_class_name('dock-app-menu'); + + // Chain our visibility and lifecycle to that of the source + this._signalsHandler.add(source, 'notify::mapped', () => { + if (!source.mapped) + this.close(); + }); + source.connect('destroy', () => this.destroy()); + + Main.uiGroup.add_child(this.actor); + + const {remoteModel} = Docking.DockManager.getDefault(); + const remoteModelApp = remoteModel?.lookupById(this.sourceActor?.app?.id); + if (remoteModelApp && DBusMenu) { + const [onQuickList, onDynamicSection] = Utils.splitHandler((sender, + {quicklist}, dynamicSection) => { + dynamicSection.removeAll(); + if (quicklist) { + quicklist.get_children().forEach(remoteItem => + dynamicSection.addMenuItem( + DBusMenuUtils.makePopupMenuItem(remoteItem, false))); + } + }); + + this._signalsHandler.add([ + remoteModelApp, + 'quicklist-changed', + onQuickList, + ], [ + this, + 'dynamic-section-changed', + onDynamicSection, + ]); + } + } + + destroy() { + super.destroy(); + delete this.sourceActor; + delete this._signalsHandler; + } + + _appendSeparator() { + this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + } + + _appendMenuItem(labelText, params) { + const item = new PopupMenu.PopupMenuItem(labelText, params); + this.addMenuItem(item); + return item; + } + + popup(_activatingButton) { + this._rebuildMenu(); + this.open(BoxPointer.PopupAnimation.FULL); + } + + removeAll() { + super.removeAll(); + + delete this._allWindowsMenuItem; + delete this._quitMenuItem; + } + + _rebuildMenu() { + this.removeAll(); + + const appItemLabel = this.sourceActor.updating + ? _('%s is being updated…').format(this.sourceActor.name) + : this.sourceActor.name; + this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem(appItemLabel)); + + const {app} = this.sourceActor; + + if (Docking.DockManager.settings.showWindowsPreview) { + // Display the app windows menu items and the separator between windows + // of the current desktop and other windows. + const windows = this.sourceActor.getInterestingWindows(); + + this._allWindowsMenuItem = new PopupMenu.PopupSubMenuMenuItem(__('All Windows'), false); + if (this._allWindowsMenuItem.menu?.actor) + this._allWindowsMenuItem.menu.actor.overlayScrollbars = true; + this._allWindowsMenuItem.hide(); + if (windows.length > 0) + this.addMenuItem(this._allWindowsMenuItem); + } else { + const windows = this.sourceActor.getInterestingWindows(); + + if (windows.length > 0) { + this.addMenuItem( + /* Translators: This is the heading of a list of open windows */ + new PopupMenu.PopupSeparatorMenuItem(_('Open Windows'))); + } + + windows.forEach(window => { + const title = window.title ? window.title : app.get_name(); + const item = this._appendMenuItem(title); + item.connect('activate', () => { + this.emit('activate-window', window); + }); + }); + } + + if (!app.is_window_backed()) { + this._appendSeparator(); + + const appInfo = app.get_app_info(); + const actions = this.sourceActor.updating ? [] : appInfo.list_actions(); + if (!this.sourceActor.updating && + app.can_open_new_window() && + actions.indexOf('new-window') === -1) { + const newMenuItem = this._appendMenuItem(_('New Window')); + newMenuItem.connect('activate', () => { + if (app.state === Shell.AppState.STOPPED) + this.sourceActor.animateLaunch(); + + app.open_new_window(-1); + this.emit('activate-window', null); + }); + this._appendSeparator(); + } + + if (!this.sourceActor.updating && + Docking.DockManager.getDefault().discreteGpuAvailable && + app.state === Shell.AppState.STOPPED) { + const appPrefersNonDefaultGPU = appInfo.get_boolean('PrefersNonDefaultGPU'); + const gpuPref = appPrefersNonDefaultGPU + ? Shell.AppLaunchGpu.DEFAULT + : Shell.AppLaunchGpu.DISCRETE; + const gpuMenuItem = this._appendMenuItem(appPrefersNonDefaultGPU + ? _('Launch using Integrated Graphics Card') + : _('Launch using Discrete Graphics Card')); + gpuMenuItem.connect('activate', () => { + this.sourceActor.animateLaunch(); + app.launch(0, -1, gpuPref); + this.emit('activate-window', null); + }); + } + + for (let i = 0; i < actions.length; i++) { + const action = actions[i]; + const item = this._appendMenuItem(appInfo.get_action_name(action)); + item.sensitive = !appInfo.busy; + item.connect('activate', (emitter, event) => { + app.launch_action(action, event.get_time(), -1); + this.emit('activate-window', null); + }); + } + + const canFavorite = global.settings.is_writable('favorite-apps') && + (this.sourceActor instanceof DockAppIcon) && + ParentalControlsManager.getDefault().shouldShowApp(app.appInfo); + + if (canFavorite) { + this._appendSeparator(); + + const isFavorite = AppFavorites.getAppFavorites().isFavorite(app.get_id()); + if (isFavorite) { + const item = this._appendMenuItem(_('Unpin')); + item.connect('activate', () => { + const favs = AppFavorites.getAppFavorites(); + favs.removeFavorite(app.get_id()); + }); + } else { + const item = this._appendMenuItem(__('Pin to Dock')); + item.connect('activate', () => { + const favs = AppFavorites.getAppFavorites(); + favs.addFavorite(app.get_id()); + }); + } + } + + if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop') && + this.sourceActor instanceof DockAppIcon && + !this.sourceActor.getSnapName()) { + this._appendSeparator(); + const item = this._appendMenuItem(_('App Details')); + item.connect('activate', () => { + const id = app.get_id(); + const args = GLib.Variant.new('(ss)', [id, '']); + Gio.DBus.get(Gio.BusType.SESSION, null, + (o, res) => { + const bus = Gio.DBus.get_finish(res); + bus.call('org.gnome.Software', + '/org/gnome/Software', + 'org.gtk.Actions', 'Activate', + GLib.Variant.new('(sava{sv})', + ['details', [args], null]), + null, 0, -1, null, null); + Main.overview.hide(); + }); + }); + } + + if (this.sourceActor instanceof DockAppIcon) { + const snapName = this.sourceActor.getSnapName(); + const snapStore = snapName + ? Shell.AppSystem.get_default().lookup_app( + 'snap-store_snap-store.desktop') : null; + + if (snapStore) { + this._appendSeparator(); + const item = this._appendMenuItem(_('App Details')); + item.connect('activate', (_, event) => { + snapStore.activate_full(-1, event.get_time()); + Util.spawnApp( + [...snapStore.appInfo.get_commandline().split(' '), snapName]); + Main.overview.hide(); + }); + } + } + } + + // dynamic menu + const items = this._getMenuItems(); + let i = items.length; + if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop')) + i -= 2; + + if (global.settings.is_writable('favorite-apps')) + i -= 2; + + if (i < 0) + i = 0; + + const dynamicSection = new PopupMenu.PopupMenuSection(); + this.addMenuItem(dynamicSection, i); + this.emit('dynamic-section-changed', dynamicSection); + + // quit menu + this._appendSeparator(); + this._quitMenuItem = this._appendMenuItem(_('Quit')); + this._quitMenuItem.connect('activate', () => this.sourceActor.closeAllWindows()); + + this.update(); + } + + // update menu content when application windows change. This is desirable as actions + // acting on windows (closing) are performed while the menu is shown. + update() { + // update, show or hide the quit menu + if (this.sourceActor.windowsCount > 0) { + if (this.sourceActor.windowsCount === 1) { + this._quitMenuItem.label.set_text(_('Quit')); + } else { + this._quitMenuItem.label.set_text(ngettext( + 'Quit %d Window', 'Quit %d Windows', this.sourceActor.windowsCount).format( + this.sourceActor.windowsCount)); + } + + this._quitMenuItem.actor.show(); + } else { + this._quitMenuItem.actor.hide(); + } + + if (Docking.DockManager.settings.showWindowsPreview) { + const windows = this.sourceActor.getInterestingWindows(); + + // update, show, or hide the allWindows menu + // Check if there are new windows not already displayed. In such case, + // repopulate the allWindows menu. Windows removal is already handled + // by each preview being connected to the destroy signal + const oldWindows = this._allWindowsMenuItem.menu._getMenuItems().map(item => { + return item._window; + }); + + const newWindows = windows.filter(w => + oldWindows.indexOf(w) < 0); + if (newWindows.length > 0) { + this._populateAllWindowMenu(windows); + + // Try to set the width to that of the submenu. + // TODO: can't get the actual size, getting a bit less. + // Temporary workaround: add 15px to compensate + this._allWindowsMenuItem.width = this._allWindowsMenuItem.menu.actor.width + 15; + } + + // The menu is created hidden and never hidden after being shown. + // Instead, a signal connected to its items destroy will set is + // insensitive if no more windows preview are shown. + if (windows.length > 0) { + this._allWindowsMenuItem.show(); + this._allWindowsMenuItem.setSensitive(true); + + if (Docking.DockManager.settings.defaultWindowsPreviewToOpen) + this._allWindowsMenuItem.menu.open(); + } + } + + // Update separators + this._getMenuItems().forEach(item => { + if ('label' in item) + this._updateSeparatorVisibility(item); + }); + } + + _populateAllWindowMenu(windows) { + this._allWindowsMenuItem.menu.removeAll(); + + if (windows.length > 0) { + const activeWorkspace = global.workspace_manager.get_active_workspace(); + let separatorShown = windows[0].get_workspace() !== activeWorkspace; + + for (let i = 0; i < windows.length; i++) { + const window = windows[i]; + if (!separatorShown && window.get_workspace() !== activeWorkspace) { + this._allWindowsMenuItem.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + separatorShown = true; + } + + const item = new WindowPreview.WindowPreviewMenuItem(window, + St.Side.LEFT); + this._allWindowsMenuItem.menu.addMenuItem(item); + item.connect('activate', () => { + this.emit('activate-window', window); + }); + + // This is to achieve a more graceful transition when the last + // window is closed. + item.connect('destroy', () => { + // It's still counting the item just going to be destroyed + if (this._allWindowsMenuItem.menu._getMenuItems().length === 1) + this._allWindowsMenuItem.setSensitive(false); + }); + } + } + } +}; + +/** + * @param w + */ +function isWindowUrgent(w) { + return w.urgent || w.demandsAttention || w._manualUrgency; +} + +/** + * Filter out unnecessary windows, for instance + * nautilus desktop window. + * + * @param windows + * @param monitorIndex + */ +export function getInterestingWindows(windows, monitorIndex) { + const {settings} = Docking.DockManager; + + // When using workspace isolation, we filter out windows + // that are neither in the current workspace nor marked urgent + if (settings.isolateWorkspaces) { + const showUrgent = settings.workspaceAgnosticUrgentWindows; + const activeWorkspace = global.workspace_manager.get_active_workspace(); + windows = windows.filter(w => { + const inWorkspace = w.get_workspace() === activeWorkspace; + return inWorkspace || (showUrgent && isWindowUrgent(w)); + }); + } + + if (settings.isolateMonitors && monitorIndex >= 0) { + windows = windows.filter(w => { + return w.get_monitor() === monitorIndex; + }); + } + + return windows.filter(w => !w.skipTaskbar); +} + +/** + * A ShowAppsIcon improved class. + * + * - set label position based on dash orientation + * Note: we are am reusing most machinery of the appIcon class. + * - implement a popupMenu based on the AppIcon code + * Note: we are reusing most machinery of the appIcon class) + * + */ + +export const DockShowAppsIcon = GObject.registerClass({ + Signals: { + 'menu-state-changed': {param_types: [GObject.TYPE_BOOLEAN]}, + 'sync-tooltip': {}, + }, +} +, class DockShowAppsIcon extends Dash.ShowAppsIcon { + _init(position) { + super._init(); + + // Re-use appIcon methods + const {prototype: appIconPrototype} = AppDisplay.AppIcon; + this.toggleButton.y_expand = false; + this.toggleButton.connect('popup-menu', () => + appIconPrototype._onKeyboardPopupMenu.call(this)); + this.toggleButton.connect('clicked', () => + this._removeMenuTimeout()); + + this.reactive = true; + this.toggleButton.popupMenu = (...args) => + this.popupMenu(...args); + this.toggleButton._setPopupTimeout = (...args) => + this._setPopupTimeout(...args); + this.toggleButton._removeMenuTimeout = (...args) => + this._removeMenuTimeout(...args); + + this.label?.add_style_class_name(Theming.PositionStyleClass[position]); + if (Docking.DockManager.settings.customThemeShrink) + this.label?.add_style_class_name('shrink'); + + this._menu = null; + this._menuManager = new PopupMenu.PopupMenuManager(this); + this._menuTimeoutId = 0; + + this._maybeEnablePopupGestures(); + } + + _createIcon(size) { + this._iconActor = super._createIcon(size); + this._iconActor.fallbackIconName = this._iconActor.iconName; + this._iconActor.fallbackGicon = this._iconActor.gicon; + this._iconActor.iconName = `view-app-grid-${Main.sessionMode.currentMode}-symbolic`; + return this._iconActor; + } + + vfunc_leave_event(...args) { + return AppDisplay.AppIcon.prototype.vfunc_leave_event.call( + this.toggleButton, ...args); + } + + vfunc_button_press_event(...args) { + try { + // TODO: Drop this when not supporting GNOME 48 (and older) anymore. + return AppDisplay.AppIcon.prototype.vfunc_button_press_event.call( + this.toggleButton, ...args); + } catch { + return Clutter.EVENT_PROPAGATE; + } + } + + vfunc_touch_event(...args) { + try { + // TODO: Drop this when not supporting GNOME 48 (and older) anymore. + return AppDisplay.AppIcon.prototype.vfunc_touch_event.call( + this.toggleButton, ...args); + } catch { + return Clutter.EVENT_PROPAGATE; + } + } + + showLabel(...args) { + itemShowLabel.call(this, ...args); + } + + setForcedHighlight(...args) { + AppDisplay.AppIcon.prototype.setForcedHighlight.call(this, ...args); + } + + _onMenuPoppedDown(...args) { + AppDisplay.AppIcon.prototype._onMenuPoppedDown.call(this, ...args); + } + + _setPopupTimeout(...args) { + AppDisplay.AppIcon.prototype._setPopupTimeout.call(this, ...args); + } + + _removeMenuTimeout(...args) { + AppDisplay.AppIcon.prototype._removeMenuTimeout?.call(this, ...args); + } + + _hasPopupMenu() { + return Docking.DockManager.extension.uuid !== 'ubuntu-dock@ubuntu.com'; + } + + _maybeEnablePopupGestures() { + if (!this._hasPopupMenu()) + return; + + if (!Clutter.LongPressGesture || !Clutter.ClickGesture) + return; + + const longPressGesture = new Clutter.LongPressGesture(); + longPressGesture.connect('recognize', () => this.popupMenu()); + this.add_action(longPressGesture); + + const rightClickGesture = new Clutter.ClickGesture({ + required_button: Clutter.BUTTON_SECONDARY, + recognize_on_press: true, + }); + rightClickGesture.connect('recognize', () => this.popupMenu()); + this.add_action(rightClickGesture); + } + + popupMenu() { + if (!this._hasPopupMenu()) + return false; + + this._removeMenuTimeout(); + this.toggleButton.fake_release(); + + if (!this._menu) { + this._menu = new DockShowAppsIconMenu(this); + this._menu.connect('open-state-changed', (menu, isPoppedUp) => { + if (!isPoppedUp) + this._onMenuPoppedDown(); + }); + const id = Main.overview.connect('hiding', () => { + this._menu.close(); + }); + this._menu.actor.connect('destroy', () => { + Main.overview.disconnect(id); + }); + this._menuManager.addMenu(this._menu); + } + + this.emit('menu-state-changed', true); + + this.toggleButton.set_hover(true); + this._menu.popup(); + // Removed in GNOME 50. + this._menuManager.ignoreRelease?.(); + this.emit('sync-tooltip'); + + return false; + } +}); + + +/** + * A menu for the showAppsIcon + */ +class DockShowAppsIconMenu extends DockAppIconMenu { + _rebuildMenu() { + this.removeAll(); + + this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem(__('Dash to Dock'))); + + const item = this._appendMenuItem(_('Settings')); + item.connect('activate', () => + Docking.DockManager.extension.openPreferences()); + } +} + +/** + * This function is used for both DockShowAppsIcon and DockDashItemContainer + */ +export function itemShowLabel() { + /* eslint-disable no-invalid-this */ + // Check if the label is still present at all. When switching workspace, the + // item might have been destroyed in between. + if (!this._labelText || !this.label.get_stage()) + return; + + this.label.set_text(this._labelText); + this.label.opacity = 0; + this.label.show(); + + const [stageX, stageY] = this.get_transformed_position(); + const node = this.label.get_theme_node(); + + const itemWidth = this.allocation.x2 - this.allocation.x1; + const itemHeight = this.allocation.y2 - this.allocation.y1; + + const labelWidth = this.label.get_width(); + const labelHeight = this.label.get_height(); + + let x, y, xOffset, yOffset; + + const position = Utils.getPosition(); + const labelOffset = node.get_length('-x-offset'); + + switch (position) { + case St.Side.LEFT: + yOffset = Math.floor((itemHeight - labelHeight) / 2); + y = stageY + yOffset; + xOffset = labelOffset; + x = stageX + this.get_width() + xOffset; + break; + case St.Side.RIGHT: + yOffset = Math.floor((itemHeight - labelHeight) / 2); + y = stageY + yOffset; + xOffset = labelOffset; + x = Math.round(stageX) - labelWidth - xOffset; + break; + case St.Side.TOP: + y = stageY + labelOffset + itemHeight; + xOffset = Math.floor((itemWidth - labelWidth) / 2); + x = stageX + xOffset; + break; + case St.Side.BOTTOM: + yOffset = labelOffset; + y = stageY - labelHeight - yOffset; + xOffset = Math.floor((itemWidth - labelWidth) / 2); + x = stageX + xOffset; + break; + } + + // keep the label inside the screen border + // Only needed fot the x coordinate. + + // Leave a few pixel gap + const gap = 5; + const monitor = Main.layoutManager.findMonitorForActor(this); + if (x - monitor.x < gap) + x += monitor.x - x + labelOffset; + else if (x + labelWidth > monitor.x + monitor.width - gap) + x -= x + labelWidth - (monitor.x + monitor.width) + gap; + + this.label.remove_all_transitions(); + this.label.set_position(x, y); + this.label.ease({ + opacity: 255, + duration: DASH_ITEM_LABEL_SHOW_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + /* eslint-enable no-invalid-this */ +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIconsDecorator.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIconsDecorator.js new file mode 100644 index 0000000..89afa3e --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appIconsDecorator.js @@ -0,0 +1,168 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + + +import { + Docking, + AppIconIndicators, + Utils, +} from './imports.js'; + +import { + Gio, +} from './dependencies/gi.js'; + +import { + AppMenu, + AppDisplay, + Main, + PopupMenu, +} from './dependencies/shell/ui.js'; + +const Labels = Object.freeze({ + GENERIC: Symbol('generic'), + ICONS: Symbol('icons'), +}); + +export class AppIconsDecorator { + constructor() { + this._signals = new Utils.GlobalSignalsHandler(); + this._methodInjections = new Utils.InjectionsHandler(); + this._propertyInjections = new Utils.PropertyInjectionsHandler( + null, {allowNewProperty: true}); + this._indicators = new Set(); + + this._patchAppIcons(); + this._decorateIcons(); + } + + destroy() { + this._signals?.destroy(); + delete this._signals; + this._methodInjections?.destroy(); + delete this._methodInjections; + this._propertyInjections?.destroy(); + delete this._propertyInjections; + this._indicators?.forEach(i => i.destroy()); + this._indicators?.clear(); + delete this._indicators; + } + + _decorateIcon(parentIcon, signalLabel = Labels.GENERIC) { + const indicator = new AppIconIndicators.UnityIndicator(parentIcon); + this._indicators.add(indicator); + this._signals.addWithLabel(signalLabel, parentIcon, 'destroy', () => { + this._indicators.delete(indicator); + indicator.destroy(); + }); + return indicator; + } + + _decorateIcons() { + const {appDisplay} = Docking.DockManager.getDefault().overviewControls; + + const decorateAppIcons = () => { + this._indicators.forEach(i => i.destroy()); + this._indicators.clear(); + this._signals.removeWithLabel(Labels.ICONS); + + const decorateViewIcons = view => { + const items = view.getAllItems(); + items.forEach(i => { + if (i instanceof AppDisplay.AppIcon) { + this._decorateIcon(i, Labels.ICONS); + } else if (i instanceof AppDisplay.FolderIcon) { + decorateViewIcons(i.view); + this._signals.addWithLabel(Labels.ICONS, i.view, + 'view-loaded', () => decorateAppIcons()); + } + }); + }; + decorateViewIcons(appDisplay); + }; + + this._signals.add(appDisplay, 'view-loaded', () => decorateAppIcons()); + decorateAppIcons(); + } + + _patchAppIcons() { + const self = this; + + this._methodInjections.add(AppDisplay.AppSearchProvider.prototype, + 'createResultObject', function (originalFunction, ...args) { + /* eslint-disable no-invalid-this */ + const result = originalFunction.call(this, ...args); + if (result instanceof AppDisplay.AppIcon) + self._decorateIcon(result); + return result; + /* eslint-enable no-invalid-this */ + }); + + this._methodInjections.add(AppDisplay.AppIcon.prototype, + 'activate', function (originalFunction, ...args) { + /* eslint-disable no-invalid-this */ + if (this.updating) { + const icon = Gio.Icon.new_for_string('action-unavailable-symbolic'); + Main.osdWindowManager.show(-1, icon, + _('%s is updating, try again later').format(this.name), + null); + return; + } + + originalFunction.call(this, ...args); + /* eslint-enable no-invalid-this */ + }); + + const appIconsTypes = [ + AppDisplay.AppSearchProvider, + AppDisplay.AppIcon, + ]; + appIconsTypes.forEach(type => + this._propertyInjections.add(type.prototype, 'updating', { + get() { + return !!this.__d2dUpdating; + }, + set(updating) { + if (this.updating === updating) + return; + this.__d2dUpdating = updating; + if (updating) + this.add_style_class_name('updating'); + else + this.remove_style_class_name('updating'); + }, + })); + + this._methodInjections.add(AppMenu.AppMenu.prototype, + 'open', function (originalFunction, ...args) { + /* eslint-disable no-invalid-this */ + if (!this.sourceActor.updating) { + originalFunction.call(this, ...args); + return; + } + + if (this.isOpen) + return; + + if (this.isEmpty()) + return; + + // Temporarily hide all the menu items a part the Pinning and + // the details one while we're updating. + const validItems = [ + this._toggleFavoriteItem, + this._detailsItem, + ]; + const items = this._getMenuItems().filter( + i => !validItems.includes(i)).map(i => + i instanceof PopupMenu.PopupMenuBase ? i.actor : i); + const itemsVisibility = items.map(i => i.visible); + items.forEach(i => (i.visible = false)); + const menuClosedId = this.connect('menu-closed', () => { + this.disconnect(menuClosedId); + items.forEach((i, idx) => (i.visible = itemsVisibility[idx])); + }); + originalFunction.call(this, ...args); + /* eslint-enable no-invalid-this */ + }); + } +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appSpread.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appSpread.js new file mode 100644 index 0000000..790ef64 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/appSpread.js @@ -0,0 +1,235 @@ +import {Atk, Clutter} from './dependencies/gi.js'; + +import { + Main, + SearchController, + Workspace, + WorkspaceThumbnail, +} from './dependencies/shell/ui.js'; + +import {Utils} from './imports.js'; + +const APP_SPREAD_RESTORE_ACTION = 'dock-app-spread-restore'; + +export class AppSpread { + constructor() { + this.app = null; + this.supported = true; + this.windows = []; + + // fail early and do nothing, if mandatory gnome shell functions are missing + if (Main.overview.isDummy || + !Workspace?.Workspace?.prototype._isOverviewWindow || + !WorkspaceThumbnail?.WorkspaceThumbnail?.prototype._isOverviewWindow) { + log('Dash to dock: Unable to temporarily replace shell functions ' + + 'for app spread - using previews instead'); + this.supported = false; + return; + } + + this._signalHandlers = new Utils.GlobalSignalsHandler(); + this._methodInjections = new Utils.InjectionsHandler(); + this._vfuncInjections = new Utils.VFuncInjectionsHandler(); + } + + get isInAppSpread() { + return !!this.app; + } + + destroy() { + if (!this.supported) + return; + this._hideAppSpread(); + this._signalHandlers.destroy(); + this._methodInjections.destroy(); + this._vfuncInjections.destroy(); + } + + toggle(app) { + const newApp = this.app !== app; + if (this.app) + Main.overview.hide(); // also triggers hook 'hidden' + + if (app && newApp) + this._showAppSpread(app); + } + + _updateWindows() { + this.windows = this.app.get_windows(); + } + + _restoreDefaultWindows() { + const {workspaceManager} = global; + + for (let i = 0; i < workspaceManager.nWorkspaces; i++) { + const metaWorkspace = workspaceManager.get_workspace_by_index(i); + metaWorkspace.list_windows().forEach(w => metaWorkspace.emit('window-added', w)); + } + } + + _filterWindows() { + const {workspaceManager} = global; + + for (let i = 0; i < workspaceManager.nWorkspaces; i++) { + const metaWorkspace = workspaceManager.get_workspace_by_index(i); + metaWorkspace.list_windows().filter(w => !this.windows.includes(w)).forEach( + w => metaWorkspace.emit('window-removed', w)); + } + } + + _restoreDefaultOverview() { + this._hideAppSpread(); + this._restoreDefaultWindows(); + } + + _showAppSpread(app) { + if (this.isInAppSpread) + return; + + // Checked in overview "hide" event handler _hideAppSpread + this.app = app; + this._updateWindows(); + + // we need to hook into overview 'hidden' like this, in case app spread + // overview is hidden by choosing another app it should then do its + // cleanup too + this._signalHandlers.add(Main.overview, 'hidden', () => this._hideAppSpread()); + + const appSpread = this; + this._methodInjections.add([ + // Filter workspaces to only show current app windows + Workspace.Workspace.prototype, '_isOverviewWindow', + function (originalMethod, window) { + /* eslint-disable no-invalid-this */ + const isOverviewWindow = originalMethod.call(this, window); + return isOverviewWindow && appSpread.windows.includes(window); + /* eslint-enable no-invalid-this */ + }, + ], + [ + // Filter thumbnails to only show current app windows + WorkspaceThumbnail.WorkspaceThumbnail.prototype, '_isOverviewWindow', + function (originalMethod, windowActor) { + /* eslint-disable no-invalid-this */ + const isOverviewWindow = originalMethod.call(this, windowActor); + return isOverviewWindow && appSpread.windows.includes(windowActor.metaWindow); + /* eslint-enable no-invalid-this */ + }, + ]); + + const activitiesButton = Main.panel.statusArea?.activities; + + if (activitiesButton) { + this._signalHandlers.add(Main.overview, 'showing', () => { + activitiesButton.remove_style_pseudo_class('overview'); + activitiesButton.remove_accessible_state(Atk.StateType.CHECKED); + }); + + let hasEventVFunc = false; + try { + hasEventVFunc = !!activitiesButton.constructor.prototype.vfunc_event; + } catch {} + + if (hasEventVFunc) { + this._vfuncInjections.add([ + activitiesButton.constructor.prototype, + 'event', + function (event) { + if (event.type() === Clutter.EventType.TOUCH_END || + event.type() === Clutter.EventType.BUTTON_RELEASE) { + if (Main.overview.shouldToggleByCornerOrButton()) + appSpread._restoreDefaultOverview(); + } + return Clutter.EVENT_PROPAGATE; + }, + ]); + } else { + /* Shell >= 50 uses gestures on the activities button */ + const click = new Clutter.ClickGesture(); + click.set_recognize_on_press(true); + click.set_enabled(true); + click.connect('recognize', () => { + if (Main.overview.shouldToggleByCornerOrButton()) + appSpread._restoreDefaultOverview(); + }); + activitiesButton.add_action_with_name(APP_SPREAD_RESTORE_ACTION, click); + } + + this._vfuncInjections.add([ + activitiesButton.constructor.prototype, + 'key_release_event', + function (keyEvent) { + const keyval = keyEvent.get_key_symbol?.() ?? keyEvent.keyval; + if (keyval === Clutter.KEY_Return || keyval === Clutter.KEY_space) { + if (Main.overview.shouldToggleByCornerOrButton()) + appSpread._restoreDefaultOverview(); + } + return Clutter.EVENT_PROPAGATE; + }, + ]); + } + + this._signalHandlers.add(Main.overview.dash.showAppsButton, 'notify::checked', () => { + if (Main.overview.dash.showAppsButton.checked) + this._restoreDefaultOverview(); + }); + + // If closing windows in AppSpread, and only one window left: + // exit app spread and focus remaining window (handled in _hideAppSpread) + this._signalHandlers.add(this.app, 'windows-changed', () => { + this._updateWindows(); + + if (this.windows.length <= 1) + Main.overview.hide(); + }); + + this._disableSearch(); + + Main.overview.show(); + } + + _hideAppSpread() { + if (!this.isInAppSpread) + return; + + if (Main.overview.visible) { + Main.panel.statusArea?.activities.add_style_pseudo_class('overview'); + Main.panel.statusArea?.activities.add_accessible_state(Atk.StateType.CHECKED); + } + + // Restore original behaviour + this.app = null; + this._enableSearch(); + this._methodInjections.clear(); + this._signalHandlers.clear(); + this._vfuncInjections.clear(); + Main.panel.statusArea?.activities.remove_action_by_name(APP_SPREAD_RESTORE_ACTION); + + // Check reason for leaving AppSpread was closing app windows and only one window left... + if (this.windows.length === 1) + Main.activateWindow(this.windows[0]); + + this.windows = []; + } + + _disableSearch() { + if (Main.overview.searchEntry) { + Main.overview.searchEntry.opacity = 0; + Main.overview.searchEntry.reactive = false; + } + + if (!SearchController.SearchController.prototype._shouldTriggerSearch) + return; + + this._methodInjections.add( + SearchController.SearchController.prototype, + '_shouldTriggerSearch', () => false); + } + + _enableSearch() { + if (Main.overview.searchEntry) { + Main.overview.searchEntry.opacity = 255; + Main.overview.searchEntry.reactive = true; + } + } +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dash.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dash.js new file mode 100644 index 0000000..956bbeb --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dash.js @@ -0,0 +1,1177 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import { + Clutter, + Gio, + GLib, + GObject, + Shell, + St, +} from './dependencies/gi.js'; + +import { + AppFavorites, + Dash, + DND, + Main, +} from './dependencies/shell/ui.js'; + +import { + Util, +} from './dependencies/shell/misc.js'; + +import { + AppIcons, + Docking, + Theming, + Utils, +} from './imports.js'; + +// module "Dash" did not export DASH_ANIMATION_TIME in old versions +// so we just define it like it is defined in Dash; +// taken from https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/dash.js +const DASH_ANIMATION_TIME = Dash.DASH_ANIMATION_TIME ?? 200; +const DASH_VISIBILITY_TIMEOUT = 3; + +const Labels = Object.freeze({ + SHOW_MOUNTS: Symbol('show-mounts'), + FIRST_LAST_CHILD_WORKAROUND: Symbol('first-last-child-workaround'), +}); + +/** + * Extend DashItemContainer + * + * - set label position based on dash orientation + * + */ +const DockDashItemContainer = GObject.registerClass( +class DockDashItemContainer extends Dash.DashItemContainer { + _init(position) { + super._init(); + + this.label?.add_style_class_name(Theming.PositionStyleClass[position]); + if (Docking.DockManager.settings.customThemeShrink) + this.label?.add_style_class_name('shrink'); + } + + showLabel() { + return AppIcons.itemShowLabel.call(this); + } + + // we override the method show taken from: + // https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/ui/dash.js + // in order to apply a little modification at the end of the animation + // which makes sure that the icon background is not blurry + show(animate) { + if (this.child == null) + return; + + this.ease({ + scale_x: 1, + scale_y: 1, + opacity: 255, + duration: animate ? DASH_ANIMATION_TIME : 0, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + // when the animation is ended, we simulate + // a hover to gain back focus and unblur the + // background + this.set_hover(true); + }, + }); + } +}); + +const DockDashIconsVerticalLayout = GObject.registerClass( + class DockDashIconsVerticalLayout extends Clutter.BoxLayout { + _init() { + super._init({ + orientation: Clutter.Orientation.VERTICAL, + }); + } + + vfunc_get_preferred_height(container, forWidth) { + const [natHeight] = super.vfunc_get_preferred_height(container, forWidth); + return [natHeight, 0]; + } + }); + + +const baseIconSizes = [16, 22, 24, 32, 48, 64, 96, 128]; + +/** + * This class is a fork of the upstream dash class (ui.dash.js) + * + * Summary of changes: + * - disconnect global signals adding a destroy method; + * - play animations even when not in overview mode + * - set a maximum icon size + * - show running and/or favorite applications + * - hide showApps label when the custom menu is shown. + * - add scrollview + * ensure actor is visible on keyfocus inseid the scrollview + * - add 128px icon size, might be useful for hidpi display + * - sync minimization application target position. + * - keep running apps ordered. + */ +export const DockDash = GObject.registerClass({ + Properties: { + 'requires-visibility': GObject.ParamSpec.boolean( + 'requires-visibility', 'requires-visibility', 'requires-visibility', + GObject.ParamFlags.READWRITE, + false), + }, + Signals: { + 'menu-opened': {}, + 'menu-closed': {}, + 'icon-size-changed': {}, + }, +}, class DockDash extends St.Widget { + _init(monitorIndex) { + // Initialize icon variables and size + super._init({ + name: 'dash', + offscreen_redirect: Clutter.OffscreenRedirect.ALWAYS, + layout_manager: new Clutter.BinLayout(), + }); + + this._maxWidth = -1; + this._maxHeight = -1; + this.iconSize = Docking.DockManager.settings.dashMaxIconSize; + this._availableIconSizes = baseIconSizes; + this._shownInitially = false; + this._initializeIconSize(this.iconSize); + this._signalsHandler = new Utils.GlobalSignalsHandler(this); + + this._separator = null; + + this._monitorIndex = monitorIndex; + this._position = Utils.getPosition(); + this._isHorizontal = (this._position === St.Side.TOP) || + (this._position === St.Side.BOTTOM); + + this._dragPlaceholder = null; + this._dragPlaceholderPos = -1; + this._animatingPlaceholdersCount = 0; + this._showLabelTimeoutId = 0; + this._resetHoverTimeoutId = 0; + this._labelShowing = false; + + this._dashContainer = new St.BoxLayout({ + name: 'dashtodockDashContainer', + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER, + vertical: !this._isHorizontal, + y_expand: this._isHorizontal, + x_expand: !this._isHorizontal, + }); + + this._scrollView = new St.ScrollView({ + name: 'dashtodockDashScrollview', + hscrollbar_policy: this._isHorizontal ? St.PolicyType.EXTERNAL : St.PolicyType.NEVER, + vscrollbar_policy: this._isHorizontal ? St.PolicyType.NEVER : St.PolicyType.EXTERNAL, + x_expand: this._isHorizontal, + y_expand: !this._isHorizontal, + enable_mouse_scrolling: false, + }); + + this._scrollView.connect('scroll-event', this._onScrollEvent.bind(this)); + + this._boxContainer = new St.BoxLayout({ + name: 'dashtodockBoxContainer', + x_align: Clutter.ActorAlign.FILL, + y_align: Clutter.ActorAlign.FILL, + vertical: !this._isHorizontal, + }); + this._boxContainer.add_style_class_name(Theming.PositionStyleClass[this._position]); + + const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL; + this._box = new St.BoxLayout({ + vertical: !this._isHorizontal, + clip_to_allocation: false, + ...!this._isHorizontal ? {layout_manager: new DockDashIconsVerticalLayout()} : {}, + x_align: rtl ? Clutter.ActorAlign.END : Clutter.ActorAlign.START, + y_align: this._isHorizontal ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START, + y_expand: !this._isHorizontal, + x_expand: this._isHorizontal, + }); + this._box._delegate = this; + this._boxContainer.add_child(this._box); + Utils.addActor(this._scrollView, this._boxContainer); + this._dashContainer.add_child(this._scrollView); + + this._showAppsIcon = new AppIcons.DockShowAppsIcon(this._position); + this._showAppsIcon.show(false); + this._showAppsIcon.icon.setIconSize(this.iconSize); + this._showAppsIcon.x_expand = false; + this._showAppsIcon.y_expand = false; + this.showAppsButton.connect('notify::hover', a => { + if (this._showAppsIcon.get_parent() === this._boxContainer) + this._ensureItemVisibility(a); + }); + if (!this._isHorizontal) + this._showAppsIcon.y_align = Clutter.ActorAlign.START; + this._hookUpLabel(this._showAppsIcon); + this._showAppsIcon.connect('menu-state-changed', (_icon, opened) => { + this._itemMenuStateChanged(this._showAppsIcon, opened); + }); + this.updateShowAppsButton(); + + this._background = new St.Widget({ + style_class: 'dash-background', + y_expand: this._isHorizontal, + x_expand: !this._isHorizontal, + }); + + const sizerBox = new Clutter.Actor(); + sizerBox.add_constraint(new Clutter.BindConstraint({ + source: this._isHorizontal ? this._showAppsIcon.icon : this._dashContainer, + coordinate: Clutter.BindCoordinate.HEIGHT, + })); + sizerBox.add_constraint(new Clutter.BindConstraint({ + source: this._isHorizontal ? this._dashContainer : this._showAppsIcon.icon, + coordinate: Clutter.BindCoordinate.WIDTH, + })); + this._background.add_child(sizerBox); + + this.add_child(this._background); + this.add_child(this._dashContainer); + + this._workId = Main.initializeDeferredWork(this._box, this._redisplay.bind(this)); + + this._shellSettings = new Gio.Settings({ + schema_id: 'org.gnome.shell', + }); + + this._appSystem = Shell.AppSystem.get_default(); + + this.iconAnimator = new Docking.IconAnimator(this); + + this._signalsHandler.add([ + this._appSystem, + 'installed-changed', + () => { + AppFavorites.getAppFavorites().reload(); + this._queueRedisplay(); + }, + ], [ + AppFavorites.getAppFavorites(), + 'changed', + this._queueRedisplay.bind(this), + ], [ + this._appSystem, + 'app-state-changed', + this._queueRedisplay.bind(this), + ], [ + Main.overview, + 'item-drag-begin', + this._onItemDragBegin.bind(this), + ], [ + Main.overview, + 'item-drag-end', + this._onItemDragEnd.bind(this), + ], [ + Main.overview, + 'item-drag-cancelled', + this._onItemDragCancelled.bind(this), + ], [ + Main.overview, + 'window-drag-begin', + this._onWindowDragBegin.bind(this), + ], [ + Main.overview, + 'window-drag-cancelled', + this._onWindowDragEnd.bind(this), + ], [ + Main.overview, + 'window-drag-end', + this._onWindowDragEnd.bind(this), + ]); + + this.connect('destroy', this._onDestroy.bind(this)); + } + + vfunc_get_preferred_height(forWidth) { + const [minHeight, natHeight] = super.vfunc_get_preferred_height.call(this, forWidth); + if (!this._isHorizontal && this._maxHeight !== -1 && natHeight > this._maxHeight) + return [minHeight, this._maxHeight]; + else + return [minHeight, natHeight]; + } + + vfunc_get_preferred_width(forHeight) { + const [minWidth, natWidth] = super.vfunc_get_preferred_width.call(this, forHeight); + if (this._isHorizontal && this._maxWidth !== -1 && natWidth > this._maxWidth) + return [minWidth, this._maxWidth]; + else + return [minWidth, natWidth]; + } + + get _container() { + return this._dashContainer; + } + + _onDestroy() { + this.iconAnimator.destroy(); + + if (this._requiresVisibilityTimeout) { + GLib.source_remove(this._requiresVisibilityTimeout); + delete this._requiresVisibilityTimeout; + } + + if (this._ensureActorVisibilityTimeoutId) { + GLib.source_remove(this._ensureActorVisibilityTimeoutId); + delete this._ensureActorVisibilityTimeoutId; + } + } + + + _onItemDragBegin(...args) { + return Dash.Dash.prototype._onItemDragBegin.call(this, ...args); + } + + _onItemDragCancelled(...args) { + return Dash.Dash.prototype._onItemDragCancelled.call(this, ...args); + } + + _onItemDragEnd(...args) { + return Dash.Dash.prototype._onItemDragEnd.call(this, ...args); + } + + _endItemDrag(...args) { + return Dash.Dash.prototype._endItemDrag.call(this, ...args); + } + + _onItemDragMotion(...args) { + return Dash.Dash.prototype._onItemDragMotion.call(this, ...args); + } + + _appIdListToHash(...args) { + return Dash.Dash.prototype._appIdListToHash.call(this, ...args); + } + + _queueRedisplay(...args) { + return Dash.Dash.prototype._queueRedisplay.call(this, ...args); + } + + _hookUpLabel(...args) { + return Dash.Dash.prototype._hookUpLabel.call(this, ...args); + } + + _syncLabel(...args) { + return Dash.Dash.prototype._syncLabel.call(this, ...args); + } + + _clearDragPlaceholder(...args) { + return Dash.Dash.prototype._clearDragPlaceholder.call(this, ...args); + } + + _clearEmptyDropTarget(...args) { + return Dash.Dash.prototype._clearEmptyDropTarget.call(this, ...args); + } + + handleDragOver(source, actor, x, y, time) { + let ret; + if (this._isHorizontal) { + ret = Dash.Dash.prototype.handleDragOver.call(this, source, actor, x, y, time); + + if (ret === DND.DragMotionResult.CONTINUE) + return ret; + } else { + const propertyInjections = new Utils.PropertyInjectionsHandler(); + propertyInjections.add(this._box, 'width', { + get: () => this._box.get_children().reduce((a, c) => a + c.height, 0), + }); + + if (this._dragPlaceholder) { + propertyInjections.add(this._dragPlaceholder, 'width', { + get: () => this._dragPlaceholder.height, + }); + } + + ret = Dash.Dash.prototype.handleDragOver.call(this, source, actor, y, x, time); + propertyInjections.destroy(); + + if (ret === DND.DragMotionResult.CONTINUE) + return ret; + + if (this._dragPlaceholder) { + this._dragPlaceholder.child.set_width(this.iconSize / 2); + this._dragPlaceholder.child.set_height(this.iconSize); + + let pos = this._dragPlaceholderPos; + if (this._isHorizontal && + Clutter.get_default_text_direction() === Clutter.TextDirection.RTL) + pos = this._box.get_children() - 1 - pos; + + if (pos !== this._dragPlaceholderPos) { + this._dragPlaceholderPos = pos; + this._box.set_child_at_index(this._dragPlaceholder, + this._dragPlaceholderPos); + } + } + } + + if (this._dragPlaceholder) { + // Ensure the next and previous icon are visible when moving the + // placeholder (we're assuming there's room for both of them) + const children = this._box.get_children(); + if (this._dragPlaceholderPos > 0) { + ensureActorVisibleInScrollView(this._scrollView, + children[this._dragPlaceholderPos - 1]); + } + + if (this._dragPlaceholderPos >= -1 && + this._dragPlaceholderPos < children.length - 1) { + ensureActorVisibleInScrollView(this._scrollView, + children[this._dragPlaceholderPos + 1]); + } + } + + return ret; + } + + acceptDrop(...args) { + return Dash.Dash.prototype.acceptDrop.call(this, ...args); + } + + _onWindowDragBegin(...args) { + return Dash.Dash.prototype._onWindowDragBegin.call(this, ...args); + } + + _onWindowDragEnd(...args) { + return Dash.Dash.prototype._onWindowDragEnd.call(this, ...args); + } + + _onScrollEvent(actor, event) { + // If scroll is not used because the icon is resized, let the scroll event propagate. + if (!Docking.DockManager.settings.iconSizeFixed) + return Clutter.EVENT_PROPAGATE; + + // reset timeout to avid conflicts with the mousehover event + this._ensureItemVisibility(null); + + // Skip to avoid double events mouse + if (event.get_scroll_direction() !== Clutter.ScrollDirection.SMOOTH) + return Clutter.EVENT_STOP; + + + let adjustment, delta = 0; + + if (this._isHorizontal) { + adjustment = this._scrollView.get_hadjustment + ? this._scrollView.get_hadjustment() + : this._scrollView.get_hscroll_bar().get_adjustment(); + } else { + adjustment = this._scrollView.get_vadjustment + ? this._scrollView.get_vadjustment() + : this._scrollView.get_vscroll_bar().get_adjustment(); + } + + const increment = adjustment.step_increment; + const [dx, dy] = event.get_scroll_delta(); + + if (this._isHorizontal) + delta = (Math.abs(dx) > Math.abs(dy) ? dx : dy) * increment; + else + delta = dy * increment; + + const value = adjustment.get_value(); + + // TODO: Remove this if possible. + if (Number.isNaN(value)) + adjustment.set_value(delta); + else + adjustment.set_value(value + delta); + + return Clutter.EVENT_STOP; + } + + _ensureItemVisibility(actor) { + if (actor?.hover) { + const destroyId = + actor.connect('destroy', () => this._ensureItemVisibility(null)); + this._ensureActorVisibilityTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, 100, () => { + actor.disconnect(destroyId); + ensureActorVisibleInScrollView(this._scrollView, actor); + this._ensureActorVisibilityTimeoutId = 0; + return GLib.SOURCE_REMOVE; + }); + } else if (this._ensureActorVisibilityTimeoutId) { + GLib.source_remove(this._ensureActorVisibilityTimeoutId); + this._ensureActorVisibilityTimeoutId = 0; + } + } + + _createAppItem(app) { + const appIcon = new AppIcons.makeAppIcon(app, this._monitorIndex, this.iconAnimator); + + if (appIcon._draggable) { + appIcon._draggable.connect('drag-begin', () => { + appIcon.opacity = 50; + }); + appIcon._draggable.connect('drag-end', () => { + appIcon.opacity = 255; + }); + } + + appIcon.connectObject('menu-state-changed', (_, opened) => { + this._itemMenuStateChanged(item, opened); + }, this); + + const item = new DockDashItemContainer(this._position); + item.setChild(appIcon); + + appIcon.connectObject('notify::hover', a => this._ensureItemVisibility(a), this); + appIcon.connectObject('clicked', actor => { + ensureActorVisibleInScrollView(this._scrollView, actor); + }, this); + + appIcon.connectObject('key-focus-in', actor => { + const [xShift, yShift] = ensureActorVisibleInScrollView(this._scrollView, actor); + + // This signal is triggered also by mouse click. The popup menu is opened at the original + // coordinates. Thus correct for the shift which is going to be applied to the scrollview. + if (appIcon._menu) { + appIcon._menu._boxPointer.xOffset = -xShift; + appIcon._menu._boxPointer.yOffset = -yShift; + } + }, this); + + appIcon.connectObject('notify::focused', () => { + const {settings} = Docking.DockManager; + if (appIcon.focused && settings.scrollToFocusedApplication) + ensureActorVisibleInScrollView(this._scrollView, item); + }, this); + + appIcon.connectObject('notify::urgent', () => { + if (appIcon.urgent) { + ensureActorVisibleInScrollView(this._scrollView, item); + if (Docking.DockManager.settings.showDockUrgentNotify) + this._requireVisibility(); + } + }, this); + + // Override default AppIcon label_actor, now the + // accessible_name is set at DashItemContainer.setLabelText + appIcon.label_actor = null; + item.setLabelText(app.get_name()); + + appIcon.icon.setIconSize(this.iconSize); + this._hookUpLabel(item, appIcon); + + item.connectObject('notify::position', () => appIcon.updateIconGeometry(), appIcon); + item.connectObject('notify::size', () => appIcon.updateIconGeometry(), appIcon); + + return item; + } + + _requireVisibility() { + this.requiresVisibility = true; + + if (this._requiresVisibilityTimeout) + GLib.source_remove(this._requiresVisibilityTimeout); + + this._requiresVisibilityTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, + DASH_VISIBILITY_TIMEOUT, () => { + this._requiresVisibilityTimeout = 0; + this.requiresVisibility = false; + }); + } + + /** + * Return an array with the "proper" appIcons currently in the dash + */ + getAppIcons() { + // Only consider children which are "proper" + // icons (i.e. ignoring drag placeholders) and which are not + // animating out (which means they will be destroyed at the end of + // the animation) + const iconChildren = this._box.get_children().filter(actor => { + return actor.child && + !!actor.child.icon && + !actor.animatingOut; + }); + + const appIcons = iconChildren.map(actor => { + return actor.child; + }); + + return appIcons; + } + + _itemMenuStateChanged(item, opened) { + Dash.Dash.prototype._itemMenuStateChanged.call(this, item, opened); + + if (opened) { + this.emit('menu-opened'); + } else { + // I want to listen from outside when a menu is closed. I used to + // add a custom signal to the appIcon, since gnome 3.8 the signal + // calling this callback was added upstream. + this.emit('menu-closed'); + } + } + + _adjustIconSize() { + // For the icon size, we only consider children which are "proper" + // icons (i.e. ignoring drag placeholders) and which are not + // animating out (which means they will be destroyed at the end of + // the animation) + const iconChildren = this._box.get_children().filter(actor => { + return actor.child && + actor.child._delegate && + actor.child._delegate.icon && + !actor.animatingOut; + }); + + iconChildren.push(this._showAppsIcon); + + if (this._maxWidth === -1 && this._maxHeight === -1) + return; + + // Check if the container is present in the stage. This avoids critical + // errors when unlocking the screen + if (!this._container.get_stage()) + return; + + const themeNode = this._dashContainer.get_theme_node(); + const maxAllocation = new Clutter.ActorBox({ + x1: 0, + y1: 0, + x2: this._isHorizontal ? this._maxWidth : 42 /* whatever */, + y2: this._isHorizontal ? 42 : this._maxHeight, + }); + const maxContent = themeNode.get_content_box(maxAllocation); + let availSpace; + if (this._isHorizontal) + availSpace = maxContent.get_width(); + else + availSpace = maxContent.get_height(); + + const spacing = themeNode.get_length('spacing'); + + const [{child: firstButton}] = iconChildren; + const {child: firstIcon} = firstButton?.icon ?? {child: null}; + + // if no icons there's nothing to adjust + if (!firstIcon) + return; + + // Enforce valid spacings during the size request + firstIcon.ensure_style(); + const [, , iconWidth, iconHeight] = firstIcon.get_preferred_size(); + const [, , buttonWidth, buttonHeight] = firstButton.get_preferred_size(); + + if (this._isHorizontal) { + // Subtract icon padding and box spacing from the available width + availSpace -= iconChildren.length * (buttonWidth - iconWidth) + + (iconChildren.length - 1) * spacing; + + if (this._separator) { + const [, , separatorWidth] = this._separator.get_preferred_size(); + availSpace -= separatorWidth + spacing; + } + } else { + // Subtract icon padding and box spacing from the available height + availSpace -= iconChildren.length * (buttonHeight - iconHeight) + + (iconChildren.length - 1) * spacing; + + if (this._separator) { + const [, , , separatorHeight] = this._separator.get_preferred_size(); + availSpace -= separatorHeight + spacing; + } + } + + const maxIconSize = availSpace / iconChildren.length; + const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage); + const iconSizes = this._availableIconSizes.map(s => s * scaleFactor); + + let [newIconSize] = this._availableIconSizes; + for (let i = 0; i < iconSizes.length; i++) { + if (iconSizes[i] <= maxIconSize) + newIconSize = this._availableIconSizes[i]; + } + + if (newIconSize === this.iconSize) + return; + + const oldIconSize = this.iconSize; + this.iconSize = newIconSize; + this.emit('icon-size-changed'); + + const scale = oldIconSize / newIconSize; + for (let i = 0; i < iconChildren.length; i++) { + const {icon} = iconChildren[i].child._delegate; + + // Set the new size immediately, to keep the icons' sizes + // in sync with this.iconSize + icon.setIconSize(this.iconSize); + + // Don't animate the icon size change when the overview + // is transitioning, not visible or when initially filling + // the dash + if (!Main.overview.visible || Main.overview.animationInProgress || + !this._shownInitially) + continue; + + const [targetWidth, targetHeight] = icon.icon.get_size(); + + // Scale the icon's texture to the previous size and + // tween to the new size + icon.icon.set_size(icon.icon.width * scale, + icon.icon.height * scale); + + icon.icon.ease({ + width: targetWidth, + height: targetHeight, + duration: DASH_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + + if (this._separator) { + const animateProperties = this._isHorizontal + ? {height: this.iconSize} : {width: this.iconSize}; + + this._separator.ease({ + ...animateProperties, + duration: DASH_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + } + + _redisplay() { + const favorites = AppFavorites.getAppFavorites().getFavoriteMap(); + + let running = this._appSystem.get_running(); + const dockManager = Docking.DockManager.getDefault(); + const {settings} = dockManager; + + this._scrollView.set({ + xAlign: Clutter.ActorAlign.FILL, + yAlign: Clutter.ActorAlign.FILL, + }); + if (dockManager.settings.dockExtended) { + if (!this._isHorizontal) { + this._scrollView.yAlign = dockManager.settings.alwaysCenterIcons + ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START; + } else { + this._scrollView.xAlign = dockManager.settings.alwaysCenterIcons + ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START; + } + } + + if (settings.isolateWorkspaces || + settings.isolateMonitors) { + // When using isolation, we filter out apps that have no windows in + // the current workspace + const monitorIndex = this._monitorIndex; + running = running.filter(app => + AppIcons.getInterestingWindows(app.get_windows(), monitorIndex).length); + } + + const children = this._box.get_children().filter(actor => { + return actor.child && + actor.child._delegate && + actor.child._delegate.app; + }); + // Apps currently in the dash + let oldApps = children.map(actor => actor.child._delegate.app); + // Apps supposed to be in the dash + const newApps = []; + + const {showFavorites} = settings; + if (showFavorites) + newApps.push(...Object.values(favorites)); + + if (settings.showRunning) { + // We reorder the running apps so that they don't change position on the + // dash with every redisplay() call + + // First: add the apps from the oldApps list that are still running + oldApps.forEach(oldApp => { + const index = running.indexOf(oldApp); + if (index > -1) { + const [app] = running.splice(index, 1); + if (!showFavorites || !(app.get_id() in favorites)) + newApps.push(app); + } + }); + + // Second: add the new apps + running.forEach(app => { + if (!showFavorites || !(app.get_id() in favorites)) + newApps.push(app); + }); + } + + this._signalsHandler.removeWithLabel(Labels.SHOW_MOUNTS); + if (dockManager.removables) { + this._signalsHandler.addWithLabel(Labels.SHOW_MOUNTS, + dockManager.removables, 'changed', this._queueRedisplay.bind(this)); + dockManager.removables.getApps().forEach(removable => { + if (!newApps.includes(removable)) + newApps.push(removable); + }); + } else { + oldApps = oldApps.filter(app => !app.location || app.isTrash); + } + + if (dockManager.trash) { + const trashApp = dockManager.trash.getApp(); + if (!newApps.includes(trashApp)) + newApps.push(trashApp); + } else { + oldApps = oldApps.filter(app => !app.isTrash); + } + + // Temporary remove the separator so that we don't compute to position icons + const oldSeparatorPos = this._box.get_children().indexOf(this._separator); + if (this._separator) + this._box.remove_child(this._separator); + + // Figure out the actual changes to the list of items; we iterate + // over both the list of items currently in the dash and the list + // of items expected there, and collect additions and removals. + // Moves are both an addition and a removal, where the order of + // the operations depends on whether we encounter the position + // where the item has been added first or the one from where it + // was removed. + // There is an assumption that only one item is moved at a given + // time; when moving several items at once, everything will still + // end up at the right position, but there might be additional + // additions/removals (e.g. it might remove all the launchers + // and add them back in the new order even if a smaller set of + // additions and removals is possible). + // If above assumptions turns out to be a problem, we might need + // to use a more sophisticated algorithm, e.g. Longest Common + // Subsequence as used by diff. + + const addedItems = []; + const removedActors = []; + + let newIndex = 0; + let oldIndex = 0; + while (newIndex < newApps.length || oldIndex < oldApps.length) { + const oldApp = oldApps.length > oldIndex ? oldApps[oldIndex] : null; + const newApp = newApps.length > newIndex ? newApps[newIndex] : null; + + // No change at oldIndex/newIndex + if (oldApp === newApp) { + oldIndex++; + newIndex++; + continue; + } + + // App removed at oldIndex + if (oldApp && !newApps.includes(oldApp)) { + removedActors.push(children[oldIndex]); + oldIndex++; + continue; + } + + // App added at newIndex + if (newApp && !oldApps.includes(newApp)) { + addedItems.push({ + app: newApp, + item: this._createAppItem(newApp), + pos: newIndex, + }); + newIndex++; + continue; + } + + // App moved + const nextApp = newApps.length > newIndex + 1 + ? newApps[newIndex + 1] : null; + const insertHere = nextApp && nextApp === oldApp; + const alreadyRemoved = removedActors.reduce((result, actor) => { + const removedApp = actor.child._delegate.app; + return result || removedApp === newApp; + }, false); + + if (insertHere || alreadyRemoved) { + const newItem = this._createAppItem(newApp); + addedItems.push({ + app: newApp, + item: newItem, + pos: newIndex + removedActors.length, + }); + newIndex++; + } else { + removedActors.push(children[oldIndex]); + oldIndex++; + } + } + + for (let i = 0; i < addedItems.length; i++) { + this._box.insert_child_at_index(addedItems[i].item, + addedItems[i].pos); + } + + for (let i = 0; i < removedActors.length; i++) { + const item = removedActors[i]; + + // Don't animate item removal when the overview is transitioning + // or hidden + if (!Main.overview.animationInProgress) + item.animateOutAndDestroy(); + else + item.destroy(); + } + + // Update separator + const nFavorites = Object.keys(favorites).length; + const nIcons = children.length + addedItems.length - removedActors.length; + if (nFavorites > 0 && nFavorites < nIcons) { + if (!this._separator) { + this._separator = new St.Widget({ + style_class: 'dash-separator', + x_align: this._isHorizontal + ? Clutter.ActorAlign.FILL : Clutter.ActorAlign.CENTER, + y_align: this._isHorizontal + ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.FILL, + width: this._isHorizontal ? -1 : this.iconSize, + height: this._isHorizontal ? this.iconSize : -1, + reactive: true, + track_hover: true, + }); + this._separator.connect('notify::hover', a => this._ensureItemVisibility(a)); + } + let pos = nFavorites + this._animatingPlaceholdersCount; + if (this._dragPlaceholder) + pos++; + const removedFavorites = removedActors.filter(a => + children.indexOf(a) < oldSeparatorPos); + pos += removedFavorites.length; + this._box.insert_child_at_index(this._separator, pos); + } else if (this._separator) { + this._separator.destroy(); + this._separator = null; + } + + this._adjustIconSize(); + + // Skip animations on first run when adding the initial set + // of items, to avoid all items zooming in at once + const animate = this._shownInitially && + !Main.layoutManager._startingUp; + + if (!this._shownInitially) + this._shownInitially = true; + + addedItems.forEach(({item}) => item.show(animate)); + + // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 + // Without it, StBoxLayout may use a stale size cache + this._box.queue_relayout(); + + // This will update the size, and the corresponding number for each icon + this._updateNumberOverlay(); + + this.updateShowAppsButton(); + } + + _updateNumberOverlay() { + const appIcons = this.getAppIcons(); + let counter = 1; + appIcons.forEach(icon => { + if (counter < 10) { + icon.setNumberOverlay(counter); + counter++; + } else if (counter === 10) { + icon.setNumberOverlay(0); + counter++; + } else { + // No overlay after 10 + icon.setNumberOverlay(-1); + } + icon.updateNumberOverlay(); + }); + } + + toggleNumberOverlay(activate) { + const appIcons = this.getAppIcons(); + appIcons.forEach(icon => { + icon.toggleNumberOverlay(activate); + }); + } + + _initializeIconSize(maxSize) { + const maxAllowed = baseIconSizes[baseIconSizes.length - 1]; + maxSize = Math.min(maxSize, maxAllowed); + + if (Docking.DockManager.settings.iconSizeFixed) { + this._availableIconSizes = [maxSize]; + } else { + this._availableIconSizes = baseIconSizes.filter(val => { + return val < maxSize; + }); + this._availableIconSizes.push(maxSize); + } + } + + setIconSize(maxSize, doNotAnimate) { + this._initializeIconSize(maxSize); + + if (doNotAnimate) + this._shownInitially = false; + + this._queueRedisplay(); + } + + /** + * Reset the displayed apps icon to maintain the correct order when changing + * show favorites/show running settings + */ + resetAppIcons() { + const children = this._box.get_children().filter(actor => { + return actor.child && + !!actor.child.icon; + }); + for (let i = 0; i < children.length; i++) { + const item = children[i]; + item.destroy(); + } + + // to avoid ugly animations, just suppress them like when dash is first loaded. + this._shownInitially = false; + this._redisplay(); + } + + get showAppsButton() { + return this._showAppsIcon.toggleButton; + } + + showShowAppsButton() { + this._showAppsIcon.visible = true; + this._showAppsIcon.show(true); + this.updateShowAppsButton(); + } + + hideShowAppsButton() { + this._showAppsIcon.visible = false; + } + + setMaxSize(maxWidth, maxHeight) { + if (this._maxWidth === maxWidth && + this._maxHeight === maxHeight) + return; + + this._maxWidth = maxWidth; + this._maxHeight = maxHeight; + this._queueRedisplay(); + } + + updateShowAppsButton() { + if (this._showAppsIcon.get_parent() && !this._showAppsIcon.visible) + return; + + const {settings} = Docking.DockManager; + const notifiedProperties = []; + const showAppsContainer = settings.showAppsAlwaysInTheEdge || !settings.dockExtended + ? this._dashContainer : this._boxContainer; + + this._signalsHandler.addWithLabel(Labels.FIRST_LAST_CHILD_WORKAROUND, + showAppsContainer, 'notify', + (_obj, pspec) => notifiedProperties.push(pspec.name)); + + if (this._showAppsIcon.get_parent() !== showAppsContainer) { + this._showAppsIcon.get_parent()?.remove_child(this._showAppsIcon); + + if (Docking.DockManager.settings.showAppsAtTop) + showAppsContainer.insert_child_below(this._showAppsIcon, null); + else + showAppsContainer.insert_child_above(this._showAppsIcon, null); + } else if (settings.showAppsAtTop) { + showAppsContainer.set_child_below_sibling(this._showAppsIcon, null); + } else { + showAppsContainer.set_child_above_sibling(this._showAppsIcon, null); + } + + this._signalsHandler.removeWithLabel(Labels.FIRST_LAST_CHILD_WORKAROUND); + + // This is indeed ugly, but we need to ensure that the last and first + // visible widgets are re-computed by St, that is buggy because of a + // mutter issue that is being fixed: + // https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/2047 + if (!notifiedProperties.includes('first-child')) + showAppsContainer.notify('first-child'); + if (!notifiedProperties.includes('last-child')) + showAppsContainer.notify('last-child'); + } +}); + + +/** + * This is a copy of the same function in utils.js, but also adjust horizontal scrolling + * and perform few further checks on the current value to avoid changing the values when + * it would be clamp to the current one in any case. + * Return the amount of shift applied + * + * @param scrollView + * @param actor + */ +function ensureActorVisibleInScrollView(scrollView, actor) { + // access to scrollView.[hv]scroll was deprecated in gnome 46 + // instead, adjustment can be accessed directly + // keep old way for backwards compatibility (gnome <= 45) + const vAdjustment = scrollView.vadjustment ?? scrollView.vscroll.adjustment; + const hAdjustment = scrollView.hadjustment ?? scrollView.hscroll.adjustment; + const {value: vValue0, pageSize: vPageSize, upper: vUpper} = vAdjustment; + const {value: hValue0, pageSize: hPageSize, upper: hUpper} = hAdjustment; + let [hValue, vValue] = [hValue0, vValue0]; + let vOffset = 0; + let hOffset = 0; + + const fade = scrollView.get_effect('fade'); + if (fade) { + vOffset = fade.fade_margins.top; + hOffset = fade.fade_margins.left; + } + + const box = actor.get_allocation_box(); + let {y1} = box, {y2} = box, {x1} = box, {x2} = box; + + let parent = actor.get_parent(); + while (parent !== scrollView) { + if (!parent) + throw new Error('Actor not in scroll view'); + + const parentBox = parent.get_allocation_box(); + y1 += parentBox.y1; + y2 += parentBox.y1; + x1 += parentBox.x1; + x2 += parentBox.x1; + parent = parent.get_parent(); + } + + if (y1 < vValue + vOffset) + vValue = Math.max(0, y1 - vOffset); + else if (vValue < vUpper - vPageSize && y2 > vValue + vPageSize - vOffset) + vValue = Math.min(vUpper - vPageSize, y2 + vOffset - vPageSize); + + if (x1 < hValue + hOffset) + hValue = Math.max(0, x1 - hOffset); + else if (hValue < hUpper - hPageSize && x2 > hValue + hPageSize - hOffset) + hValue = Math.min(hUpper - hPageSize, x2 + hOffset - hPageSize); + + if (vValue !== vValue0) { + vAdjustment.ease(vValue, { + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + duration: Util.SCROLL_TIME, + }); + } + + if (hValue !== hValue0) { + hAdjustment.ease(hValue, { + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + duration: Util.SCROLL_TIME, + }); + } + + return [hValue - hValue0, vValue - vValue0]; +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js new file mode 100644 index 0000000..d992b1a --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dbusmenuUtils.js @@ -0,0 +1,280 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import { + Atk, + Clutter, + Gio, + GLib, + St, +} from './dependencies/gi.js'; + +import {PopupMenu} from './dependencies/shell/ui.js'; + +import {Utils} from './imports.js'; + +// Dbusmenu features not (yet) supported: +// +// * The CHILD_DISPLAY property +// +// This seems to have only one possible value in the Dbusmenu API, so +// there's little point in depending on it--the code in libdbusmenu sets it +// if and only if an item has children, so for our purposes it's simpler +// and more intuitive to just check children.length. (This does ignore the +// possibility of a program not using libdbusmenu and setting CHILD_DISPLAY +// independently, perhaps to indicate that an childless menu item should +// nevertheless be displayed like a submenu.) +// +// * Children more than two levels deep +// +// PopupMenu doesn't seem to support submenus in submenus. +// +// * Shortcut keys +// +// If these keys are supposed to be installed as global shortcuts, we'd +// have to query these aggressively and not wait for the DBus menu to be +// mapped to a popup menu. A shortcut key that only works once the popup +// menu is open and has key focus is possibly of marginal value. + +/** + * + */ +export async function haveDBusMenu() { + try { + const {default: DBusMenu} = await import('gi://Dbusmenu'); + return DBusMenu; + } catch (e) { + log(`Failed to import DBusMenu, quicklists are not available: ${e}`); + return null; + } +} + +const DBusMenu = await haveDBusMenu(); + +/** + * @param dbusmenuItem + * @param deep + */ +export function makePopupMenuItem(dbusmenuItem, deep) { + // These are the only properties guaranteed to be available when the root + // item is first announced. Other properties might be loaded already, but + // be sure to connect to Dbusmenu.MENUITEM_SIGNAL_PROPERTY_CHANGED to get + // the most up-to-date values in case they aren't. + const itemType = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_TYPE); + const label = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_LABEL); + const visible = dbusmenuItem.property_get_bool(DBusMenu.MENUITEM_PROP_VISIBLE); + const enabled = dbusmenuItem.property_get_bool(DBusMenu.MENUITEM_PROP_ENABLED); + const accessibleDesc = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_ACCESSIBLE_DESC); + // const childDisplay = dbusmenuItem.property_get(Dbusmenu.MENUITEM_PROP_CHILD_DISPLAY); + + let item; + const signalsHandler = new Utils.GlobalSignalsHandler(); + const wantIcon = itemType === DBusMenu.CLIENT_TYPES_IMAGE; + + // If the basic type of the menu item needs to change, call this. + const recreateItem = () => { + const newItem = makePopupMenuItem(dbusmenuItem, deep); + const parentMenu = item._parent; + parentMenu.addMenuItem(newItem); + // Reminder: Clutter thinks of later entries in the child list as + // "above" earlier ones, so "above" here means "below" in terms of the + // menu's vertical order. + parentMenu.actor.set_child_above_sibling(newItem.actor, item.actor); + if (newItem.menu) + parentMenu.actor.set_child_above_sibling(newItem.menu.actor, newItem.actor); + + parentMenu.actor.remove_child(item.actor); + item.destroy(); + item = null; + }; + + const updateDisposition = () => { + const disposition = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_DISPOSITION); + let iconName = null; + switch (disposition) { + case DBusMenu.MENUITEM_DISPOSITION_ALERT: + case DBusMenu.MENUITEM_DISPOSITION_WARNING: + iconName = 'dialog-warning-symbolic'; + break; + case DBusMenu.MENUITEM_DISPOSITION_INFORMATIVE: + iconName = 'dialog-information-symbolic'; + break; + } + if (iconName) { + item._dispositionIcon = new St.Icon({ + icon_name: iconName, + style_class: 'popup-menu-icon', + y_align: Clutter.ActorAlign.CENTER, + y_expand: true, + }); + let expander; + for (let child = item.label.get_next_sibling(); ; child = child.get_next_sibling()) { + if (!child) { + expander = new St.Bin({ + style_class: 'popup-menu-item-expander', + x_expand: true, + }); + item.actor.add_child(expander); + break; + } else if (child instanceof St.Widget && + child.has_style_class_name('popup-menu-item-expander')) { + expander = child; + break; + } + } + item.actor.insert_child_above(item._dispositionIcon, expander); + } else if (item._dispositionIcon) { + item.actor.remove_child(item._dispositionIcon); + item._dispositionIcon = null; + } + }; + + const updateIcon = () => { + if (!wantIcon) + return; + + const iconData = dbusmenuItem.property_get_byte_array(DBusMenu.MENUITEM_PROP_ICON_DATA); + const iconName = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_ICON_NAME); + if (iconName) + item.icon.icon_name = iconName; + else if (iconData.length) + item.icon.gicon = Gio.BytesIcon.new(iconData); + }; + + const updateOrnament = () => { + const toggleType = dbusmenuItem.property_get(DBusMenu.MENUITEM_PROP_TOGGLE_TYPE); + switch (toggleType) { + case DBusMenu.MENUITEM_TOGGLE_CHECK: + item.actor.accessible_role = Atk.Role.CHECK_MENU_ITEM; + break; + case DBusMenu.MENUITEM_TOGGLE_RADIO: + item.actor.accessible_role = Atk.Role.RADIO_MENU_ITEM; + break; + default: + item.actor.accessible_role = Atk.Role.MENU_ITEM; + } + let ornament = PopupMenu.Ornament.NONE; + const state = dbusmenuItem.property_get_int(DBusMenu.MENUITEM_PROP_TOGGLE_STATE); + if (state === DBusMenu.MENUITEM_TOGGLE_STATE_UNKNOWN) { + // PopupMenu doesn't natively support an "unknown" ornament, but we + // can hack one in: + item.setOrnament(ornament); + item.actor.add_accessible_state(Atk.StateType.INDETERMINATE); + item._ornamentLabel.text = '\u2501'; + item.actor.remove_style_pseudo_class('checked'); + } else { + item.actor.remove_accessible_state(Atk.StateType.INDETERMINATE); + if (state === DBusMenu.MENUITEM_TOGGLE_STATE_CHECKED) { + if (toggleType === DBusMenu.MENUITEM_TOGGLE_CHECK) + ornament = PopupMenu.Ornament.CHECK; + else if (toggleType === DBusMenu.MENUITEM_TOGGLE_RADIO) + ornament = PopupMenu.Ornament.DOT; + + item.actor.add_style_pseudo_class('checked'); + } else { + item.actor.remove_style_pseudo_class('checked'); + } + item.setOrnament(ornament); + } + }; + + const onPropertyChanged = (_, name, value) => { + // `value` is null when a property is cleared, so handle those cases + // with sensible defaults. + switch (name) { + case DBusMenu.MENUITEM_PROP_TYPE: + recreateItem(); + break; + case DBusMenu.MENUITEM_PROP_ENABLED: + item.setSensitive(value ? value.unpack() : false); + break; + case DBusMenu.MENUITEM_PROP_LABEL: + item.label.text = value ? value.unpack() : ''; + break; + case DBusMenu.MENUITEM_PROP_VISIBLE: + item.actor.visible = value ? value.unpack() : false; + break; + case DBusMenu.MENUITEM_PROP_DISPOSITION: + updateDisposition(); + break; + case DBusMenu.MENUITEM_PROP_ACCESSIBLE_DESC: + item.actor.get_accessible().accessible_description = value && value.unpack() || ''; + break; + case DBusMenu.MENUITEM_PROP_ICON_DATA: + case DBusMenu.MENUITEM_PROP_ICON_NAME: + updateIcon(); + break; + case DBusMenu.MENUITEM_PROP_TOGGLE_TYPE: + case DBusMenu.MENUITEM_PROP_TOGGLE_STATE: + updateOrnament(); + break; + } + }; + + + // Start actually building the menu item. + const children = dbusmenuItem.get_children(); + if (children.length && !deep) { + // Make a submenu. + item = new PopupMenu.PopupSubMenuMenuItem(label, wantIcon); + const updateChildren = () => { + const itemChildren = dbusmenuItem.get_children(); + if (!itemChildren.length) { + recreateItem(); + return; + } + + item.menu.removeAll(); + itemChildren.forEach(remoteChild => + item.menu.addMenuItem(makePopupMenuItem(remoteChild, true))); + }; + updateChildren(); + signalsHandler.add( + [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_ADDED, updateChildren], + [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_MOVED, updateChildren], + [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_REMOVED, updateChildren]); + } else { + // Don't make a submenu. + if (!deep) { + // We only have the potential to get a submenu if we aren't deep. + signalsHandler.add( + [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_ADDED, recreateItem], + [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_MOVED, recreateItem], + [dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_CHILD_REMOVED, recreateItem]); + } + + if (itemType === DBusMenu.CLIENT_TYPES_SEPARATOR) { + item = new PopupMenu.PopupSeparatorMenuItem(); + } else if (wantIcon) { + item = new PopupMenu.PopupImageMenuItem(label, null); + item.icon = item._icon; + } else { + item = new PopupMenu.PopupMenuItem(label); + } + } + + // Set common initial properties. + item.actor.visible = visible; + item.setSensitive(enabled); + if (accessibleDesc) + item.actor.get_accessible().accessible_description = accessibleDesc; + + updateDisposition(); + updateIcon(); + updateOrnament(); + + // Prevent an initial resize flicker. + if (wantIcon) + item.icon.icon_size = 16; + + + signalsHandler.add(dbusmenuItem, DBusMenu.MENUITEM_SIGNAL_PROPERTY_CHANGED, onPropertyChanged); + + // Connections on item will be lost when item is disposed; there's no need + // to add them to signalsHandler. + item.connect('activate', () => + dbusmenuItem.handle_event(DBusMenu.MENUITEM_EVENT_ACTIVATED, + new GLib.Variant('i', 0), Math.floor(Date.now() / 1000))); + item.connect('destroy', () => signalsHandler.destroy()); + + return item; +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/gi.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/gi.js new file mode 100644 index 0000000..c4b5385 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/gi.js @@ -0,0 +1,14 @@ +export {default as Atk} from 'gi://Atk'; +export {default as Clutter} from 'gi://Clutter'; +export {default as Cogl} from 'gi://Cogl'; +export {default as GLib} from 'gi://GLib'; +export {default as GObject} from 'gi://GObject'; +export {default as GdkPixbuf} from 'gi://GdkPixbuf'; +export {default as Gio} from 'gi://Gio'; +export {default as GioUnix} from 'gi://GioUnix'; +export {default as Meta} from 'gi://Meta'; +export {default as Mtk} from 'gi://Mtk'; +export {default as Pango} from 'gi://Pango'; +export {default as Shell} from 'gi://Shell'; +export {default as St} from 'gi://St'; + diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/extensions/extension.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/extensions/extension.js new file mode 100644 index 0000000..bd9db98 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/extensions/extension.js @@ -0,0 +1 @@ +export * as Extension from 'resource:///org/gnome/shell/extensions/extension.js'; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/misc.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/misc.js new file mode 100644 index 0000000..cfd4b95 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/misc.js @@ -0,0 +1,5 @@ +export * as AnimationUtils from 'resource:///org/gnome/shell/misc/animationUtils.js'; +export * as Config from 'resource:///org/gnome/shell/misc/config.js'; +export * as ExtensionUtils from 'resource:///org/gnome/shell/misc/extensionUtils.js'; +export * as ParentalControlsManager from 'resource:///org/gnome/shell/misc/parentalControlsManager.js'; +export * as Util from 'resource:///org/gnome/shell/misc/util.js'; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/ui.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/ui.js new file mode 100644 index 0000000..33f1082 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/dependencies/shell/ui.js @@ -0,0 +1,18 @@ +export * as AppDisplay from 'resource:///org/gnome/shell/ui/appDisplay.js'; +export * as AppMenu from 'resource:///org/gnome/shell/ui/appMenu.js'; +export * as AppFavorites from 'resource:///org/gnome/shell/ui/appFavorites.js'; +export * as BoxPointer from 'resource:///org/gnome/shell/ui/boxpointer.js'; +export * as DND from 'resource:///org/gnome/shell/ui/dnd.js'; +export * as Dash from 'resource:///org/gnome/shell/ui/dash.js'; +export * as Layout from 'resource:///org/gnome/shell/ui/layout.js'; +export * as Main from 'resource:///org/gnome/shell/ui/main.js'; +export * as Overview from 'resource:///org/gnome/shell/ui/overview.js'; +export * as OverviewControls from 'resource:///org/gnome/shell/ui/overviewControls.js'; +export * as PointerWatcher from 'resource:///org/gnome/shell/ui/pointerWatcher.js'; +export * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; +export * as SearchController from 'resource:///org/gnome/shell/ui/searchController.js'; +export * as ShellMountOperation from 'resource:///org/gnome/shell/ui/shellMountOperation.js'; +export * as Workspace from 'resource:///org/gnome/shell/ui/workspace.js'; +export * as WorkspaceSwitcherPopup from 'resource:///org/gnome/shell/ui/workspaceSwitcherPopup.js'; +export * as WorkspaceThumbnail from 'resource:///org/gnome/shell/ui/workspaceThumbnail.js'; +export * as WorkspacesView from 'resource:///org/gnome/shell/ui/workspacesView.js'; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/desktopIconsIntegration.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/desktopIconsIntegration.js new file mode 100644 index 0000000..ca07747 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/desktopIconsIntegration.js @@ -0,0 +1,170 @@ +/* + * The code in this file is distributed under a "1-clause BSD license", + * which makes it compatible with GPLv2 and GPLv3 too, and others. + * + * License text: + * + * Copyright (C) 2021 Sergio Costas (rastersoft@gmail.com) + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * Integration class + * + * This class must be added to other extensions in order to integrate + * them with Desktop Icons NG. It allows an extension to notify how much margin + * it uses in each side of each monitor. + * + * DON'T SEND PATCHES TO THIS FILE TO THE EXTENSION MAINTAINER. SEND THEM TO + * DESKTOP ICONS NG MAINTAINER: https://gitlab.com/rastersoft/desktop-icons-ng + * + * In the *enable()* function, create a *DesktopIconsUsableAreaClass()* + * object with + * + * new DesktopIconsIntegration.DesktopIconsUsableAreaClass(object); + * + * Now, in the *disable()* function just call to the *destroy()* method before + * nullifying the pointer. You must create a new object in enable() the next + * time the extension is enabled. + * + * In your code, every time you change the margins, you should call first to + * *resetMargins()* method to clear the current margins, and then call to + * *setMargins(...)* method as many times as you need to set the margins in each + * monitor. You don't need to call it for all the monitors, only for those where + * you are painting something. If you don't set values for a monitor, they will + * be considered zero. + * + * The margins values are relative to the monitor border. + * + *******************************************************************************/ + +import GLib from 'gi://GLib'; +import * as Main from 'resource:///org/gnome/shell/ui/main.js'; +import * as ExtensionUtils from 'resource:///org/gnome/shell/misc/extensionUtils.js'; +import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; + +const IDENTIFIER_UUID = '130cbc66-235c-4bd6-8571-98d2d8bba5e2'; + +export class DesktopIconsUsableAreaClass { + _checkIfExtensionIsEnabled(extension) { + return (extension?.state === ExtensionUtils.ExtensionState.ENABLED) || + (extension?.state === ExtensionUtils.ExtensionState.ACTIVE); + } + + constructor() { + const Me = Extension.lookupByURL(import.meta.url); + this._UUID = Me.uuid; + this._extensionManager = Main.extensionManager; + this._timedMarginsID = 0; + this._margins = {}; + this._emID = this._extensionManager.connect('extension-state-changed', (_obj, extension) => { + if (!extension) + return; + + // If an extension is being enabled and lacks the + // DesktopIconsUsableArea object, we can avoid launching a refresh + if (this._checkIfExtensionIsEnabled(extension)) { + this._sendMarginsToExtension(extension); + return; + } + // if the extension is being disabled, we must do a full refresh, + // because if there were other extensions originally + // loaded after that extension, those extensions will be disabled + // and enabled again without notification + this._changedMargins(); + }); + } + + /** + * Sets or updates the top, bottom, left and right margins for a + * monitor. Values are measured from the monitor border (and NOT from + * the workspace border). + * + * @param {int} monitor Monitor number to which set the margins. + * A negative value means "the primary monitor". + * @param {int} top Top margin in pixels + * @param {int} bottom Bottom margin in pixels + * @param {int} left Left margin in pixels + * @param {int} right Right margin in pixels + */ + setMargins(monitor, top, bottom, left, right) { + this._margins[monitor] = { + top, + bottom, + left, + right, + }; + this._changedMargins(); + } + + /** + * Clears the current margins. Must be called before configuring the monitors + * margins with setMargins(). + */ + resetMargins() { + this._margins = {}; + this._changedMargins(); + } + + /** + * Disconnects all the signals and removes the margins. + */ + destroy() { + if (this._emID) { + this._extensionManager.disconnect(this._emID); + this._emID = 0; + } + if (this._timedMarginsID) { + GLib.source_remove(this._timedMarginsID); + this._timedMarginsID = 0; + } + this._margins = null; + this._changedMargins(); + } + + _changedMargins() { + if (this._timedMarginsID) + GLib.source_remove(this._timedMarginsID); + + this._timedMarginsID = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => { + this._sendMarginsToAll(); + this._timedMarginsID = 0; + return GLib.SOURCE_REMOVE; + }); + } + + _sendMarginsToAll() { + this._extensionManager.getUuids().forEach(uuid => + this._sendMarginsToExtension(this._extensionManager.lookup(uuid))); + } + + _sendMarginsToExtension(extension) { + // check that the extension is an extension that has the logic to accept + // working margins + if (!this._checkIfExtensionIsEnabled(extension)) + return; + + const usableArea = extension?.stateObj?.DesktopIconsUsableArea; + if (usableArea?.uuid === IDENTIFIER_UUID) { + usableArea.setMarginsForExtension( + this._UUID, this._margins); + } + } +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/docking.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/docking.js new file mode 100644 index 0000000..393b1de --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/docking.js @@ -0,0 +1,2698 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import { + Clutter, + GLib, + Gio, + GObject, + Meta, + Shell, + St, +} from './dependencies/gi.js'; + +import { + AppMenu, + AppDisplay, + Layout, + Main, + OverviewControls, + PointerWatcher, + Workspace, + WorkspacesView, + WorkspaceSwitcherPopup, +} from './dependencies/shell/ui.js'; + +import { + AnimationUtils, +} from './dependencies/shell/misc.js'; + +import { + AppIconsDecorator, + AppSpread, + DockDash, + DesktopIconsIntegration, + FileManager1API, + Intellihide, + LauncherAPI, + Locations, + NotificationsMonitor, + Theming, + Utils, +} from './imports.js'; + +import {Extension} from './dependencies/shell/extensions/extension.js'; + +// Use __ () and N__() for the extension gettext domain, and reuse +// the shell domain with the default _() and N_() +const {gettext: __} = Extension; + +const {signals: Signals} = imports; + +const DOCK_DWELL_CHECK_INTERVAL = 100; +const ICON_ANIMATOR_DURATION = 3000; +const STARTUP_ANIMATION_TIME = 500; + +export const State = Object.freeze({ + HIDDEN: 0, + SHOWING: 1, + SHOWN: 2, + HIDING: 3, +}); + +const scrollAction = Object.freeze({ + DO_NOTHING: 0, + CYCLE_WINDOWS: 1, + SWITCH_WORKSPACE: 2, +}); + +const Labels = Object.freeze({ + INITIALIZE: Symbol('initialize'), + ISOLATION: Symbol('isolation'), + LOCATIONS: Symbol('locations'), + MAIN_DASH: Symbol('main-dash'), + OLD_DASH_CHANGES: Symbol('old-dash-changes'), + SETTINGS: Symbol('settings'), + STARTUP_ANIMATION: Symbol('startup-animation'), + WORKSPACE_SWITCH_SCROLL: Symbol('workspace-switch-scroll'), +}); + +/** + * A simple St.Widget with one child whose allocation takes into account the + * slide out of its child via the slide-x property ([0:1]). + * + * Required since I want to track the input region of this container which is + * based on its allocation even if the child overflows the parent actor. By doing + * this the region of the dash that is slide-out is not stealing anymore the input + * regions making the extension usable when the primary monitor is the right one. + * + * The slide-x parameter can be used to directly animate the sliding. The parent + * must have a WEST (SOUTH) anchor_point to achieve the sliding to the RIGHT (BOTTOM) + * side. + */ +const DashSlideContainer = GObject.registerClass({ + Properties: { + 'monitor-index': GObject.ParamSpec.uint( + 'monitor-index', 'monitor-index', 'monitor-index', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + 0, GLib.MAXUINT32, 0), + 'side': GObject.ParamSpec.enum( + 'side', 'side', 'side', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + St.Side, St.Side.LEFT), + 'slide-x': GObject.ParamSpec.double( + 'slide-x', 'slide-x', 'slide-x', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT, + 0, 1, 1), + }, +}, class DashSlideContainer extends St.Bin { + _init(params = {}) { + super._init(params); + + this._slideoutSize = 0; // minimum size when slided out + this.connect('notify::slide-x', () => this.queue_relayout()); + + if (this.side === St.Side.TOP && DockManager.settings.dockFixed) { + this._signalsHandler = new Utils.GlobalSignalsHandler(this); + this._signalsHandler.add(Main.panel, 'notify::height', + () => this.queue_relayout()); + } + } + + vfunc_allocate(box) { + const contentBox = this.get_theme_node().get_content_box(box); + + this.set_allocation(box); + + if (!this.child) + return; + + const availWidth = contentBox.x2 - contentBox.x1; + let availHeight = contentBox.y2 - contentBox.y1; + const [, , natChildWidth, natChildHeight] = + this.child.get_preferred_size(); + + const childWidth = natChildWidth; + const childHeight = natChildHeight; + + const childBox = new Clutter.ActorBox(); + + const slideoutSize = this._slideoutSize; + + if (this.side === St.Side.LEFT) { + childBox.x1 = (this.slideX - 1) * (childWidth - slideoutSize); + childBox.x2 = slideoutSize + this.slideX * (childWidth - slideoutSize); + childBox.y1 = 0; + childBox.y2 = childBox.y1 + childHeight; + } else if ((this.side === St.Side.RIGHT) || (this.side === St.Side.BOTTOM)) { + childBox.x1 = 0; + childBox.x2 = childWidth; + childBox.y1 = 0; + childBox.y2 = childBox.y1 + childHeight; + } else if (this.side === St.Side.TOP) { + const monitor = Main.layoutManager.monitors[this.monitorIndex]; + let yOffset = 0; + if (Main.panel.x === monitor.x && Main.panel.y === monitor.y && + DockManager.settings.dockFixed) + yOffset = Main.panel.height; + childBox.x1 = 0; + childBox.x2 = childWidth; + childBox.y1 = (this.slideX - 1) * (childHeight - slideoutSize) + yOffset; + childBox.y2 = slideoutSize + this.slideX * (childHeight - slideoutSize) + yOffset; + availHeight += yOffset; + } + + this.child.allocate(childBox); + + this.child.set_clip(-childBox.x1, -childBox.y1, + -childBox.x1 + availWidth, -childBox.y1 + availHeight); + } + + /** + * Just the child width but taking into account the slided out part + * + * @param forHeight + */ + vfunc_get_preferred_width(forHeight) { + let [minWidth, natWidth] = super.vfunc_get_preferred_width(forHeight || 0); + if ((this.side === St.Side.LEFT) || (this.side === St.Side.RIGHT)) { + minWidth = (minWidth - this._slideoutSize) * this.slideX + this._slideoutSize; + natWidth = (natWidth - this._slideoutSize) * this.slideX + this._slideoutSize; + } + return [minWidth, natWidth]; + } + + /** + * Just the child height but taking into account the slided out part + * + * @param forWidth + */ + vfunc_get_preferred_height(forWidth) { + let [minHeight, natHeight] = super.vfunc_get_preferred_height(forWidth || 0); + if ((this.side === St.Side.TOP) || (this.side === St.Side.BOTTOM)) { + minHeight = (minHeight - this._slideoutSize) * this.slideX + this._slideoutSize; + natHeight = (natHeight - this._slideoutSize) * this.slideX + this._slideoutSize; + + if (this.side === St.Side.TOP && DockManager.settings.dockFixed) { + const monitor = Main.layoutManager.monitors[this.monitorIndex]; + if (Main.panel.x === monitor.x && Main.panel.y === monitor.y) { + minHeight += Main.panel.height; + natHeight += Main.panel.height; + } + } + } + return [minHeight, natHeight]; + } +}); + +const DockedDash = GObject.registerClass({ + Properties: { + 'is-main': GObject.ParamSpec.boolean( + 'is-main', 'is-main', 'is-main', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + false), + 'monitor-index': GObject.ParamSpec.uint( + 'monitor-index', 'monitor-index', 'monitor-index', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + 0, GLib.MAXUINT32, 0), + }, + Signals: { + 'showing': {}, + 'hiding': {}, + }, +}, class DashToDock extends St.Bin { + _init(params) { + this._position = Utils.getPosition(); + + // This is the centering actor + super._init({ + ...params, + name: 'dashtodockContainer', + reactive: false, + style_class: Theming.PositionStyleClass[this._position], + }); + + if (this.monitorIndex === undefined) { + // Hello turkish locale, gjs has instead defined this.monitorIndex + // See: https://gitlab.gnome.org/GNOME/gjs/-/merge_requests/742 + this.monitorIndex = this.monitor_index; + } + + this._rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL; + + // Load settings + const {settings} = DockManager; + this._isHorizontal = (this._position === St.Side.TOP) || (this._position === St.Side.BOTTOM); + + // Temporary ignore hover events linked to autohide for whatever reason + this._ignoreHover = false; + this._oldIgnoreHover = null; + // This variables are linked to the settings regardles of autohide or intellihide + // being temporary disable. Get set by _updateVisibilityMode; + this._autohideIsEnabled = null; + this._intellihideIsEnabled = null; + + // This variable marks if _disableUnredirect() is called + // to help restore the original state when intelihide is disabled. + this._unredirectDisabled = false; + + // Create intellihide object to monitor windows overlapping + this._intellihide = new Intellihide.Intellihide(this.monitorIndex); + + // initialize dock state + this._dockState = State.HIDDEN; + + // Put dock on the required monitor + this._monitor = Main.layoutManager.monitors[this.monitorIndex]; + + // this store size and the position where the dash is shown; + // used by intellihide module to check window overlap. + this.staticBox = new Clutter.ActorBox(); + + // Initialize pressure barrier variables + this._canUsePressure = false; + this._pressureBarrier = null; + this._barrier = null; + this._removeBarrierTimeoutId = 0; + + // Initialize dwelling system variables + this._dockDwelling = false; + this._dockWatch = null; + this._dockDwellUserTime = 0; + this._dockDwellTimeoutId = 0; + + // Create a new dash object + this.dash = new DockDash.DockDash(this.monitorIndex); + + if (Main.overview.isDummy || !settings.showShowAppsButton) + this.dash.hideShowAppsButton(); + + // Create the containers for sliding in and out and + // centering, turn on track hover + // This is the sliding actor whose allocation is to be tracked for input regions + this._slider = new DashSlideContainer({ + monitor_index: this._monitor.index, + side: this._position, + slide_x: Main.layoutManager._startingUp ? 0 : 1, + ...this._isHorizontal ? { + x_align: Clutter.ActorAlign.CENTER, + } : { + y_align: Clutter.ActorAlign.CENTER, + }, + }); + + // This is the actor whose hover status us tracked for autohide + this._box = new St.BoxLayout({ + name: 'dashtodockBox', + reactive: true, + track_hover: true, + }); + this._box.connect('notify::hover', this._hoverChanged.bind(this)); + + // Connect global signals + this._signalsHandler = new Utils.GlobalSignalsHandler(this); + this._bindSettingsChanges(); + this._signalsHandler.add([ + // update when workarea changes, for instance if other extensions modify the struts + // (like moving th panel at the bottom) + global.display, + 'workareas-changed', + this._resetPosition.bind(this), + ], [ + global.display, + 'in-fullscreen-changed', + this._updateBarrier.bind(this), + ], [ + // Monitor windows overlapping + this._intellihide, + 'status-changed', + this._updateDashVisibility.bind(this), + ], [ + this.dash, + 'menu-opened', + () => { + this._onMenuOpened(); + }, + ], [ + // sync hover after a popupmenu is closed + this.dash, + 'menu-closed', + () => { + this._onMenuClosed(); + }, + ], [ + this.dash, + 'notify::requires-visibility', + () => this._updateDashVisibility(), + ]); + + if (!Main.overview.isDummy) { + this._signalsHandler.add([ + Main.overview, + 'item-drag-begin', + this._onDragStart.bind(this), + ], [ + Main.overview, + 'item-drag-end', + this._onDragEnd.bind(this), + ], [ + Main.overview, + 'item-drag-cancelled', + this._onDragEnd.bind(this), + ], [ + Main.overview, + 'showing', + this._onOverviewShowing.bind(this), + ], [ + Main.overview, + 'hiding', + this._onOverviewHiding.bind(this), + ], + [ + Main.overview, + 'hidden', + this._onOverviewHidden.bind(this), + ]); + } + + this._themeManager = new Theming.ThemeManager(this); + this._signalsHandler.add(this._themeManager, 'updated', + () => this.dash.resetAppIcons()); + + this._signalsHandler.add(DockManager.iconTheme, 'changed', + () => this.dash.resetAppIcons()); + + // Since the actor is not a topLevel child and its parent is now not added to the Chrome, + // the allocation change of the parent container (slide in and slideout) doesn't trigger + // anymore an update of the input regions. Force the update manually. + this.connect('notify::allocation', + Main.layoutManager._queueUpdateRegions.bind(Main.layoutManager)); + + + // Since Clutter has no longer ClutterAllocationFlags, + // "allocation-changed" signal has been removed. MR !1245 + this.dash._container.connect('notify::allocation', this._updateStaticBox.bind(this)); + this._slider.connect(this._isHorizontal ? 'notify::x' : 'notify::y', + this._updateStaticBox.bind(this)); + + // Load optional features that need to be activated for one dock only + if (this.isMain) + this._enableExtraFeatures(); + // Load optional features that need to be activated once per dock + this._optionalScrollWorkspaceSwitch(); + + // Delay operations that require the shell to be fully loaded and with + // user theme applied. + + this._signalsHandler.addWithLabel(Labels.INITIALIZE, global.stage, + 'after-paint', () => this._initialize()); + + // Add dash container actor and the container to the Chrome. + this.set_child(this._slider); + this._slider.set_child(this._box); + this._box.add_child(this.dash); + + // Add aligning container without tracking it for input region + this._trackDock(); + + // Create and apply height/width constraint to the dash. + if (this._isHorizontal) { + this.connect('notify::width', () => { + this.dash.setMaxSize(this.width, this.height); + }); + } else { + this.connect('notify::height', () => { + this.dash.setMaxSize(this.width, this.height); + }); + } + + if (this._position === St.Side.RIGHT) { + this.connect('notify::width', () => + (this.translation_x = -this.width)); + } else if (this._position === St.Side.BOTTOM) { + this.connect('notify::height', () => + (this.translation_y = -this.height)); + } + + // Set initial position + this._resetPosition(); + + this.connect('destroy', this._onDestroy.bind(this)); + } + + get position() { + return this._position; + } + + get isHorizontal() { + return this._isHorizontal; + } + + _untrackDock() { + Main.layoutManager.untrackChrome(this); + } + + _trackDock() { + if (DockManager.settings.dockFixed) { + if (this.get_parent()) + Main.layoutManager.removeChrome(this); + Main.layoutManager.addChrome(this, { + trackFullscreen: true, + affectsStruts: true, + }); + } else { + if (this.get_parent()) + Main.layoutManager.removeChrome(this); + Main.layoutManager.addChrome(this); + } + } + + _initialize() { + this._signalsHandler.removeWithLabel(Labels.INITIALIZE); + + // Apply custom css class according to the settings + this._themeManager.updateCustomTheme(); + + this._updateVisibilityMode(); + + // In case we are already inside the overview when the extension is loaded, + // for instance on unlocking the screen if it was locked with the overview open. + if (Main.overview.visibleTarget) + this._onOverviewShowing(); + + this._updateAutoHideBarriers(); + } + + _onDestroy() { + // The dash, intellihide and themeManager have global signals as well internally + this.dash.destroy(); + this._intellihide.destroy(); + this._themeManager.destroy(); + + if (this._marginLater) { + Utils.laterRemove(this._marginLater); + delete this._marginLater; + } + + if (this._triggerTimeoutId) + GLib.source_remove(this._triggerTimeoutId); + + this._restoreUnredirect(); + + // Remove barrier timeout + if (this._removeBarrierTimeoutId > 0) + GLib.source_remove(this._removeBarrierTimeoutId); + + // Remove existing barrier + this._removeBarrier(); + + // Remove pointer watcher + if (this._dockWatch) { + PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); + this._dockWatch = null; + } + } + + _updateAutoHideBarriers() { + // Remove pointer watcher + if (this._dockWatch) { + PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); + this._dockWatch = null; + } + + // Setup pressure barrier (GS38+ only) + this._updatePressureBarrier(); + this._updateBarrier(); + + // setup dwelling system if pressure barriers are not available + this._setupDockDwellIfNeeded(); + } + + _bindSettingsChanges() { + const {settings} = DockManager; + this._signalsHandler.add([ + settings, + 'changed::scroll-action', + () => { + this._optionalScrollWorkspaceSwitch(); + }, + ], [ + settings, + 'changed::dash-max-icon-size', + () => { + this.dash.setIconSize(settings.dashMaxIconSize); + }, + ], [ + settings, + 'changed::icon-size-fixed', + () => { + this.dash.setIconSize(settings.dashMaxIconSize); + }, + ], [ + settings, + 'changed::show-favorites', + () => { + this.dash.resetAppIcons(); + }, + ], [ + settings, + 'changed::show-trash', + () => { + this.dash.resetAppIcons(); + }, + Utils.SignalsHandlerFlags.CONNECT_AFTER, + ], [ + settings, + 'changed::show-mounts', + () => { + this.dash.resetAppIcons(); + }, + Utils.SignalsHandlerFlags.CONNECT_AFTER, + ], [ + settings, + 'changed::isolate-locations', + () => this.dash.resetAppIcons(), + Utils.SignalsHandlerFlags.CONNECT_AFTER, + ], [ + settings, + 'changed::dance-urgent-applications', + () => this.dash.resetAppIcons(), + Utils.SignalsHandlerFlags.CONNECT_AFTER, + ], [ + settings, + 'changed::show-running', + () => { + this.dash.resetAppIcons(); + }, + ], [ + settings, + 'changed::show-apps-always-in-the-edge', + () => { + this.dash.updateShowAppsButton(); + }, + ], [ + settings, + 'changed::show-apps-at-top', + () => { + this.dash.updateShowAppsButton(); + }, + ], [ + settings, + 'changed::show-show-apps-button', + () => { + if (!Main.overview.isDummy && + settings.showShowAppsButton) + this.dash.showShowAppsButton(); + else + this.dash.hideShowAppsButton(); + }, + ], [ + settings, + 'changed::dock-fixed', + () => { + this._untrackDock(); + this._trackDock(); + + this._resetPosition(); + this._updateAutoHideBarriers(); + this._updateVisibilityMode(); + }, + ], [ + settings, + 'changed::manualhide', + () => { + this._updateVisibilityMode(); + }, + ], [ + settings, + 'changed::intellihide', + () => { + this._updateVisibilityMode(); + this._updateVisibleDesktop(); + }, + ], [ + settings, + 'changed::intellihide-mode', + () => { + this._intellihide.forceUpdate(); + }, + ], [ + settings, + 'changed::autohide', + () => { + this._updateVisibilityMode(); + this._updateAutoHideBarriers(); + }, + ], [ + settings, + 'changed::autohide-in-fullscreen', + this._updateBarrier.bind(this), + ], [ + settings, + 'changed::show-dock-urgent-notify', + () => { + this.dash.resetAppIcons(); + }, + ], + [ + settings, + 'changed::extend-height', + this._resetPosition.bind(this), + ], [ + settings, + 'changed::height-fraction', + this._resetPosition.bind(this), + ], [ + settings, + 'changed::always-center-icons', + () => this.dash.resetAppIcons(), + ], [ + settings, + 'changed::require-pressure-to-show', + () => this._updateAutoHideBarriers(), + ], [ + settings, + 'changed::pressure-threshold', + () => { + this._updatePressureBarrier(); + this._updateBarrier(); + }, + ]); + } + + _disableUnredirect() { + if (!this._unredirectDisabled) { + if (Meta.disable_unredirect_for_display !== undefined) + Meta.disable_unredirect_for_display(global.display); + else if (global.compositor.disable_unredirect !== undefined) + global.compositor.disable_unredirect(); + this._unredirectDisabled = true; + } + } + + _restoreUnredirect() { + if (this._unredirectDisabled) { + if (Meta.enable_unredirect_for_display !== undefined) + Meta.enable_unredirect_for_display(global.display); + else if (global.compositor.enable_unredirect !== undefined) + global.compositor.enable_unredirect(); + this._unredirectDisabled = false; + } + } + + /** + * This is call when visibility settings change + */ + _updateVisibilityMode() { + const {settings} = DockManager; + if (DockManager.settings.dockFixed || DockManager.settings.manualhide) { + this._autohideIsEnabled = false; + this._intellihideIsEnabled = false; + } else { + this._autohideIsEnabled = settings.autohide; + this._intellihideIsEnabled = settings.intellihide; + } + + if (this._autohideIsEnabled) + this.add_style_class_name('autohide'); + else + this.remove_style_class_name('autohide'); + + if (this._intellihideIsEnabled) { + this._intellihide.enable(); + } else { + this._intellihide.disable(); + this._restoreUnredirect(); + } + + this._updateDashVisibility(); + } + + /** + * Show/hide dash based on, in order of priority: + * overview visibility + * fixed mode + * intellihide + * autohide + * overview visibility + */ + _updateDashVisibility() { + if (DockManager.settings.manualhide) { + this._ignoreHover = true; + this._removeAnimations(); + this._animateOut(0, 0); + return; + } + + if (Main.overview.visibleTarget) + return; + + const {settings} = DockManager; + + if (DockManager.settings.dockFixed) { + this._removeAnimations(); + this._animateIn(settings.animationTime, 0); + } else if (this._intellihideIsEnabled) { + if (!this.dash.requiresVisibility && this._intellihide.getOverlapStatus()) { + this._ignoreHover = false; + // Do not hide if autohide is enabled and mouse is hover + if (!this._box.hover || !this._autohideIsEnabled) + this._animateOut(settings.animationTime, 0); + } else { + this._ignoreHover = true; + this._removeAnimations(); + this._animateIn(settings.animationTime, 0); + } + } else if (this._autohideIsEnabled) { + this._ignoreHover = false; + + if (this._box.hover || this.dash.requiresVisibility) + this._animateIn(settings.animationTime, 0); + else + this._animateOut(settings.animationTime, 0); + } else { + this._animateOut(settings.animationTime, 0); + } + } + + _onOverviewShowing() { + this.add_style_class_name('overview'); + + this._ignoreHover = true; + this._intellihide.disable(); + this._removeAnimations(); + this._animateIn(DockManager.settings.animationTime, 0); + } + + _onOverviewHiding() { + this._intellihide.enable(); + this._updateDashVisibility(); + } + + _onOverviewHidden() { + this.remove_style_class_name('overview'); + this._updateDashVisibility(); + } + + _onMenuOpened() { + this._ignoreHover = true; + } + + _onMenuClosed() { + this._ignoreHover = false; + this._box.sync_hover(); + this._updateDashVisibility(); + } + + _hoverChanged() { + if (!this._ignoreHover) { + // Skip if dock is not in autohide mode for instance because it is shown + // by intellihide. + if (this._autohideIsEnabled) { + if (this._box.hover || Main.overview.visible) + this._show(); + else + this._hide(); + } + } + } + + getDockState() { + return this._dockState; + } + + _show() { + this._delayedHide = false; + if ((this._dockState === State.HIDDEN) || (this._dockState === State.HIDING)) { + if (this._dockState === State.HIDING) + // suppress all potential queued transitions - i.e. added but not started, + // always give priority to show + this._removeAnimations(); + + this.emit('showing'); + this._animateIn(DockManager.settings.animationTime, 0); + } + } + + _hide() { + // If no hiding animation is running or queued + if ((this._dockState === State.SHOWN) || (this._dockState === State.SHOWING)) { + const {settings} = DockManager; + const delay = settings.hideDelay; + + if (this._dockState === State.SHOWING) { + // if a show already started, let it finish; queue hide without removing the show. + // to obtain this, we wait for the animateIn animation to be completed + this._delayedHide = true; + return; + } + + this.emit('hiding'); + this._animateOut(settings.animationTime, delay); + } + } + + _animateIn(time, delay) { + if (this._intellihideIsEnabled) + this._disableUnredirect(); + this._dockState = State.SHOWING; + this.dash.iconAnimator.start(); + this._delayedHide = false; + + this._slider.ease_property('slide-x', 1, { + duration: time * 1000, + delay: delay * 1000, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this._dockState = State.SHOWN; + // Remove barrier so that mouse pointer is released and can + // monitors on other side of dock. + // NOTE: Delay needed to keep mouse from moving past dock and + // re-hiding dock immediately. This gives users an opportunity + // to hover over the dock + if (this._removeBarrierTimeoutId > 0) + GLib.source_remove(this._removeBarrierTimeoutId); + + if (!this._delayedHide) { + this._removeBarrierTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, 100, this._removeBarrier.bind(this)); + } else { + this._hide(); + } + }, + }); + } + + _animateOut(time, delay) { + this._dockState = State.HIDING; + + this._slider.ease_property('slide-x', 0, { + duration: time * 1000, + delay: delay * 1000, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + onComplete: () => { + this._dockState = State.HIDDEN; + if (this._intellihideIsEnabled) + this._restoreUnredirect(); + // Remove queued barrier removal timeout if any + if (this._removeBarrierTimeoutId > 0) + GLib.source_remove(this._removeBarrierTimeoutId); + this._updateBarrier(); + this.dash.iconAnimator.pause(); + }, + }); + } + + /** + * Dwelling system based on the GNOME Shell 3.14 messageTray code. + */ + _setupDockDwellIfNeeded() { + // If we don't have extended barrier features, then we need + // to support the old tray dwelling mechanism. + if (this._autohideIsEnabled && + (!Utils.supportsExtendedBarriers() || + !DockManager.settings.requirePressureToShow)) { + const pointerWatcher = PointerWatcher.getPointerWatcher(); + this._dockWatch = pointerWatcher.addWatch( + DOCK_DWELL_CHECK_INTERVAL, this._checkDockDwell.bind(this)); + this._dockDwelling = false; + this._dockDwellUserTime = 0; + } + } + + _checkDockDwell(x, y) { + const workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitor.index); + let shouldDwell; + // Check for the correct screen edge, extending the sensitive area to the whole workarea, + // minus 1 px to avoid conflicting with other active corners. + if (this._position === St.Side.LEFT) { + shouldDwell = (x === this._monitor.x) && (y > workArea.y) && + (y < workArea.y + workArea.height); + } else if (this._position === St.Side.RIGHT) { + shouldDwell = (x === this._monitor.x + this._monitor.width - 1) && + (y > workArea.y) && (y < workArea.y + workArea.height); + } else if (this._position === St.Side.TOP) { + shouldDwell = (y === this._monitor.y) && (x > workArea.x) && + (x < workArea.x + workArea.width); + } else if (this._position === St.Side.BOTTOM) { + shouldDwell = (y === this._monitor.y + this._monitor.height - 1) && + (x > workArea.x) && (x < workArea.x + workArea.width); + } + + if (shouldDwell) { + // We only set up dwell timeout when the user is not hovering over the dock + // already (!this._box.hover). + // The _dockDwelling variable is used so that we only try to + // fire off one dock dwell - if it fails (because, say, the user has the mouse down), + // we don't try again until the user moves the mouse up and down again. + if (!this._dockDwelling && !this._box.hover && (this._dockDwellTimeoutId === 0)) { + // Save the interaction timestamp so we can detect user input + const focusWindow = global.display.focus_window; + this._dockDwellUserTime = focusWindow ? focusWindow.user_time : 0; + + this._dockDwellTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, + DockManager.settings.showDelay * 1000, + this._dockDwellTimeout.bind(this)); + GLib.Source.set_name_by_id(this._dockDwellTimeoutId, + '[dash-to-dock] this._dockDwellTimeout'); + } + this._dockDwelling = true; + } else { + this._cancelDockDwell(); + this._dockDwelling = false; + } + } + + _cancelDockDwell() { + if (this._dockDwellTimeoutId !== 0) { + GLib.source_remove(this._dockDwellTimeoutId); + this._dockDwellTimeoutId = 0; + } + } + + _dockDwellTimeout() { + this._dockDwellTimeoutId = 0; + + if (!DockManager.settings.autohideInFullscreen && + this._monitor.inFullscreen) + return GLib.SOURCE_REMOVE; + + // We don't want to open the tray when a modal dialog + // is up, so we check the modal count for that. When we are in the + // overview we have to take the overview's modal push into account + if (Main.modalCount > (Main.overview.visible ? 1 : 0)) + return GLib.SOURCE_REMOVE; + + // If the user interacted with the focus window since we started the tray + // dwell (by clicking or typing), don't activate the message tray + const focusWindow = global.display.focus_window; + const currentUserTime = focusWindow ? focusWindow.user_time : 0; + if (currentUserTime !== this._dockDwellUserTime) + return GLib.SOURCE_REMOVE; + + // Reuse the pressure version function, the logic is the same + this._onPressureSensed(); + return GLib.SOURCE_REMOVE; + } + + _updatePressureBarrier() { + const {settings} = DockManager; + this._canUsePressure = Utils.supportsExtendedBarriers(); + const {pressureThreshold} = settings; + + // Remove existing pressure barrier + if (this._pressureBarrier) { + this._pressureBarrier.destroy(); + this._pressureBarrier = null; + } + + if (this._barrier) { + this._barrier.destroy(); + this._barrier = null; + } + + // Create new pressure barrier based on pressure threshold setting + if (this._canUsePressure && this._autohideIsEnabled && + DockManager.settings.requirePressureToShow) { + this._pressureBarrier = new Layout.PressureBarrier( + pressureThreshold, settings.showDelay * 1000, + Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW); + this._pressureBarrier.connect('trigger', _barrier => { + if (!settings.autohideInFullscreen && this._monitor.inFullscreen) + return; + this._onPressureSensed(); + }); + } + } + + /** + * handler for mouse pressure sensed + */ + _onPressureSensed() { + if (Main.overview.visibleTarget) + return; + + if (this._triggerTimeoutId) + GLib.source_remove(this._triggerTimeoutId); + + // In case the mouse move away from the dock area before hovering it, + // in such case the leave event would never be triggered and the dock + // would stay visible forever. + this._triggerTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => { + const [x, y, mods_] = global.get_pointer(); + let shouldHide = true; + switch (this._position) { + case St.Side.LEFT: + if (x <= this.staticBox.x2 && + x >= this._monitor.x && + y >= this._monitor.y && + y <= this._monitor.y + this._monitor.height) + shouldHide = false; + + break; + case St.Side.RIGHT: + if (x >= this.staticBox.x1 && + x <= this._monitor.x + this._monitor.width && + y >= this._monitor.y && + y <= this._monitor.y + this._monitor.height) + shouldHide = false; + + break; + case St.Side.TOP: + if (x >= this._monitor.x && + x <= this._monitor.x + this._monitor.width && + y <= this.staticBox.y2 && + y >= this._monitor.y) + shouldHide = false; + + break; + case St.Side.BOTTOM: + if (x >= this._monitor.x && + x <= this._monitor.x + this._monitor.width && + y >= this.staticBox.y1 && + y <= this._monitor.y + this._monitor.height) + shouldHide = false; + } + if (shouldHide) { + this._triggerTimeoutId = 0; + this._hoverChanged(); + return GLib.SOURCE_REMOVE; + } else { + return GLib.SOURCE_CONTINUE; + } + }); + + this._show(); + } + + /** + * Remove pressure barrier + */ + _removeBarrier() { + if (this._barrier) { + if (this._pressureBarrier) + this._pressureBarrier.removeBarrier(this._barrier); + this._barrier.destroy(); + this._barrier = null; + } + this._removeBarrierTimeoutId = 0; + return false; + } + + /** + * Update pressure barrier size + */ + _updateBarrier() { + // Remove existing barrier + this._removeBarrier(); + + // The barrier needs to be removed in fullscreen with autohide disabled + // otherwise the mouse can get trapped on monitor. + if (this._monitor.inFullscreen && + !DockManager.settings.autohideInFullscreen) + return; + + // Manually reset pressure barrier + // This is necessary because we remove the pressure barrier when it is + // triggered to show the dock + if (this._pressureBarrier) { + this._pressureBarrier._reset(); + this._pressureBarrier._isTriggered = false; + } + + // Create new barrier + // The barrier extends to the whole workarea, minus 1 px to avoid + // conflicting with other active corners + // Note: dash in fixed position doesn't use pressure barrier. + if (this._canUsePressure && this._autohideIsEnabled && + DockManager.settings.requirePressureToShow) { + let x1, x2, y1, y2, direction; + const workArea = Main.layoutManager.getWorkAreaForMonitor( + this._monitor.index); + + if (this._position === St.Side.LEFT) { + x1 = this._monitor.x + 1; + x2 = x1; + y1 = workArea.y + 1; + y2 = workArea.y + workArea.height - 1; + direction = Meta.BarrierDirection.POSITIVE_X; + } else if (this._position === St.Side.RIGHT) { + x1 = this._monitor.x + this._monitor.width - 1; + x2 = x1; + y1 = workArea.y + 1; + y2 = workArea.y + workArea.height - 1; + direction = Meta.BarrierDirection.NEGATIVE_X; + } else if (this._position === St.Side.TOP) { + x1 = workArea.x + 1; + x2 = workArea.x + workArea.width - 1; + y1 = this._monitor.y; + y2 = y1; + direction = Meta.BarrierDirection.POSITIVE_Y; + } else if (this._position === St.Side.BOTTOM) { + x1 = workArea.x + 1; + x2 = workArea.x + workArea.width - 1; + y1 = this._monitor.y + this._monitor.height; + y2 = y1; + direction = Meta.BarrierDirection.NEGATIVE_Y; + } + + if (this._pressureBarrier && this._dockState === State.HIDDEN) { + this._barrier = new Meta.Barrier({ + backend: global.backend, + x1, + x2, + y1, + y2, + directions: direction, + }); + this._pressureBarrier.addBarrier(this._barrier); + } + } + } + + _isPrimaryMonitor() { + return this.monitorIndex === Main.layoutManager.primaryIndex; + } + + _resetPosition() { + // Ensure variables linked to settings are updated. + this._updateVisibilityMode(); + + const {dockFixed: fixedIsEnabled, dockExtended: extendHeight} = DockManager.settings; + + if (fixedIsEnabled) + this.add_style_class_name('fixed'); + else + this.remove_style_class_name('fixed'); + + // Note: do not use the workarea coordinates in the direction on which the dock is placed, + // to avoid a loop [position change -> workArea change -> position change] with + // fixed dock. + const workArea = Main.layoutManager.getWorkAreaForMonitor(this.monitorIndex); + + let fraction = DockManager.settings.heightFraction; + if (extendHeight) + fraction = 1; + else if ((fraction < 0) || (fraction > 1)) + fraction = 0.95; + + if (this._isHorizontal) { + this.width = Math.round(fraction * workArea.width); + + let posY = this._monitor.y; + if (this._position === St.Side.BOTTOM) + posY += this._monitor.height; + + this.x = workArea.x + Math.round((1 - fraction) / 2 * workArea.width); + this.y = posY; + + if (extendHeight) { + this.dash._container.set_width(this.width); + this.add_style_class_name('extended'); + } else { + this.dash._container.set_width(-1); + this.remove_style_class_name('extended'); + } + } else { + this.height = Math.round(fraction * workArea.height); + + let posX = this._monitor.x; + if (this._position === St.Side.RIGHT) + posX += this._monitor.width; + + this.x = posX; + this.y = workArea.y + Math.round((1 - fraction) / 2 * workArea.height); + + if (extendHeight) { + this.dash._container.set_height(this.height); + this.add_style_class_name('extended'); + } else { + this.dash._container.set_height(-1); + this.remove_style_class_name('extended'); + } + } + } + + _updateVisibleDesktop() { + if (!this._intellihideIsEnabled) + return; + + const {desktopIconsUsableArea} = DockManager.getDefault(); + if (this._position === St.Side.BOTTOM) + desktopIconsUsableArea.setMargins(this.monitorIndex, 0, this._box.height, 0, 0); + else if (this._position === St.Side.TOP) + desktopIconsUsableArea.setMargins(this.monitorIndex, this._box.height, 0, 0, 0); + else if (this._position === St.Side.RIGHT) + desktopIconsUsableArea.setMargins(this.monitorIndex, 0, 0, 0, this._box.width); + else if (this._position === St.Side.LEFT) + desktopIconsUsableArea.setMargins(this.monitorIndex, 0, 0, this._box.width, 0); + } + + _updateStaticBox() { + this.staticBox.init_rect( + this.x + this._slider.x - (this._position === St.Side.RIGHT ? this._box.width : 0), + this.y + this._slider.y - (this._position === St.Side.BOTTOM ? this._box.height : 0), + this._box.width, + this._box.height + ); + + this._intellihide.updateTargetBox(this.staticBox); + this._updateVisibleDesktop(); + } + + _removeAnimations() { + this._slider.remove_all_transitions(); + } + + _onDragStart() { + this._oldIgnoreHover = this._ignoreHover; + this._ignoreHover = true; + this._animateIn(DockManager.settings.animationTime, 0); + } + + _onDragEnd() { + if (this._oldIgnoreHover) + this._ignoreHover = this._oldIgnoreHover; + this._oldIgnoreHover = null; + this._box.sync_hover(); + this._updateDashVisibility(); + } + + /** + * Show dock and give key focus to it + */ + _onAccessibilityFocus() { + this._box.navigate_focus(null, St.DirectionType.TAB_FORWARD, false); + this._animateIn(DockManager.settings.animationTime, 0); + } + + // Optional features to be enabled only for the main Dock + _enableExtraFeatures() { + // Restore dash accessibility + Main.ctrlAltTabManager.addGroup( + this.dash, _('Dash'), 'user-bookmarks-symbolic', + {focusCallback: this._onAccessibilityFocus.bind(this)}); + } + + /** + * Switch workspace by scrolling over the dock + */ + _optionalScrollWorkspaceSwitch() { + const isEnabled = () => + DockManager.settings.scrollAction === scrollAction.SWITCH_WORKSPACE; + + const enable = () => { + this._signalsHandler.removeWithLabel(Labels.WORKSPACE_SWITCH_SCROLL); + + this._signalsHandler.addWithLabel(Labels.WORKSPACE_SWITCH_SCROLL, + this._box, 'scroll-event', (_, e) => onScrollEvent(e)); + }; + + const disable = () => { + this._signalsHandler.removeWithLabel(Labels.WORKSPACE_SWITCH_SCROLL); + + if (this._optionalScrollWorkspaceSwitchDeadTimeId) { + GLib.source_remove(this._optionalScrollWorkspaceSwitchDeadTimeId); + this._optionalScrollWorkspaceSwitchDeadTimeId = 0; + } + }; + + DockManager.settings.connect('changed::scroll-action', () => { + if (isEnabled()) + enable(); + else + disable(); + }); + + if (isEnabled()) + enable(); + + // This was inspired to desktop-scroller@obsidien.github.com + const onScrollEvent = event => { + // When in overview change workspace only in windows view + if (Main.overview.visible) + return false; + + const activeWs = global.workspace_manager.get_active_workspace(); + let direction = null; + + let prevDirection, nextDirection; + if (global.workspace_manager.layout_columns > global.workspace_manager.layout_rows) { + prevDirection = Meta.MotionDirection.UP; + nextDirection = Meta.MotionDirection.DOWN; + } else { + prevDirection = Meta.MotionDirection.LEFT; + nextDirection = Meta.MotionDirection.RIGHT; + } + + switch (event.get_scroll_direction()) { + case Clutter.ScrollDirection.UP: + direction = prevDirection; + break; + case Clutter.ScrollDirection.DOWN: + direction = nextDirection; + break; + case Clutter.ScrollDirection.SMOOTH: { + const [dx_, dy] = event.get_scroll_delta(); + if (dy < 0) + direction = prevDirection; + else if (dy > 0) + direction = nextDirection; + } + break; + } + + if (direction) { + // Prevent scroll events from triggering too many workspace switches + // by adding a 250ms dead time between each scroll event. + // Useful on laptops when using a touch pad. + + // During the deadtime do nothing + if (this._optionalScrollWorkspaceSwitchDeadTimeId) { + return false; + } else { + this._optionalScrollWorkspaceSwitchDeadTimeId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, 250, () => { + this._optionalScrollWorkspaceSwitchDeadTimeId = 0; + }); + } + + let ws; + + ws = activeWs.get_neighbor(direction); + + if (!Main.wm._workspaceSwitcherPopup) { + // Support Workspace Grid extension showing their custom + // Grid Workspace Switcher + if (global.workspace_manager.workspace_grid !== undefined) { + Main.wm._workspaceSwitcherPopup = + global.workspace_manager.workspace_grid.getWorkspaceSwitcherPopup(); + } else { + Main.wm._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup(); + } + } + // Set the actor non reactive, so that it doesn't prevent the + // clicks events from reaching the dash actor. I can't see a reason + // why it should be reactive. + Main.wm._workspaceSwitcherPopup.reactive = false; + Main.wm._workspaceSwitcherPopup.connect('destroy', () => { + Main.wm._workspaceSwitcherPopup = null; + }); + + // If Workspace Grid is installed, let them handle the scroll behavior. + if (global.workspace_manager.workspace_grid !== undefined) + ws = global.workspace_manager.workspace_grid.actionMoveWorkspace(direction); + else + Main.wm.actionMoveWorkspace(ws); + + // Do not show workspaceSwitcher in overview + if (!Main.overview.visible) + Main.wm._workspaceSwitcherPopup.display(ws.index()); + + return true; + } else { + return false; + } + }; + } + + _activateApp(appIndex) { + const children = this.dash._box.get_children().filter(actor => { + return actor.child && + actor.child.app; + }); + + // Apps currently in the dash + const apps = children.map(actor => { + return actor.child; + }); + + // Activate with button = 1, i.e. same as left click + const button = 1; + if (appIndex < apps.length) + apps[appIndex].activate(button); + } +}); + +/* + * Handle keyboard shortcuts + */ +const NUM_HOTKEYS = 10; + +const KeyboardShortcuts = class DashToDockKeyboardShortcuts { + constructor() { + this._signalsHandler = new Utils.GlobalSignalsHandler(); + + this._hotKeysEnabled = false; + if (DockManager.settings.hotKeys) + this._enableHotKeys(); + + this._signalsHandler.add([ + DockManager.settings, + 'changed::hot-keys', + () => { + if (DockManager.settings.hotKeys) + this._enableHotKeys.bind(this)(); + else + this._disableHotKeys.bind(this)(); + }, + ]); + + this._optionalNumberOverlay(); + } + + destroy() { + DockManager.allDocks.forEach(dock => { + if (dock._numberOverlayTimeoutId) { + GLib.source_remove(dock._numberOverlayTimeoutId); + dock._numberOverlayTimeoutId = 0; + } + }); + + // Remove keybindings + this._disableHotKeys(); + this._disableExtraShortcut(); + this._signalsHandler.destroy(); + } + + _enableHotKeys() { + if (this._hotKeysEnabled) + return; + + // Setup keyboard bindings for dash elements + const keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; + const {mainDock} = DockManager.getDefault(); + keys.forEach(function (key) { + for (let i = 0; i < NUM_HOTKEYS; i++) { + const appNum = i; + Main.wm.addKeybinding(key + (i + 1), DockManager.settings, + Meta.KeyBindingFlags.IGNORE_AUTOREPEAT, + Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, + () => { + mainDock._activateApp(appNum); + this._showOverlay(); + }); + } + }, this); + + this._hotKeysEnabled = true; + } + + _disableHotKeys() { + if (!this._hotKeysEnabled) + return; + + const keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; + keys.forEach(key => { + for (let i = 0; i < NUM_HOTKEYS; i++) + Main.wm.removeKeybinding(key + (i + 1)); + }, this); + + this._hotKeysEnabled = false; + } + + _optionalNumberOverlay() { + const {settings} = DockManager; + this._shortcutIsSet = false; + // Enable extra shortcut if either 'overlay' or 'show-dock' are true + if (settings.hotKeys && + (settings.hotkeysOverlay || settings.hotkeysShowDock)) + this._enableExtraShortcut(); + + this._signalsHandler.add([ + settings, + 'changed::hot-keys', + this._checkHotkeysOptions.bind(this), + ], [ + settings, + 'changed::hotkeys-overlay', + this._checkHotkeysOptions.bind(this), + ], [ + settings, + 'changed::hotkeys-show-dock', + this._checkHotkeysOptions.bind(this), + ]); + } + + _checkHotkeysOptions() { + const {settings} = DockManager; + + if (settings.hotKeys && + (settings.hotkeysOverlay || settings.hotkeysShowDock)) + this._enableExtraShortcut(); + else + this._disableExtraShortcut(); + } + + _enableExtraShortcut() { + if (!this._shortcutIsSet) { + Main.wm.addKeybinding('shortcut', DockManager.settings, + Meta.KeyBindingFlags.IGNORE_AUTOREPEAT, + Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, + this._showOverlay.bind(this)); + this._shortcutIsSet = true; + } + } + + _disableExtraShortcut() { + if (this._shortcutIsSet) { + Main.wm.removeKeybinding('shortcut'); + this._shortcutIsSet = false; + } + } + + _showOverlay() { + for (const dock of DockManager.allDocks) { + if (DockManager.settings.hotkeysOverlay) + dock.dash.toggleNumberOverlay(true); + + // Restart the counting if the shortcut is pressed again + if (dock._numberOverlayTimeoutId) { + GLib.source_remove(dock._numberOverlayTimeoutId); + dock._numberOverlayTimeoutId = 0; + } + + // Hide the overlay/dock after the timeout + const timeout = DockManager.settings.shortcutTimeout * 1000; + dock._numberOverlayTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, timeout, () => { + dock._numberOverlayTimeoutId = 0; + dock.dash.toggleNumberOverlay(false); + // Hide the dock again if necessary + dock._updateDashVisibility(); + }); + + // Show the dock if it is hidden + if (DockManager.settings.hotkeysShowDock) { + const showDock = dock._intellihideIsEnabled || dock._autohideIsEnabled; + if (showDock) + dock._show(); + } + } + } +}; + +/** + * Isolate overview to open new windows for inactive apps + * Note: the future implementation is not fully contained here. + * Some bits are around in other methods of other classes. + * This class just take care of enabling/disabling the option. + */ +const WorkspaceIsolation = class DashToDockWorkspaceIsolation { + constructor() { + const {settings} = DockManager; + + this._signalsHandler = new Utils.GlobalSignalsHandler(); + this._injectionsHandler = new Utils.InjectionsHandler(); + + const updateAllDocks = () => { + DockManager.allDocks.forEach(dock => + dock.dash.resetAppIcons()); + if (settings.isolateWorkspaces || + settings.isolateMonitors) + this._enable.bind(this)(); + else + this._disable.bind(this)(); + }; + this._signalsHandler.add( + [settings, 'changed::isolate-workspaces', updateAllDocks], + [settings, 'changed::workspace-agnostic-urgent-windows', updateAllDocks], + [settings, 'changed::isolate-monitors', updateAllDocks] + ); + + if (settings.isolateWorkspaces || + settings.isolateMonitors) + this._enable(); + } + + _enable() { + // ensure I never double-register/inject + // although it should never happen + this._disable(); + + DockManager.allDocks.forEach(dock => { + this._signalsHandler.addWithLabel( + Labels.ISOLATION, + [global.display, 'restacked', () => dock.dash._queueRedisplay()], + [global.display, 'window-marked-urgent', () => dock.dash._queueRedisplay()], + [global.display, 'window-demands-attention', () => dock.dash._queueRedisplay()], + [global.window_manager, 'switch-workspace', () => dock.dash._queueRedisplay()] + ); + + // This last signal is only needed for monitor isolation, as windows + // might migrate from one monitor to another without triggering 'restacked' + if (DockManager.settings.isolateMonitors) { + this._signalsHandler.addWithLabel(Labels.ISOLATION, + global.display, + 'window-entered-monitor', + dock.dash._queueRedisplay.bind(dock.dash)); + } + }, this); + + /** + * here this is the Shell.App + */ + function IsolatedOverview() { + // These lines take care of Nautilus for icons on Desktop + const activeWorkspaceIndex = + global.workspaceManager.get_active_workspace_index(); + const windows = this.get_windows().filter(w => + !w.skipTaskbar && w.get_workspace().index() === activeWorkspaceIndex); + + if (windows.length) + return Main.activateWindow(windows[0]); + return this.open_new_window(-1); + } + + this._injectionsHandler.addWithLabel(Labels.ISOLATION, + Shell.App.prototype, + 'activate', + IsolatedOverview); + } + + _disable() { + this._signalsHandler.removeWithLabel(Labels.ISOLATION); + this._injectionsHandler.removeWithLabel(Labels.ISOLATION); + } + + destroy() { + this._signalsHandler.destroy(); + this._injectionsHandler.destroy(); + } +}; + + +export class DockManager { + constructor(extension) { + if (DockManager._singleton) + throw new Error('DashToDock has been already initialized'); + DockManager._singleton = this; + this._extension = extension; + this._signalsHandler = new Utils.GlobalSignalsHandler(this); + this._methodInjections = new Utils.InjectionsHandler(this); + this._vfuncInjections = new Utils.VFuncInjectionsHandler(this); + this._propertyInjections = new Utils.PropertyInjectionsHandler(this); + this._settings = this._extension.getSettings( + 'org.gnome.shell.extensions.dash-to-dock'); + this._appSwitcherSettings = new Gio.Settings({schema_id: 'org.gnome.shell.app-switcher'}); + this._mapSettingsValues(); + + this._iconTheme = new St.IconTheme(); + + this._desktopIconsUsableArea = new DesktopIconsIntegration.DesktopIconsUsableAreaClass(); + this._oldDash = Main.overview.isDummy ? null : Main.overview.dash; + this._discreteGpuAvailable = AppDisplay.discreteGpuAvailable; + this._appSpread = new AppSpread.AppSpread(); + this._notificationsMonitor = new NotificationsMonitor.NotificationsMonitor(); + + const needsRemoteModel = () => + !this._notificationsMonitor.dndMode && this._settings.showIconsEmblems; + + const ensureRemoteModel = () => { + if (needsRemoteModel && !this._remoteModel) { + this._remoteModel = new LauncherAPI.LauncherEntryRemoteModel(); + this._appIconsDecorator = new AppIconsDecorator.AppIconsDecorator(); + } else if (!needsRemoteModel) { + this._remoteModel?.destroy(); + delete this._remoteModel; + this._appIconsDecorator?.destroy(); + delete this._appIconsDecorator; + } + }; + ensureRemoteModel(); + + this._notificationsMonitor.connect('changed', ensureRemoteModel); + this._settings.connect('changed::show-icons-emblems', ensureRemoteModel); + + if (this._discreteGpuAvailable === undefined) { + const updateDiscreteGpuAvailable = () => { + const switcherooProxy = global.get_switcheroo_control(); + if (switcherooProxy) { + const prop = switcherooProxy.get_cached_property('HasDualGpu'); + this._discreteGpuAvailable = prop?.unpack() ?? false; + } else { + this._discreteGpuAvailable = false; + } + }; + this._signalsHandler.add(global, 'notify::switcheroo-control', + () => updateDiscreteGpuAvailable()); + updateDiscreteGpuAvailable(); + } + + // Connect relevant signals to the toggling function + this._bindSettingsChanges(); + + this._ensureLocations(); + + /* Array of all the docks created */ + this._allDocks = []; + this._createDocks(); + + this._overrideAppMenus(); + + // status variable: true when the overview is shown through the dash + // applications button. + this._forcedOverview = false; + } + + static getDefault() { + return DockManager._singleton; + } + + static get allDocks() { + return DockManager.getDefault()._allDocks; + } + + static get extension() { + return DockManager.getDefault().extension; + } + + static get settings() { + return DockManager.getDefault().settings; + } + + get extension() { + return this._extension; + } + + get settings() { + return this._settings; + } + + static get iconTheme() { + return DockManager.getDefault().iconTheme; + } + + get settings() { // eslint-disable-line no-dupe-class-members + return this._settings; + } + + get iconTheme() { + return this._iconTheme; + } + + get fm1Client() { + return this._fm1Client; + } + + get remoteModel() { + return this._remoteModel; + } + + get mainDock() { + return this._allDocks.length ? this._allDocks[0] : null; + } + + get removables() { + return this._removables; + } + + get trash() { + return this._trash; + } + + get desktopIconsUsableArea() { + return this._desktopIconsUsableArea; + } + + get discreteGpuAvailable() { + return AppDisplay.discreteGpuAvailable || this._discreteGpuAvailable; + } + + get appSpread() { + return this._appSpread; + } + + get notificationsMonitor() { + return this._notificationsMonitor; + } + + getDockByMonitor(monitorIndex) { + return this._allDocks.find(d => d.monitorIndex === monitorIndex); + } + + _ensureLocations() { + const {showMounts, showTrash} = this.settings; + + if (showTrash || showMounts) { + if (!this._fm1Client) + this._fm1Client = new FileManager1API.FileManager1Client(); + } else if (this._fm1Client) { + this._fm1Client.destroy(); + this._fm1Client = null; + } + + if (showMounts && !this._removables) { + this._removables = new Locations.Removables(); + } else if (!showMounts && this._removables) { + this._removables.destroy(); + this._removables = null; + } + + if (showTrash && !this._trash) { + this._trash = new Locations.Trash(); + } else if (!showTrash && this._trash) { + this._trash.destroy(); + this._trash = null; + } + + Locations.unWrapFileManagerApp(); + [this._methodInjections, this._propertyInjections].forEach( + injections => injections.removeWithLabel(Labels.LOCATIONS)); + + if (showMounts || showTrash) { + if (this.settings.isolateLocations) { + const fileManagerApp = Locations.wrapFileManagerApp(); + + this._methodInjections.addWithLabel(Labels.LOCATIONS, [ + Shell.AppSystem.prototype, 'get_running', + function (originalMethod, ...args) { + /* eslint-disable no-invalid-this */ + const runningApps = originalMethod.call(this, ...args); + const locationApps = Locations.getRunningApps(); + if (!locationApps.length) + return runningApps; + + const fileManagerIdx = runningApps.indexOf(fileManagerApp); + if (fileManagerIdx > -1 && fileManagerApp?.state !== Shell.AppState.RUNNING) + runningApps.splice(fileManagerIdx, 1); + + return [...runningApps, ...locationApps].sort(Utils.shellAppCompare); + /* eslint-enable no-invalid-this */ + }, + ], + [ + Shell.WindowTracker.prototype, 'get_window_app', + function (originalMethod, window) { + /* eslint-disable no-invalid-this */ + const locationApp = Locations.getRunningApps().find(a => + a.get_windows().includes(window)); + return locationApp ?? originalMethod.call(this, window); + /* eslint-enable no-invalid-this */ + }, + ], + [ + Shell.WindowTracker.prototype, 'get_app_from_pid', + function (originalMethod, pid) { + /* eslint-disable no-invalid-this */ + const locationApp = Locations.getRunningApps().find(a => + a.get_pids().includes(pid)); + return locationApp ?? originalMethod.call(this, pid); + /* eslint-enable no-invalid-this */ + }, + ]); + + const {get: defaultFocusAppGetter} = Object.getOwnPropertyDescriptor( + Shell.WindowTracker.prototype, 'focus_app'); + this._propertyInjections.addWithLabel(Labels.LOCATIONS, + Shell.WindowTracker.prototype, 'focus_app', { + get() { + const locationApp = Locations.getRunningApps().find(a => a.isFocused); + return locationApp ?? defaultFocusAppGetter.call(this); + }, + }); + } + } + } + + _toggle() { + if (this._toggleLater) + return; + + this._toggleLater = Utils.laterAdd(Meta.LaterType.BEFORE_REDRAW, () => { + delete this._toggleLater; + this._restoreDash(); + this._deleteDocks(); + this._createDocks(); + this.emit('toggled'); + }); + } + + _mapExternalSetting(settings, key, mappedKey, mapValueFunction) { + const camelMappedKey = mappedKey.replace(/-([a-z\d])/g, k => k[1].toUpperCase()); + + const dockPropertyDesc = Object.getOwnPropertyDescriptor(this.settings, camelMappedKey); + + if (!dockPropertyDesc) + throw new Error('Setting %s not found in dock'.format(mappedKey)); + + const mappedValue = () => mapValueFunction(settings.get_value(key).recursiveUnpack()); + Object.defineProperty(this.settings, camelMappedKey, { + get: () => mappedValue() ?? dockPropertyDesc.value, + set: value => { + if (mappedValue()) + dockPropertyDesc.value = value; + }, + }); + + this._signalsHandler.addWithLabel(Labels.SETTINGS, settings, + 'changed::%s'.format(key), () => { + this._signalsHandler.blockWithLabel(Labels.SETTINGS); + this.settings.emit('changed::%s'.format(mappedKey), mappedKey); + this._signalsHandler.unblockWithLabel(Labels.SETTINGS); + }); + } + + _mapSettingsValues() { + this.settings.settingsSchema.list_keys().forEach(key => { + const camelKey = key.replace(/-([a-z\d])/g, k => k[1].toUpperCase()); + const updateSetting = () => { + const schemaKey = this.settings.settingsSchema.get_key(key); + if (schemaKey.get_range().deepUnpack()[0] === 'enum') + this.settings[camelKey] = this.settings.get_enum(key); + else + this.settings[camelKey] = this.settings.get_value(key).recursiveUnpack(); + }; + updateSetting(); + this._signalsHandler.addWithLabel(Labels.SETTINGS, this.settings, + `changed::${key}`, updateSetting); + if (key !== camelKey) { + Object.defineProperty(this.settings, key, + {get: () => this.settings[camelKey]}); + } + }); + Object.defineProperties(this.settings, { + dockExtended: {get: () => this.settings.extendHeight}, + }); + } + + _bindSettingsChanges() { + // Connect relevant signals to the toggling function + this._signalsHandler.addWithLabel(Labels.SETTINGS, [ + Utils.getMonitorManager(), + 'monitors-changed', + this._toggle.bind(this), + ], [ + Main.sessionMode, + 'updated', + this._toggle.bind(this), + ], [ + this._settings, + 'changed::multi-monitor', + this._toggle.bind(this), + ], [ + this._settings, + 'changed::preferred-monitor', + this._toggle.bind(this), + ], [ + this._settings, + 'changed::preferred-monitor-by-connector', + this._toggle.bind(this), + ], [ + this._settings, + 'changed::dock-position', + this._toggle.bind(this), + ], [ + this._settings, + 'changed::extend-height', + () => this._adjustPanelCorners(), + ], [ + this._settings, + 'changed::dock-fixed', + () => this._adjustPanelCorners(), + ], [ + this._settings, + 'changed::show-trash', + () => this._ensureLocations(), + ], [ + this._settings, + 'changed::show-mounts', + () => this._ensureLocations(), + ], [ + this._settings, + 'changed::isolate-locations', + () => this._ensureLocations(), + ], [ + this._settings, + 'changed::intellihide', + () => { + if (!this._settings.intellihide) + this._desktopIconsUsableArea.resetMargins(); + }, + ]); + + this._mapExternalSetting(this._appSwitcherSettings, 'current-workspace-only', + 'isolate-workspaces', value => value || undefined); + } + + _createDocks() { + // If there are no monitors (headless configurations, but it can also + // happen temporary while disconnecting and reconnecting monitors), just + // do nothing. When a monitor will be connected we we'll be notified and + // and thus create the docks. This prevents pointing trying to access + // monitors throughout the code, were we are assuming that at least the + // primary monitor is present. + if (Main.layoutManager.monitors.length <= 0) + return; + + + this._preferredMonitorIndex = this.settings.preferredMonitor; + if (this._preferredMonitorIndex === -2) { + const monitorManager = Utils.getMonitorManager(); + this._preferredMonitorIndex = monitorManager.get_monitor_for_connector( + this.settings.preferredMonitorByConnector); + } else if (this._preferredMonitorIndex >= 0) { + // Primary monitor used to be always 0 in Gdk, but the shell has a different + // concept (where the order depends on mutter order). + // So even if now the extension settings may use the same logic of the shell + // we prefer not to break the previously configured systems, and so we still + // assume that the gsettings monitor numbering follows the old strategy. + // This ensure the indexing in the settings and in the shell are matched, + // i.e. that we start counting from the primaryMonitorIndex + this._preferredMonitorIndex = + (Main.layoutManager.primaryIndex + this._preferredMonitorIndex) % + Main.layoutManager.monitors.length; + } + + // In case of multi-monitor, we consider the dock on the primary monitor + // to be the preferred (main) one regardless of the settings the dock + // goes on the primary monitor also if the settings are inconsistent + // (e.g. desired monitor not connected). + if (this.settings.multiMonitor || + this._preferredMonitorIndex < 0 || + this._preferredMonitorIndex > Main.layoutManager.monitors.length - 1) + this._preferredMonitorIndex = Main.layoutManager.primaryIndex; + + + // First we create the main Dock, to get the extra features to bind to this one + let dock = new DockedDash({ + monitorIndex: this._preferredMonitorIndex, + isMain: true, + }); + this._allDocks.push(dock); + + // connect app icon into the view selector + dock.dash.showAppsButton.connect('notify::checked', + this._onShowAppsButtonToggled.bind(this)); + + // Make the necessary changes to Main.overview.dash + this._prepareMainDash(); + + // Adjust corners if necessary + this._adjustPanelCorners(); + + if (this.settings.multiMonitor) { + const nMon = Main.layoutManager.monitors.length; + for (let iMon = 0; iMon < nMon; iMon++) { + if (iMon === this._preferredMonitorIndex) + continue; + dock = new DockedDash({monitorIndex: iMon}); + this._allDocks.push(dock); + // connect app icon into the view selector + dock.dash.showAppsButton.connect('notify::checked', + this._onShowAppsButtonToggled.bind(this)); + } + } + + // Load optional features. We load *after* the docks are created, since + // we need to connect the signals to all dock instances. + this._workspaceIsolation = new WorkspaceIsolation(); + this._keyboardShortcuts = new KeyboardShortcuts(); + + this.emit('docks-ready'); + } + + _prepareStartupAnimation() { + DockManager.allDocks.forEach(dock => { + const {dash} = dock; + + dock.opacity = 255; + dash.set({ + opacity: 0, + translation_x: 0, + translation_y: 0, + }); + }); + } + + _runStartupAnimation() { + DockManager.allDocks.forEach(dock => { + const {dash} = dock; + + switch (dock.position) { + case St.Side.LEFT: + dash.translation_x = -dash.width; + break; + case St.Side.RIGHT: + dash.translation_x = dash.width; + break; + case St.Side.BOTTOM: + dash.translation_y = dash.height; + break; + case St.Side.TOP: + dash.translation_y = -dash.height; + break; + } + + dash.ease({ + opacity: 255, + translation_x: 0, + translation_y: 0, + duration: STARTUP_ANIMATION_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + }); + } + + _prepareMainDash() { + // Ensure Main.overview.dash is set to our dash in dummy mode + // while just use the default getter otherwise. + // The getter must be dynamic and not set only when we've a dummy + // overview because the mode can change dynamically. + this._propertyInjections.removeWithLabel(Labels.MAIN_DASH); + const defaultDashGetter = Object.getOwnPropertyDescriptor( + Main.overview.constructor.prototype, 'dash').get; + this._propertyInjections.addWithLabel(Labels.MAIN_DASH, Main.overview, 'dash', { + get: () => Main.overview.isDummy + ? this.mainDock.dash : defaultDashGetter.call(Main.overview), + }); + + if (Main.overview.isDummy) + return; + + // Hide usual Dash + this._oldDash.hide(); + + // Also set dash width to 1, so it's almost not taken into account by code + // calculating the reserved space in the overview. The reason to keep it at 1 is + // to allow its visibility change to trigger an allocation of the appGrid which + // in turn is triggering the appsIcon spring animation, required when no other + // actors has this effect, i.e in horizontal mode and without the workspaceThumbnails + // 1 static workspace only) + this._oldDash.set_height(1); + + this._signalsHandler.addWithLabel(Labels.OLD_DASH_CHANGES, [ + this._oldDash, + 'notify::visible', + () => this._oldDash.hide(), + ], [ + this._oldDash, + 'notify::height', + () => this._oldDash.set_height(1), + ]); + + // Pretend I'm the dash: meant to make app grid swarm animation come from + // the right position of the appShowButton. + this.overviewControls.dash = this.mainDock.dash; + this.searchController._showAppsButton = this.mainDock.dash.showAppsButton; + + // We also need to ignore max-size changes + this._methodInjections.addWithLabel(Labels.MAIN_DASH, this._oldDash, + 'setMaxSize', () => {}); + this._methodInjections.addWithLabel(Labels.MAIN_DASH, this._oldDash, + 'allocate', () => {}); + // And to return the preferred height depending on the state + this._methodInjections.addWithLabel(Labels.MAIN_DASH, this._oldDash, + 'get_preferred_height', (_originalMethod, ...args) => { + if (this.mainDock.isHorizontal && !this.settings.dockFixed) + return this.mainDock.get_preferred_height(...args); + return [0, 0]; + }); + + // FIXME: https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2890 + // const { ControlsManagerLayout } = OverviewControls; + const ControlsManagerLayout = this.overviewControls.layout_manager.constructor; + + const maybeAdjustBoxSize = (state, box, spacing) => { + // ensure that an undefined value will be converted into a valid one + spacing = spacing ?? 0; + + if (state === OverviewControls.ControlsState.WINDOW_PICKER) { + const searchBox = this.overviewControls._searchEntry.get_allocation_box(); + const {shouldShow: wsThumbnails} = this.overviewControls._thumbnailsBox; + + if (!wsThumbnails) { + box.y1 += spacing; + box.y2 -= spacing; + } + + box.y2 -= searchBox.get_height() + 2 * spacing; + } + + return box; + }; + + const maybeAdjustBoxToDock = (state, box, spacing) => { + maybeAdjustBoxSize(state, box, spacing); + + if (this.mainDock.isHorizontal || this.settings.dockFixed) + return box; + + const [, preferredWidth] = this.mainDock.get_preferred_width( + box.get_height()); + + if (this.mainDock.position === St.Side.LEFT) + box.x1 += preferredWidth; + else if (this.mainDock.position === St.Side.RIGHT) + box.x2 -= preferredWidth; + + return box; + }; + + this._vfuncInjections.addWithLabel(Labels.MAIN_DASH, ControlsManagerLayout.prototype, + 'allocate', function (container) { + /* eslint-disable no-invalid-this */ + const oldPostAllocation = this._runPostAllocation; + this._runPostAllocation = () => {}; + + const monitor = Main.layoutManager.findMonitorForActor(container); + const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index); + const startX = workArea.x - monitor.x; + const startY = workArea.y - monitor.y; + const workAreaBox = new Clutter.ActorBox(); + workAreaBox.set_origin(startX, startY); + workAreaBox.set_size(workArea.width, workArea.height); + + // GNOME 46 changes "spacing" to "_spacing". + const spacing = this.spacing ?? this._spacing; + + maybeAdjustBoxToDock(undefined, workAreaBox, spacing); + const oldStartY = workAreaBox.y1; + + const propertyInjections = new Utils.PropertyInjectionsHandler(); + propertyInjections.add(Main.layoutManager.panelBox, 'height', {value: startY}); + + if (Main.layoutManager.panelBox.y === Main.layoutManager.primaryMonitor.y) + workAreaBox.y1 -= oldStartY; + + this.vfunc_allocate(container, workAreaBox); + + propertyInjections.destroy(); + workAreaBox.y1 = oldStartY; + + const adjustActorHorizontalAllocation = actor => { + if (!actor.visible || !workAreaBox.x1) + return; + + const contentBox = actor.get_allocation_box(); + contentBox.set_size(workAreaBox.get_width(), contentBox.get_height()); + contentBox.set_origin(workAreaBox.x1, contentBox.y1); + actor.allocate(contentBox); + }; + + [this._searchEntry, this._workspacesThumbnails, this._searchController].forEach( + actor => adjustActorHorizontalAllocation(actor)); + + this._runPostAllocation = oldPostAllocation; + this._runPostAllocation(); + /* eslint-enable no-invalid-this */ + }); + + /** + * This can be removed or bypassed when GNOME/gnome-shell!1892 will be merged + * + * @param originalFunction + * @param state + * @param workAreaBox + * @param {...any} args + */ + function workspaceBoxOriginFixer(originalFunction, state, workAreaBox, ...args) { + /* eslint-disable no-invalid-this */ + const workspaceBox = originalFunction.call(this, state, workAreaBox, ...args); + workspaceBox.set_origin(workAreaBox.x1, workspaceBox.y1); + return workspaceBox; + /* eslint-enable no-invalid-this */ + } + + this._methodInjections.addWithLabel(Labels.MAIN_DASH, [ + ControlsManagerLayout.prototype, + '_computeWorkspacesBoxForState', + function (originalFunction, state, ...args) { + /* eslint-disable no-invalid-this */ + if (state === OverviewControls.ControlsState.HIDDEN) + return originalFunction.call(this, state, ...args); + + const box = workspaceBoxOriginFixer.call(this, originalFunction, state, ...args); + // GNOME 46 changes "spacing" to "_spacing". + const spacing = this.spacing ?? this._spacing; + const dock = DockManager.getDefault().getDockByMonitor(Main.layoutManager.primaryIndex); + if (!dock) + return box; + else + return maybeAdjustBoxSize(state, box, spacing); + /* eslint-enable no-invalid-this */ + }, + ], [ + WorkspacesView.SecondaryMonitorDisplay.prototype, + '_getWorkspacesBoxForState', + function (originalFunction, state, ...args) { + /* eslint-disable no-invalid-this */ + if (state === OverviewControls.ControlsState.HIDDEN) + return originalFunction.call(this, state, ...args); + + const box = workspaceBoxOriginFixer.call(this, originalFunction, state, ...args); + const dock = DockManager.getDefault().getDockByMonitor(this._monitorIndex); + if (!dock) + return box; + if (state === OverviewControls.ControlsState.WINDOW_PICKER && + dock.position === St.Side.BOTTOM) { + const [, preferredHeight] = dock.get_preferred_height(box.get_width()); + box.y2 -= preferredHeight; + } + return box; + /* eslint-enable no-invalid-this */ + }, + ], [ + ControlsManagerLayout.prototype, + '_getAppDisplayBoxForState', + function (originalFunction, ...args) { + /* eslint-disable no-invalid-this */ + return workspaceBoxOriginFixer.call(this, originalFunction, ...args); + /* eslint-enable no-invalid-this */ + }, + ]); + + this._vfuncInjections.addWithLabel(Labels.MAIN_DASH, Workspace.WorkspaceBackground.prototype, + 'allocate', function (box) { + /* eslint-disable no-invalid-this */ + this.vfunc_allocate(box); + + // This code has been submitted upstream via GNOME/gnome-shell!1892 + // so can be removed when that gets merged (or bypassed on newer shell + // versions). + const monitor = Main.layoutManager.monitors[this._monitorIndex]; + const [contentWidth, contentHeight] = this._bin.get_content_box().get_size(); + const [mX1, mX2] = [monitor.x, monitor.x + monitor.width]; + const [mY1, mY2] = [monitor.y, monitor.y + monitor.height]; + const [wX1, wX2] = [this._workarea.x, this._workarea.x + this._workarea.width]; + const [wY1, wY2] = [this._workarea.y, this._workarea.y + this._workarea.height]; + const xScale = contentWidth / this._workarea.width; + const yScale = contentHeight / this._workarea.height; + const leftOffset = wX1 - mX1; + const topOffset = wY1 - mY1; + const rightOffset = mX2 - wX2; + const bottomOffset = mY2 - wY2; + + const contentBox = new Clutter.ActorBox(); + contentBox.set_origin(-leftOffset * xScale, -topOffset * yScale); + contentBox.set_size( + contentWidth + (leftOffset + rightOffset) * xScale, + contentHeight + (topOffset + bottomOffset) * yScale); + + this._backgroundGroup.allocate(contentBox); + /* eslint-enable no-invalid-this */ + }); + + // Reduce the space that the workspaces can use in secondary monitors + this._methodInjections.addWithLabel(Labels.MAIN_DASH, WorkspacesView.WorkspacesView.prototype, + '_getFirstFitAllWorkspaceBox', function (originalFunction, ...args) { + /* eslint-disable no-invalid-this */ + const box = originalFunction.call(this, ...args); + if (DockManager.settings.dockFixed || + this._monitorIndex === Main.layoutManager.primaryIndex) + return box; + + const dock = DockManager.getDefault().getDockByMonitor(this._monitorIndex); + if (!dock) + return box; + + if (dock.isHorizontal) { + const [, preferredHeight] = dock.get_preferred_height(box.get_width()); + box.y2 -= preferredHeight; + if (dock.position === St.Side.TOP) + box.set_origin(box.x1, box.y1 + preferredHeight); + } else { + const [, preferredWidth] = dock.get_preferred_width(box.get_height()); + box.x2 -= preferredWidth / 2; + if (dock.position === St.Side.LEFT) + box.set_origin(box.x1 + preferredWidth, box.y1); + } + return box; + /* eslint-enable no-invalid-this */ + }); + + if (AppDisplay.BaseAppView?.prototype?._pageForCoords) { + // Ensure we handle Dnd events happening on the dock when we're + // dragging from AppDisplay. + // Remove when merged + // https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2002 + this._methodInjections.addWithLabel(Labels.MAIN_DASH, + AppDisplay.BaseAppView.prototype, + '_pageForCoords', function (originalFunction, ...args) { + /* eslint-disable no-invalid-this */ + if (!this._scrollView.has_pointer) + return AppDisplay.SidePages.NONE; + return originalFunction.call(this, ...args); + /* eslint-enable no-invalid-this */ + }); + } + + if (Main.layoutManager._startingUp) { + this._prepareStartupAnimation(); + + const hadOverview = Main.sessionMode.hasOverview; + + // Convince LayoutManager to use the legacy startup animation: + // Reset overview controls state to HIDDEN, as skipping the startup + // overview leaves it stuck at WINDOW_PICKER + if (this._settings.disableOverviewOnStartup) { + Main.sessionMode.hasOverview = false; + Main.overview._overview.controls._stateAdjustment.value = + OverviewControls.ControlsState.HIDDEN; + } + + this._signalsHandler.addWithLabel(Labels.STARTUP_ANIMATION, + Main.layoutManager, 'startup-complete', () => { + this._signalsHandler.removeWithLabel(Labels.STARTUP_ANIMATION); + Main.sessionMode.hasOverview = hadOverview; + this._runStartupAnimation(); + }); + } + } + + _deleteDocks() { + if (!this._allDocks.length) + return; + + // Remove extra features + this._workspaceIsolation.destroy(); + this._keyboardShortcuts.destroy(); + this._desktopIconsUsableArea.resetMargins(); + + // Delete all docks + this._allDocks.forEach(d => d.destroy()); + this._allDocks = []; + + this.emit('docks-destroyed'); + } + + _restoreDash() { + if (!this._oldDash) + return; + + this._signalsHandler.removeWithLabel(Labels.OLD_DASH_CHANGES); + [this._methodInjections, this._vfuncInjections, this._propertyInjections].forEach( + injections => injections.removeWithLabel(Labels.MAIN_DASH)); + + this.overviewControls.layout_manager._dash = this._oldDash; + this.overviewControls.dash = this._oldDash; + this.searchController._showAppsButton = this._oldDash.showAppsButton; + Main.overview.dash.show(); + Main.overview.dash.set_height(-1); // reset default dash size + // This force the recalculation of the icon size + Main.overview.dash._maxHeight = -1; + } + + get overviewControls() { + return Main.overview._overview.controls; + } + + get searchController() { + return this.overviewControls._searchController; + } + + _onShowAppsButtonToggled(button) { + const {checked} = button; + const {overviewControls} = this; + + if (!Main.overview.visible) { + this.mainDock.dash.showAppsButton._fromDesktop = true; + Main.overview.show(OverviewControls.ControlsState.APP_GRID); + } else if (!checked && this.mainDock.dash.showAppsButton._fromDesktop) { + Main.overview.hide(); + this.mainDock.dash.showAppsButton._fromDesktop = false; + } else { + // TODO: I'm not sure how reliable this is, we might need to move the + // _onShowAppsButtonToggled logic into the extension. + if (!checked) + this.mainDock.dash.showAppsButton._fromDesktop = false; + + + // Instead of "syncing" the stock button, let's call its callback directly. + overviewControls._onShowAppsButtonToggled(); + } + + // Because we "disconnected" from the search controller, we have to manage its state. + this.searchController._setSearchActive(false); + } + + _overrideAppMenus() { + this._methodInjections.add(AppMenu.AppMenu.prototype, + '_updateFavoriteItem', function (originalFunction, ...args) { + /* eslint-disable no-invalid-this */ + originalFunction.call(this, ...args); + if (!this._toggleFavoriteItem.visible) + return; + + const {id} = this._app; + this._toggleFavoriteItem.label.text = this._appFavorites.isFavorite(id) + ? _('Unpin') : __('Pin to Dock'); + /* eslint-enable no-invalid-this */ + }); + } + + destroy() { + this.emit('destroy'); + if (this._toggleLater) { + Utils.laterRemove(this._toggleLater); + delete this._toggleLater; + } + this._restoreDash(); + this._deleteDocks(); + this._revertPanelCorners(); + if (this._oldSelectorMargin) + this.searchController.margin_bottom = this._oldSelectorMargin; + if (this._fm1Client) { + this._fm1Client.destroy(); + this._fm1Client = null; + } + this._notificationsMonitor.destroy(); + this._appSpread.destroy(); + this._trash?.destroy(); + this._trash = null; + Locations.unWrapFileManagerApp(); + this._removables?.destroy(); + this._removables = null; + this._iconTheme = null; + this._remoteModel?.destroy(); + this._appIconsDecorator?.destroy(); + this._settings = null; + this._appSwitcherSettings = null; + this._oldDash = null; + + this._desktopIconsUsableArea?.destroy(); + this._desktopIconsUsableArea = null; + this._extension = null; + DockManager._singleton = null; + } + + /** + * Adjust Panel corners, remove this when 41 won't be supported anymore + */ + _adjustPanelCorners() { + if (!this._hasPanelCorners()) + return; + + const position = Utils.getPosition(); + const isHorizontal = (position === St.Side.TOP) || (position === St.Side.BOTTOM); + const dockOnPrimary = this._settings.multiMonitor || + this._preferredMonitorIndex === Main.layoutManager.primaryIndex; + + if (!isHorizontal && dockOnPrimary && this.settings.dockExtended && this.settings.dockFixed) { + Main.panel._rightCorner.hide(); + Main.panel._leftCorner.hide(); + } else { + this._revertPanelCorners(); + } + } + + _revertPanelCorners() { + if (!this._hasPanelCorners()) + return; + + Main.panel._leftCorner.show(); + Main.panel._rightCorner.show(); + } + + _hasPanelCorners() { + return !!Main.panel?._rightCorner && !!Main.panel?._leftCorner; + } +} +Signals.addSignalMethods(DockManager.prototype); + +// This class drives long-running icon animations, to keep them running in sync +// with each other, and to save CPU by pausing them when the dock is hidden. +export class IconAnimator { + constructor(actor) { + this._count = 0; + this._started = false; + this._animations = { + wiggle: [], + }; + this._timeline = new Clutter.Timeline({ + duration: AnimationUtils.adjustAnimationTime(ICON_ANIMATOR_DURATION) || 1, + repeat_count: -1, + actor, + }); + + this._updateSettings(); + this._settingsChangedId = St.Settings.get().connect('notify', + () => this._updateSettings()); + + this._timeline.connect('new-frame', () => { + const progress = this._timeline.get_progress(); + const wiggleRotation = progress < 1 / 6 ? 15 * Math.sin(progress * 24 * Math.PI) : 0; + const wigglers = this._animations.wiggle; + for (let i = 0, iMax = wigglers.length; i < iMax; i++) + wigglers[i].target.rotation_angle_z = wiggleRotation; + }); + } + + _updateSettings() { + this._timeline.set_duration( + AnimationUtils.adjustAnimationTime(ICON_ANIMATOR_DURATION) || 1); + } + + destroy() { + St.Settings.get().disconnect(this._settingsChangedId); + this._timeline.stop(); + this._timeline = null; + for (const pairs of Object.values(this._animations)) { + for (let i = 0, iMax = pairs.length; i < iMax; i++) { + const pair = pairs[i]; + pair.target.disconnect(pair.targetDestroyId); + } + } + this._animations = null; + } + + pause() { + if (this._started && this._count > 0) + this._timeline.stop(); + + this._started = false; + } + + start() { + if (!this._started && this._count > 0) + this._timeline.start(); + + this._started = true; + } + + addAnimation(target, name) { + const targetDestroyId = target.connect('destroy', + () => this.removeAnimation(target, name)); + this._animations[name].push({target, targetDestroyId}); + if (this._started && this._count === 0) + this._timeline.start(); + + this._count++; + } + + removeAnimation(target, name) { + const pairs = this._animations[name]; + for (let i = 0, iMax = pairs.length; i < iMax; i++) { + const pair = pairs[i]; + if (pair.target === target) { + target.disconnect(pair.targetDestroyId); + pairs.splice(i, 1); + this._count--; + if (this._started && this._count === 0) + this._timeline.stop(); + + return; + } + } + } +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/extension.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/extension.js new file mode 100644 index 0000000..68de2ac --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/extension.js @@ -0,0 +1,18 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import {DockManager} from './docking.js'; +import {Extension} from './dependencies/shell/extensions/extension.js'; + +// We export this so it can be accessed by other extensions +export let dockManager; + +export default class DashToDockExtension extends Extension.Extension { + enable() { + dockManager = new DockManager(this); + } + + disable() { + dockManager?.destroy(); + dockManager = null; + } +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/fileManager1API.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/fileManager1API.js new file mode 100644 index 0000000..192214a --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/fileManager1API.js @@ -0,0 +1,201 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import {GLib, Gio} from './dependencies/gi.js'; +const {signals: Signals} = imports; + +import {Utils} from './imports.js'; + +const FileManager1Iface = '\ + \ + '; + +const FileManager1Proxy = Gio.DBusProxy.makeProxyWrapper(FileManager1Iface); + +const Labels = Object.freeze({ + WINDOWS: Symbol('windows'), +}); + +/** + * This class implements a client for the org.freedesktop.FileManager1 dbus + * interface, and specifically for the OpenWindowsWithLocations property + * which is published by Nautilus, but is not an official part of the interface. + * + * The property is a map from window identifiers to a list of locations open in + * the window. + */ +export class FileManager1Client { + constructor() { + this._signalsHandler = new Utils.GlobalSignalsHandler(); + this._cancellable = new Gio.Cancellable(); + + this._windowsByPath = new Map(); + this._windowsByLocation = new Map(); + this._proxy = new FileManager1Proxy(Gio.DBus.session, + 'org.freedesktop.FileManager1', + '/org/freedesktop/FileManager1', + (initable, error) => { + // Use async construction to avoid blocking on errors. + if (error) { + if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) + global.log(error); + } else { + this._updateWindows(); + this._updateLocationMap(); + } + }, this._cancellable); + + this._signalsHandler.add([ + this._proxy, + 'g-properties-changed', + this._onPropertyChanged.bind(this), + ], [ + // We must additionally listen for Screen events to know when to + // rebuild our location map when the set of available windows changes. + global.workspaceManager, + 'workspace-added', + () => this._onWindowsChanged(), + ], [ + global.workspaceManager, + 'workspace-removed', + () => this._onWindowsChanged(), + ], [ + global.display, + 'window-entered-monitor', + () => this._onWindowsChanged(), + ], [ + global.display, + 'window-left-monitor', + () => this._onWindowsChanged(), + ]); + } + + destroy() { + if (this._windowsUpdateIdle) { + GLib.source_remove(this._windowsUpdateIdle); + delete this._windowsUpdateIdle; + } + this._cancellable.cancel(); + this._signalsHandler.destroy(); + this._windowsByLocation.clear(); + this._windowsByPath.clear(); + this._proxy = null; + } + + /** + * Return an array of windows that are showing a location or + * sub-directories of that location. + * + * @param location + */ + getWindows(location) { + if (!location) + return []; + + location += location.endsWith('/') ? '' : '/'; + const windows = []; + this._windowsByLocation.forEach((wins, l) => { + if (l.startsWith(location)) + windows.push(...wins); + }); + return [...new Set(windows)]; + } + + _onPropertyChanged(proxy, changed, _invalidated) { + const property = changed.unpack(); + if (property && + ('OpenWindowsWithLocations' in property)) + this._updateLocationMap(); + } + + _updateWindows() { + const oldSize = this._windowsByPath.size; + const oldPaths = this._windowsByPath.keys(); + this._windowsByPath = Utils.getWindowsByObjectPath(); + + if (oldSize !== this._windowsByPath.size) + return true; + + return [...oldPaths].some(path => !this._windowsByPath.has(path)); + } + + _onWindowsChanged() { + if (this._windowsUpdateIdle) + return; + + this._windowsUpdateIdle = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + if (this._updateWindows()) + this._updateLocationMap(); + + delete this._windowsUpdateIdle; + return GLib.SOURCE_REMOVE; + }); + } + + _updateLocationMap() { + const properties = this._proxy.get_cached_property_names(); + if (!properties) { + // Nothing to check yet. + return; + } + + if (properties.includes('OpenWindowsWithLocations')) + this._updateFromPaths(); + } + + _locationMapsEquals(mapA, mapB) { + if (mapA.size !== mapB.size) + return false; + + const setsEquals = (a, b) => a.size === b.size && + [...a].every(value => b.has(value)); + + for (const [key, val] of mapA) { + const windowsSet = mapB.get(key); + if (!windowsSet || !setsEquals(windowsSet, val)) + return false; + } + return true; + } + + _updateFromPaths() { + const locationsByWindowsPath = this._proxy.OpenWindowsWithLocations; + + const windowsByLocation = new Map(); + this._signalsHandler.removeWithLabel(Labels.WINDOWS); + + Object.entries(locationsByWindowsPath).forEach(([windowPath, locations]) => { + locations.forEach(location => { + const win = this._windowsByPath.get(windowPath); + const windowGroup = win ? [win] : []; + + win?.foreach_transient(w => windowGroup.push(w) || true); + + windowGroup.forEach(window => { + location += location.endsWith('/') ? '' : '/'; + // Use a set to deduplicate when a window has a + // location open in multiple tabs. + const windows = windowsByLocation.get(location) || new Set(); + windows.add(window); + + if (windows.size === 1) + windowsByLocation.set(location, windows); + + this._signalsHandler.addWithLabel(Labels.WINDOWS, window, + 'unmanaged', () => { + const wins = this._windowsByLocation.get(location); + wins.delete(window); + if (!wins.size) + this._windowsByLocation.delete(location); + this.emit('windows-changed'); + }); + }); + }); + }); + + if (!this._locationMapsEquals(this._windowsByLocation, windowsByLocation)) { + this._windowsByLocation = windowsByLocation; + this.emit('windows-changed'); + } + } +} +Signals.addSignalMethods(FileManager1Client.prototype); diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/imports.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/imports.js new file mode 100644 index 0000000..94bf337 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/imports.js @@ -0,0 +1,17 @@ +export * as AppIconIndicators from './appIconIndicators.js'; +export * as AppIcons from './appIcons.js'; +export * as AppIconsDecorator from './appIconsDecorator.js'; +export * as AppSpread from './appSpread.js'; +export * as DockDash from './dash.js'; +export * as DBusMenuUtils from './dbusmenuUtils.js'; +export * as DesktopIconsIntegration from './desktopIconsIntegration.js'; +export * as Docking from './docking.js'; +export * as Extension from './extension.js'; +export * as FileManager1API from './fileManager1API.js'; +export * as Intellihide from './intellihide.js'; +export * as LauncherAPI from './launcherAPI.js'; +export * as Locations from './locations.js'; +export * as NotificationsMonitor from './notificationsMonitor.js'; +export * as Theming from './theming.js'; +export * as Utils from './utils.js'; +export * as WindowPreview from './windowPreview.js'; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/intellihide.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/intellihide.js new file mode 100644 index 0000000..9668af2 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/intellihide.js @@ -0,0 +1,337 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import { + GLib, + Meta, + Shell, +} from './dependencies/gi.js'; + +import { + Docking, + Utils, +} from './imports.js'; + +const {signals: Signals} = imports; + +// A good compromise between reactivity and efficiency; to be tuned. +const INTELLIHIDE_CHECK_INTERVAL = 100; + +const OverlapStatus = Object.freeze({ + UNDEFINED: -1, + FALSE: 0, + TRUE: 1, +}); + +const IntellihideMode = Object.freeze({ + ALL_WINDOWS: 0, + FOCUS_APPLICATION_WINDOWS: 1, + MAXIMIZED_WINDOWS: 2, + ALWAYS_ON_TOP: 3, +}); + +// List of windows type taken into account. Order is important (keep the original +// enum order). +const handledWindowTypes = [ + Meta.WindowType.NORMAL, + Meta.WindowType.DOCK, + Meta.WindowType.DIALOG, + Meta.WindowType.MODAL_DIALOG, + Meta.WindowType.TOOLBAR, + Meta.WindowType.MENU, + Meta.WindowType.UTILITY, + Meta.WindowType.SPLASHSCREEN, + Meta.WindowType.DROPDOWN_MENU, +]; + +// List of applications, ignore windows of these applications in considering intellihide +const ignoreApps = ['com.rastersoft.ding', 'com.desktop.ding']; + +/** + * A rough and ugly implementation of the intellihide behaviour. + * Intallihide object: emit 'status-changed' signal when the overlap of windows + * with the provided targetBoxClutter.ActorBox changes; + */ +export class Intellihide { + constructor(monitorIndex) { + // Load settings + this._monitorIndex = monitorIndex; + + this._signalsHandler = new Utils.GlobalSignalsHandler(); + this._tracker = Shell.WindowTracker.get_default(); + this._focusApp = null; // The application whose window is focused. + this._topApp = null; // The application whose window is on top on the monitor with the dock. + + this._isEnabled = false; + this._status = OverlapStatus.UNDEFINED; + this._targetBox = null; + + this._checkOverlapTimeoutContinue = false; + this._checkOverlapTimeoutId = 0; + + this._trackedWindows = new Map(); + + // Connect global signals + this._signalsHandler.add([ + // Add signals on windows created from now on + global.display, + 'window-created', + this._windowCreated.bind(this), + ], [ + // triggered for instance when the window list order changes, + // included when the workspace is switched + global.display, + 'restacked', + this._checkOverlap.bind(this), + ], [ + // when windows are alwasy on top, the focus window can change + // without the windows being restacked. Thus monitor window focus change. + this._tracker, + 'notify::focus-app', + this._checkOverlap.bind(this), + ], [ + // update wne monitor changes, for instance in multimonitor when monitor are attached + Utils.getMonitorManager(), + 'monitors-changed', + this._checkOverlap.bind(this), + ]); + } + + destroy() { + // Disconnect global signals + this._signalsHandler.destroy(); + + // Remove residual windows signals + this.disable(); + } + + enable() { + this._isEnabled = true; + this._status = OverlapStatus.UNDEFINED; + global.get_window_actors().forEach(function (wa) { + this._addWindowSignals(wa); + }, this); + this._doCheckOverlap(); + } + + disable() { + this._isEnabled = false; + + for (const wa of this._trackedWindows.keys()) + this._removeWindowSignals(wa); + + this._trackedWindows.clear(); + + if (this._checkOverlapTimeoutId > 0) { + GLib.source_remove(this._checkOverlapTimeoutId); + this._checkOverlapTimeoutId = 0; + } + } + + _windowCreated(display, metaWindow) { + this._addWindowSignals(metaWindow.get_compositor_private()); + this._doCheckOverlap(); + } + + _addWindowSignals(wa) { + if (!this._handledWindow(wa)) + return; + const signalId = wa.connect('notify::allocation', this._checkOverlap.bind(this)); + this._trackedWindows.set(wa, signalId); + wa.connect('destroy', this._removeWindowSignals.bind(this)); + } + + _removeWindowSignals(wa) { + if (this._trackedWindows.get(wa)) { + wa.disconnect(this._trackedWindows.get(wa)); + this._trackedWindows.delete(wa); + } + } + + updateTargetBox(box) { + this._targetBox = box; + this._checkOverlap(); + } + + forceUpdate() { + this._status = OverlapStatus.UNDEFINED; + this._doCheckOverlap(); + } + + getOverlapStatus() { + return this._status === OverlapStatus.TRUE; + } + + _checkOverlap() { + if (!this._isEnabled || !this._targetBox) + return; + + /* Limit the number of calls to the doCheckOverlap function */ + if (this._checkOverlapTimeoutId) { + this._checkOverlapTimeoutContinue = true; + return; + } + + this._doCheckOverlap(); + + this._checkOverlapTimeoutId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, INTELLIHIDE_CHECK_INTERVAL, () => { + this._doCheckOverlap(); + if (this._checkOverlapTimeoutContinue) { + this._checkOverlapTimeoutContinue = false; + return GLib.SOURCE_CONTINUE; + } else { + this._checkOverlapTimeoutId = 0; + return GLib.SOURCE_REMOVE; + } + }); + } + + _doCheckOverlap() { + if (!this._isEnabled || !this._targetBox) + return; + + let overlaps = OverlapStatus.FALSE; + let windows = global.get_window_actors().filter(wa => this._handledWindow(wa)); + + if (windows.length > 0) { + /* + * Get the top window on the monitor where the dock is placed. + * The idea is that we dont want to overlap with the windows of the topmost application, + * event is it's not the focused app -- for instance because in multimonitor the user + * select a window in the secondary monitor. + */ + + let topWindow = null; + for (let i = windows.length - 1; i >= 0; i--) { + const metaWin = windows[i].get_meta_window(); + if (metaWin.get_monitor() === this._monitorIndex) { + topWindow = metaWin; + break; + } + } + + if (topWindow) { + this._topApp = this._tracker.get_window_app(topWindow); + // If there isn't a focused app, use that of the window on top + this._focusApp = this._tracker.focus_app || this._topApp; + + windows = windows.filter(this._intellihideFilterInteresting, this); + + for (let i = 0; i < windows.length; i++) { + const win = windows[i].get_meta_window(); + + if (win) { + const rect = win.get_frame_rect(); + + const test = (rect.x < this._targetBox.x2) && + (rect.x + rect.width >= this._targetBox.x1) && + (rect.y < this._targetBox.y2) && + (rect.y + rect.height >= this._targetBox.y1); + + if (test) { + overlaps = OverlapStatus.TRUE; + break; + } + } + } + } + } + + if (this._status !== overlaps) { + this._status = overlaps; + this.emit('status-changed', this._status); + } + } + + // Filter interesting windows to be considered for intellihide. + // Consider all windows visible on the current workspace. + // Optionally skip windows of other applications + _intellihideFilterInteresting(wa) { + const metaWin = wa.get_meta_window(); + const currentWorkspace = global.workspace_manager.get_active_workspace_index(); + const workspace = metaWin.get_workspace(); + const workspaceIndex = workspace.index(); + + // Depending on the intellihide mode, exclude non-relevent windows + switch (Docking.DockManager.settings.intellihideMode) { + case IntellihideMode.ALL_WINDOWS: + // Do nothing + break; + + case IntellihideMode.FOCUS_APPLICATION_WINDOWS: + // Skip windows of other apps + if (this._focusApp) { + // The DropDownTerminal extension is not an application per se + // so we match its window by wm class instead + if (metaWin.get_wm_class() === 'DropDownTerminalWindow') + return true; + + const currentApp = this._tracker.get_window_app(metaWin); + const focusWindow = global.display.get_focus_window(); + + // Consider half maximized windows side by side + // and windows which are alwayson top + if (currentApp !== this._focusApp && currentApp !== this._topApp && + !((focusWindow && focusWindow.maximized_vertically && + !focusWindow.maximized_horizontally) && + (metaWin.maximized_vertically && !metaWin.maximized_horizontally) && + metaWin.get_monitor() === focusWindow.get_monitor()) && + !metaWin.is_above()) + return false; + } + break; + + case IntellihideMode.MAXIMIZED_WINDOWS: + // Skip unmaximized windows + if (!metaWin.maximized_vertically && !metaWin.maximized_horizontally && !metaWin.fullscreen) + return false; + break; + + case IntellihideMode.ALWAYS_ON_TOP: + // Always on top, except for fullscreen windows + if (this._focusApp) { + const {focusWindow} = global.display; + if (!focusWindow?.fullscreen) + return false; + } + break; + } + + if (workspaceIndex === currentWorkspace && metaWin.showing_on_its_workspace()) + return true; + else + return false; + } + + // Filter windows by type + // inspired by Opacify@gnome-shell.localdomain.pl + _handledWindow(wa) { + const metaWindow = wa.get_meta_window(); + + if (!metaWindow) + return false; + + // The DING extension desktop window needs to be excluded + // so we match its window by application id and window property. + const wmApp = metaWindow.get_gtk_application_id(); + if (ignoreApps.includes(wmApp) && metaWindow.is_skip_taskbar()) + return false; + + // The DropDownTerminal extension uses the POPUP_MENU window type hint + // so we match its window by wm class instead + if (metaWindow.get_wm_class() === 'DropDownTerminalWindow') + return true; + + const wtype = metaWindow.get_window_type(); + for (let i = 0; i < handledWindowTypes.length; i++) { + const hwtype = handledWindowTypes[i]; + if (hwtype === wtype) + return true; + else if (hwtype > wtype) + return false; + } + return false; + } +} + +Signals.addSignalMethods(Intellihide.prototype); diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/launcherAPI.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/launcherAPI.js new file mode 100644 index 0000000..8778d32 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/launcherAPI.js @@ -0,0 +1,287 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import {Gio} from './dependencies/gi.js'; +import {DBusMenuUtils} from './imports.js'; + +const DBusMenu = await DBusMenuUtils.haveDBusMenu(); + +export class LauncherEntryRemoteModel { + constructor() { + this._entrySourceStacks = new Map(); + this._remoteMaps = new Map(); + + this._launcher_entry_dbus_signal_id = + Gio.DBus.session.signal_subscribe(null, // sender + 'com.canonical.Unity.LauncherEntry', // iface + 'Update', // member + null, // path + null, // arg0 + Gio.DBusSignalFlags.NONE, + (_connection, senderName, _objectPath, _interfaceName, _signalName, parameters) => + this._onUpdate(senderName, ...parameters.deep_unpack())); + + this._dbus_name_owner_changed_signal_id = + Gio.DBus.session.signal_subscribe('org.freedesktop.DBus', // sender + 'org.freedesktop.DBus', // interface + 'NameOwnerChanged', // member + '/org/freedesktop/DBus', // path + null, // arg0 + Gio.DBusSignalFlags.NONE, + (connection, _senderName, _objectPath, _interfaceName, _signalName, parameters) => + this._onDBusNameChange(...parameters.deep_unpack().slice(1))); + + this._acquireUnityDBus(); + } + + destroy() { + if (this._launcher_entry_dbus_signal_id) + Gio.DBus.session.signal_unsubscribe(this._launcher_entry_dbus_signal_id); + + + if (this._dbus_name_owner_changed_signal_id) + Gio.DBus.session.signal_unsubscribe(this._dbus_name_owner_changed_signal_id); + + + this._releaseUnityDBus(); + } + + _lookupStackById(appId) { + let sourceStack = this._entrySourceStacks.get(appId); + if (!sourceStack) { + sourceStack = new PropertySourceStack(new LauncherEntry(), + launcherEntryDefaults); + this._entrySourceStacks.set(appId, sourceStack); + } + + return sourceStack; + } + + lookupById(appId) { + return this._lookupStackById(appId).target; + } + + _acquireUnityDBus() { + if (!this._unity_bus_id) { + this._unity_bus_id = Gio.DBus.session.own_name('com.canonical.Unity', + Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT | Gio.BusNameOwnerFlags.REPLACE, + null, () => (this._unity_bus_id = 0)); + } + } + + _releaseUnityDBus() { + if (this._unity_bus_id) { + Gio.DBus.session.unown_name(this._unity_bus_id); + this._unity_bus_id = 0; + } + } + + _onDBusNameChange(before, after) { + if (!before || !this._remoteMaps.size) + return; + + const remoteMap = this._remoteMaps.get(before); + if (!remoteMap) + return; + + this._remoteMaps.delete(before); + if (after && !this._remoteMaps.has(after)) { + this._remoteMaps.set(after, remoteMap); + } else { + for (const [appId, remote] of remoteMap) { + const sourceStack = this._entrySourceStacks.get(appId); + const changed = sourceStack.remove(remote); + if (changed) + sourceStack.target._emitChangedEvents(changed); + } + } + } + + _onUpdate(senderName, appUri, properties) { + if (!senderName) + return; + + + const appId = appUri.replace(/(^\w+:|^)\/\//, ''); + if (!appId) + return; + + + let remoteMap = this._remoteMaps.get(senderName); + if (!remoteMap) + this._remoteMaps.set(senderName, remoteMap = new Map()); + + let remote = remoteMap.get(appId); + if (!remote) + remoteMap.set(appId, remote = Object.assign({}, launcherEntryDefaults)); + + for (const name in properties) { + if (name === 'quicklist' && DBusMenu) { + const quicklistPath = properties[name].unpack(); + if (quicklistPath && + (!remote._quicklistMenuClient || + remote._quicklistMenuClient.dbus_object !== quicklistPath)) { + remote.quicklist = null; + let menuClient = remote._quicklistMenuClient; + if (menuClient) { + menuClient.dbus_object = quicklistPath; + } else { + // This property should not be enumerable + Object.defineProperty(remote, '_quicklistMenuClient', { + writable: true, + value: menuClient = new DBusMenu.Client({ + dbus_name: senderName, + dbus_object: quicklistPath, + }), + }); + } + const handler = () => { + const root = menuClient.get_root(); + if (remote.quicklist !== root) { + remote.quicklist = root; + if (sourceStack.isTop(remote)) { + sourceStack.target.quicklist = root; + sourceStack.target._emitChangedEvents(['quicklist']); + } + } + }; + menuClient.connect(DBusMenu.CLIENT_SIGNAL_ROOT_CHANGED, handler); + } + } else { + remote[name] = properties[name].unpack(); + } + } + + const sourceStack = this._lookupStackById(appId); + sourceStack.target._emitChangedEvents(sourceStack.update(remote)); + } +} + +const launcherEntryDefaults = Object.freeze({ + count: 0, + progress: 0, + urgent: false, + updating: false, + quicklist: null, + 'count-visible': false, + 'progress-visible': false, +}); + +const LauncherEntry = class DashToDockLauncherEntry { + constructor() { + this._connections = new Map(); + this._handlers = new Map(); + this._nextId = 0; + } + + connect(eventNames, callback) { + if (typeof eventNames === 'string') + eventNames = [eventNames]; + + callback(this, this); + const id = this._nextId++; + const handler = {id, callback}; + eventNames.forEach(name => { + let handlerList = this._handlers.get(name); + if (!handlerList) + this._handlers.set(name, handlerList = []); + + handlerList.push(handler); + }); + this._connections.set(id, eventNames); + return id; + } + + disconnect(id) { + const eventNames = this._connections.get(id); + if (!eventNames) + return; + + this._connections.delete(id); + eventNames.forEach(name => { + const handlerList = this._handlers.get(name); + if (handlerList) { + for (let i = 0, iMax = handlerList.length; i < iMax; i++) { + if (handlerList[i].id === id) { + handlerList.splice(i, 1); + break; + } + } + } + }); + } + + _emitChangedEvents(propertyNames) { + const handlers = new Set(); + propertyNames.forEach(name => { + const handlerList = this._handlers.get(`${name}-changed`); + if (handlerList) { + for (let i = 0, iMax = handlerList.length; i < iMax; i++) + handlers.add(handlerList[i]); + } + }); + Array.from(handlers).sort((x, y) => x.id - y.id).forEach(handler => handler.callback(this, this)); + } +}; + +for (const [name, defaultValue] of Object.entries(launcherEntryDefaults)) { + const jsName = name.replaceAll('-', '_'); + LauncherEntry.prototype[jsName] = defaultValue; + if (jsName !== name) { + Object.defineProperty(LauncherEntry.prototype, name, { + get() { + return this[jsName]; + }, + set(value) { + this[jsName] = value; + }, + }); + } +} + +const PropertySourceStack = class DashToDockPropertySourceStack { + constructor(target, bottom) { + this.target = target; + this._bottom = bottom; + this._stack = []; + } + + isTop(source) { + return this._stack.length > 0 && this._stack[this._stack.length - 1] === source; + } + + update(source) { + if (!this.isTop(source)) { + this.remove(source); + this._stack.push(source); + } + return this._assignFrom(source); + } + + remove(source) { + const stack = this._stack; + const top = stack[stack.length - 1]; + if (top === source) { + stack.length--; + return this._assignFrom(stack.length > 0 ? stack[stack.length - 1] : this._bottom); + } + for (let i = 0, iMax = stack.length; i < iMax; i++) { + if (stack[i] === source) { + stack.splice(i, 1); + break; + } + } + + return null; + } + + _assignFrom(source) { + const changedProperties = []; + for (const name in source) { + if (this.target[name] !== source[name]) { + this.target[name] = source[name]; + changedProperties.push(name); + } + } + return changedProperties; + } +}; diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ar/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ar/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..ffb5786 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ar/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/be/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/be/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..ba9a252 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/be/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/bg/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/bg/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..87e8892 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/bg/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/cs/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/cs/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..7b092bf Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/cs/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/de/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/de/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..0f7dc82 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/de/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/el/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/el/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..c64aa83 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/el/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/es/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/es/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..d143515 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/es/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/eu/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/eu/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..044406f Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/eu/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/fr/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/fr/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..9ca1002 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/fr/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/gl/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/gl/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..110db82 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/gl/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/hu/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/hu/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..b8a17f9 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/hu/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/id/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/id/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..3ea32d5 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/id/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/it/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/it/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..56361a5 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/it/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ja/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ja/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..a2ba4ef Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ja/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ka/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ka/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..47042cf Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ka/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/kk/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/kk/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..f5f92c0 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/kk/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ko/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ko/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..d9c5308 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ko/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/lv/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/lv/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..e6d146a Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/lv/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/nb/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/nb/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..db5ad46 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/nb/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/nl/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/nl/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..e926ee0 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/nl/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pl/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pl/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..a48230d Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pl/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pt/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pt/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..9733e40 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pt/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pt_BR/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pt_BR/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..de2a4e6 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/pt_BR/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ro/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ro/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..a3c2b23 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ro/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ru/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ru/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..69ed645 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/ru/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sk/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sk/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..9348b7e Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sk/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sl/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sl/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..2f30b3a Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sl/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sr/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sr/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..b5e6fb6 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sr/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sr@latin/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sr@latin/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..8dfc69e Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sr@latin/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sv/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sv/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..d6cdf84 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/sv/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/tr/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/tr/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..6a12cfb Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/tr/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/uk_UA/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/uk_UA/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..d71acaf Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/uk_UA/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/zh_CN/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/zh_CN/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..b701af8 Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/zh_CN/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/zh_TW/LC_MESSAGES/dashtodock.mo b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/zh_TW/LC_MESSAGES/dashtodock.mo new file mode 100644 index 0000000..1bd8e0a Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locale/zh_TW/LC_MESSAGES/dashtodock.mo differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locations.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locations.js new file mode 100644 index 0000000..534466c --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locations.js @@ -0,0 +1,1535 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import { + Gio, + GioUnix, + GLib, + GObject, + Shell, + St, +} from './dependencies/gi.js'; + +import {ShellMountOperation} from './dependencies/shell/ui.js'; + +import { + Docking, + Utils, +} from './imports.js'; + +import {Extension} from './dependencies/shell/extensions/extension.js'; + +// Use __ () and N__() for the extension gettext domain, and reuse +// the shell domain with the default _() and N_() +const {gettext: __} = Extension; + +const {signals: Signals} = imports; + +const FALLBACK_REMOVABLE_MEDIA_ICON = 'drive-removable-media'; +const FALLBACK_TRASH_ICON = 'user-trash'; +const FILE_MANAGER_DESKTOP_APP_ID = 'org.gnome.Nautilus.desktop'; +const ATTRIBUTE_METADATA_CUSTOM_ICON = 'metadata::custom-icon'; +const TRASH_URI = 'trash://'; +const UPDATE_TRASH_DELAY = 1000; +const LAUNCH_HANDLER_MAX_WAIT = 200; + +const NautilusFileOperations2Interface = '\ + \ + \ + \ + \ + \ + \ +'; + +const NautilusFileOperations2ProxyInterface = + Gio.DBusProxy.makeProxyWrapper(NautilusFileOperations2Interface); + +const Labels = Object.freeze({ + LOCATION_WINDOWS: Symbol('location-windows'), + WINDOWS_CHANGED: Symbol('windows-changed'), +}); + +const GJS_SUPPORTS_FILE_IFACE_PROMISES = imports.system.version >= 17101; + +if (GJS_SUPPORTS_FILE_IFACE_PROMISES) { + Gio._promisify(Gio.File.prototype, 'query_info_async'); + Gio._promisify(Gio.File.prototype, 'query_default_handler_async'); +} + + +/** + * + */ +function makeNautilusFileOperationsProxy() { + const proxy = new NautilusFileOperations2ProxyInterface( + Gio.DBus.session, + 'org.gnome.Nautilus', + '/org/gnome/Nautilus/FileOperations2', (_p, error) => { + if (error) + logError(error, 'Error connecting to Nautilus'); + } + ); + + proxy.platformData = params => { + const defaultParams = { + parentHandle: '', + timestamp: global.get_current_time(), + windowPosition: 'center', + }; + const {parentHandle, timestamp, windowPosition} = { + ...defaultParams, + ...params, + }; + + return { + 'parent-handle': new GLib.Variant('s', parentHandle), + 'timestamp': new GLib.Variant('u', timestamp), + 'window-position': new GLib.Variant('s', windowPosition), + }; + }; + + return proxy; +} + +export const LocationAppInfo = GObject.registerClass({ + Implements: [Gio.AppInfo], + Properties: { + 'location': GObject.ParamSpec.object( + 'location', 'location', 'location', + GObject.ParamFlags.READWRITE, + Gio.File.$gtype), + 'name': GObject.ParamSpec.string( + 'name', 'name', 'name', + GObject.ParamFlags.READWRITE, + null), + 'icon': GObject.ParamSpec.object( + 'icon', 'icon', 'icon', + GObject.ParamFlags.READWRITE, + Gio.Icon.$gtype), + 'cancellable': GObject.ParamSpec.object( + 'cancellable', 'cancellable', 'cancellable', + GObject.ParamFlags.READWRITE, + Gio.Cancellable.$gtype), + }, +}, class LocationAppInfo extends GioUnix.DesktopAppInfo { + static get GJS_BINARY_PATH() { + if (!this._gjsBinaryPath) + this._gjsBinaryPath = GLib.find_program_in_path('gjs'); + + return this._gjsBinaryPath; + } + + list_actions() { + return []; + } + + get_action_name() { + return null; + } + + get_boolean() { + return false; + } + + vfunc_dup() { + return new LocationAppInfo({ + location: this.location, + name: this.name, + icon: this.icon, + cancellable: this.cancellable, + }); + } + + vfunc_equal(other) { + if (this.location) + return this.location.equal(other?.location); + + return this.name === other.name && + (this.icon ? this.icon.equal(other?.icon) : !other?.icon); + } + + vfunc_get_id() { + return 'location:%s'.format(this.location?.get_uri()); + } + + vfunc_get_name() { + return this.name; + } + + vfunc_get_description() { + return null; + } + + vfunc_get_executable() { + return null; + } + + vfunc_get_icon() { + return this.icon; + } + + vfunc_launch(files, context) { + if (files?.length) { + throw new GLib.Error(Gio.IOErrorEnum, + Gio.IOErrorEnum.NOT_SUPPORTED, 'Launching with files not supported'); + } + + return this.getHandlerApp().launch_uris([this.location.get_uri()], context); + } + + vfunc_supports_uris() { + return false; + } + + vfunc_supports_files() { + return false; + } + + vfunc_launch_uris(uris, context) { + return this.launch(uris, context); + } + + vfunc_should_show() { + return true; + } + + vfunc_set_as_default_for_type() { + throw new GLib.Error(Gio.IOErrorEnum, + Gio.IOErrorEnum.NOT_SUPPORTED, 'Not supported'); + } + + vfunc_set_as_default_for_extension() { + throw new GLib.Error(Gio.IOErrorEnum, + Gio.IOErrorEnum.NOT_SUPPORTED, 'Not supported'); + } + + vfunc_add_supports_type() { + throw new GLib.Error(Gio.IOErrorEnum, + Gio.IOErrorEnum.NOT_SUPPORTED, 'Not supported'); + } + + vfunc_can_remove_supports_type() { + return false; + } + + vfunc_remove_supports_type() { + return false; + } + + vfunc_can_delete() { + return false; + } + + vfunc_do_delete() { + return false; + } + + vfunc_get_commandline() { + try { + return this.getHandlerApp().get_commandline(); + } catch { + return this._getFallbackCommandLine(); + } + } + + vfunc_get_display_name() { + return this.name; + } + + vfunc_set_as_last_used_for_type() { + throw new GLib.Error(Gio.IOErrorEnum, + Gio.IOErrorEnum.NOT_SUPPORTED, 'Not supported'); + } + + vfunc_get_supported_types() { + return []; + } + + _getFallbackCommandLine() { + return `gio open ${this.location?.get_uri()}`; + } + + async _queryLocationIcons(params) { + const icons = {standard: null, custom: null}; + if (!this.location) + return icons; + + const cancellable = params.cancellable ?? this.cancellable; + const iconsQuery = []; + if (params?.standard) + iconsQuery.push(Gio.FILE_ATTRIBUTE_STANDARD_ICON); + + if (params?.custom) + iconsQuery.push(ATTRIBUTE_METADATA_CUSTOM_ICON); + + if (!iconsQuery.length) + throw new Error('Invalid Query Location Icons parameters'); + + let info; + try { + if (!GJS_SUPPORTS_FILE_IFACE_PROMISES) { + Gio._promisify(this.location.constructor.prototype, + 'query_info_async', 'query_info_finish'); + } + info = await this.location.query_info_async( + iconsQuery.join(','), + Gio.FileQueryInfoFlags.NONE, + GLib.PRIORITY_LOW, cancellable); + if (info.has_attribute(Gio.FILE_ATTRIBUTE_STANDARD_ICON)) + icons.standard = info.get_icon(); + } catch (e) { + if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND) || + e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_MOUNTED)) + return icons; + throw e; + } + + const customIcon = info.get_attribute_string(ATTRIBUTE_METADATA_CUSTOM_ICON); + if (customIcon) { + const customIconFile = GLib.uri_parse_scheme(customIcon) + ? Gio.File.new_for_uri(customIcon) : Gio.File.new_for_path(customIcon); + const iconFileInfo = await customIconFile.query_info_async( + Gio.FILE_ATTRIBUTE_STANDARD_TYPE, + Gio.FileQueryInfoFlags.NONE, + GLib.PRIORITY_LOW, cancellable); + + if (iconFileInfo.get_file_type() === Gio.FileType.REGULAR) + icons.custom = Gio.FileIcon.new(customIconFile); + } + + return icons; + } + + async _updateLocationIcon(params = {standard: true, custom: true}) { + const cancellable = new Utils.CancellableChild(this.cancellable); + + try { + this._updateIconCancellable?.cancel(); + this._updateIconCancellable = cancellable; + + const icons = await this._queryLocationIcons({cancellable, ...params}); + const icon = icons.custom ?? icons.standard; + + if (icon && !icon.equal(this.icon)) + this.icon = icon; + } catch (e) { + if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) + logError(e, 'Impossible to update icon for %s'.format(this.get_id())); + } finally { + cancellable.cancel(); + if (this._updateIconCancellable === cancellable) + delete this._updateIconCancellable; + } + } + + async _getHandlerAppAsync(cancellable) { + if (!this.location) + return null; + + try { + if (!GJS_SUPPORTS_FILE_IFACE_PROMISES) { + Gio._promisify(this.location.constructor.prototype, + 'query_default_handler_async', + 'query_default_handler_finish'); + } + + return await this.location.query_default_handler_async( + GLib.PRIORITY_DEFAULT, cancellable); + } catch (e) { + if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_MOUNTED)) + return getFileManagerApp()?.appInfo; + + if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + logError(e, 'Impossible to find an URI handler for %s'.format( + this.get_id())); + } + + throw e; + } + } + + _getHandlerAppFromWorker(cancellable) { + const locationsWorker = GLib.build_filenamev([ + Docking.DockManager.extension.path, + 'locationsWorker.js', + ]); + const locationsWorkerArgs = [LocationAppInfo.GJS_BINARY_PATH, '-m', + locationsWorker, 'handler', this.location.get_uri(), + '--timeout', `${LAUNCH_HANDLER_MAX_WAIT}`]; + const subProcess = Gio.Subprocess.new(locationsWorkerArgs, + Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE); + + try { + const [, stdOut, stdErr] = subProcess.communicate(null, cancellable); + subProcess.wait(cancellable); + const errorCode = subProcess.get_exit_status(); + const textDecoder = new TextDecoder(); + + if (errorCode) { + const errorLines = textDecoder.decode(stdErr.toArray()).split('\n'); + const error = new GLib.Error(Gio.IOErrorEnum, + errorCode === GLib.MAXUINT8 ? 0 : errorCode, errorLines[0]); + error.stack = `${errorLines.slice(3).join('\n')}${error.stack}`; + throw error; + } + + const desktopId = textDecoder.decode(stdOut.toArray()).trim(); + const handlerApp = Shell.AppSystem.get_default().lookup_app(desktopId)?.appInfo; + return handlerApp; + } finally { + subProcess.force_exit(); + } + } + + getHandlerApp() { + if (this._handlerApp) + return this._handlerApp; + + if (!this.location) + return null; + + const cancellable = new Utils.CancellableChild(this.cancellable); + + try { + if (LocationAppInfo.GJS_BINARY_PATH) + this._handlerApp = this._getHandlerAppFromWorker(cancellable); + else + this._handlerApp = this.location.query_default_handler(cancellable); + + if (!this._handlerApp) { + throw new GLib.Error(Gio.IOErrorEnum, + Gio.IOErrorEnum.NOT_FOUND, `Handler for ${this.location} not found`); + } + } catch (e) { + if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_MOUNTED)) + return getFileManagerApp()?.appInfo; + + if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + logError(e, 'Impossible to find an URI handler for %s'.format( + this.get_id())); + } + + throw e; + } + + return this._handlerApp; + } + + destroy() { + this.location = null; + this.icon = null; + this.name = null; + this._handlerApp = null; + this.cancellable?.cancel(); + } +}); + +const RemovableAction = Object.freeze({ + MOUNT: 'mount', + UNMOUNT: 'unmount', + EJECT: 'eject', +}); + +const MountableVolumeAppInfo = GObject.registerClass({ + Implements: [Gio.AppInfo], + Properties: { + 'volume': GObject.ParamSpec.object( + 'volume', 'volume', 'volume', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + Gio.Volume.$gtype), + 'mount': GObject.ParamSpec.object( + 'mount', 'mount', 'mount', + GObject.ParamFlags.READWRITE, + Gio.Mount.$gtype), + 'busy': GObject.ParamSpec.boolean( + 'busy', 'busy', 'busy', + GObject.ParamFlags.READWRITE, + false), + }, +}, +class MountableVolumeAppInfo extends LocationAppInfo { + _init(volume, cancellable = null) { + super._init({ + volume, + cancellable, + }); + + this._signalsHandler = new Utils.GlobalSignalsHandler(); + + const updateAndMonitor = () => { + this._update(); + this._monitorChanges(); + }; + updateAndMonitor(); + this._mountChanged = this.connect('notify::mount', updateAndMonitor); + + if (!this.mount && this.volume.get_identifier('class') === 'network') { + // For some devices the mount point isn't advertised promptly + // even if it's already existing, and there's no signaling about + this._lazyUpdater = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 2, () => { + this._update(); + delete this._lazyUpdater; + return GLib.SOURCE_REMOVE; + }); + } + } + + get busy() { + return !!this._currentAction; + } + + get currentAction() { + return this._currentAction; + } + + destroy() { + if (this._lazyUpdater) { + GLib.source_remove(this._lazyUpdater); + delete this._lazyUpdater; + } + this.disconnect(this._mountChanged); + this.mount = null; + this._signalsHandler.destroy(); + + super.destroy(); + } + + vfunc_dup() { + return new MountableVolumeAppInfo({ + volume: this.volume, + cancellable: this.cancellable, + }); + } + + vfunc_get_id() { + const uuid = this.mount?.get_uuid() ?? this.volume.get_uuid(); + return uuid ? 'mountable-volume:%s'.format(uuid) : super.vfunc_get_id(); + } + + vfunc_equal(other) { + if (this.volume === other?.volume && this.mount === other?.mount) + return true; + + return this.get_id() === other?.get_id(); + } + + list_actions() { + const actions = []; + const {mount} = this; + + if (mount) { + if (this.mount.can_unmount()) + actions.push(RemovableAction.UNMOUNT); + if (this.mount.can_eject()) + actions.push(RemovableAction.EJECT); + + return actions; + } + + if (this.volume.can_mount()) + actions.push(RemovableAction.MOUNT); + if (this.volume.can_eject()) + actions.push(RemovableAction.EJECT); + + return actions; + } + + get_action_name(action) { + switch (action) { + case RemovableAction.MOUNT: + return __('Mount'); + case RemovableAction.UNMOUNT: + return __('Unmount'); + case RemovableAction.EJECT: + return __('Eject'); + default: + return null; + } + } + + vfunc_launch(files, context) { + if (this.mount || files?.length) + return super.vfunc_launch(files, context); + + this.mountAndLaunch(files, context); + return true; + } + + _update() { + this.mount = this.volume.get_mount(); + + const removable = this.mount ?? this.volume; + this.name = removable.get_name(); + this.icon = removable.get_icon(); + + this.location = this.mount?.get_default_location() ?? + this.volume.get_activation_root(); + + this._updateLocationIcon({custom: true}); + } + + _monitorChanges() { + this._signalsHandler.destroy(); + + const removable = this.mount ?? this.volume; + this._signalsHandler.add(removable, 'changed', () => this._update()); + + if (this.mount) { + this._signalsHandler.add(this.mount, 'pre-unmount', () => this._update()); + this._signalsHandler.add(this.mount, 'unmounted', () => this._update()); + } + } + + async mountAndLaunch(files, context) { + if (this.mount) + return super.vfunc_launch(files, context); + + try { + await this.launchAction(RemovableAction.MOUNT); + if (!this.mount) { + throw new Error('No mounted location to open for %s'.format( + this.get_id())); + } + + return super.vfunc_launch(files, context); + } catch (e) { + logError(e, 'Mount and launch %s'.format(this.get_id())); + return false; + } + } + + _notifyActionError(action, message) { + switch (action) { + case RemovableAction.MOUNT: + global.notify_error(__('Failed to mount “%s”'.format( + this.get_name())), message); + break; + + case RemovableAction.UNMOUNT: + global.notify_error(__('Failed to unmount “%s”'.format( + this.get_name())), message); + break; + + case RemovableAction.EJECT: + global.notify_error(__('Failed to eject “%s”'.format( + this.get_name())), message); + break; + } + } + + async launchAction(action) { + if (!this.list_actions().includes(action)) + throw new Error('Action %s is not supported by %s', action, this); + + switch (this._currentAction) { + case RemovableAction.MOUNT: + this._notifyActionError(action, + __('Mount operation already in progress')); + break; + + case RemovableAction.UNMOUNT: + this._notifyActionError(action, + __('Unmount operation already in progress')); + break; + + case RemovableAction.EJECT: + this._notifyActionError(action, + __('Eject operation already in progress')); + break; + + default: + if (this._currentAction) { + throw new Error('Another action %s is being performed in %s'.format( + this._currentAction, this)); + } + } + + this._currentAction = action; + this.notify('busy'); + const removable = this.mount ?? this.volume; + const operation = new ShellMountOperation.ShellMountOperation(removable); + try { + switch (action) { + case RemovableAction.MOUNT: + await this.volume.mount(Gio.MountMountFlags.NONE, operation.mountOp, + this.cancellable); + return true; + + case RemovableAction.UNMOUNT: + await this.mount.unmount_with_operation(Gio.MountUnmountFlags.FORCE, + operation.mountOp, this.cancellable); + return true; + + case RemovableAction.EJECT: + await removable.eject_with_operation(Gio.MountUnmountFlags.FORCE, + operation.mountOp, this.cancellable); + return true; + + default: + logError(new Error(), 'No action %s on removable %s'.format(action, + removable.get_name())); + return false; + } + } catch (e) { + if (action === RemovableAction.MOUNT && + e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.ALREADY_MOUNTED)) + return true; + else if (action === RemovableAction.UNMOUNT && + e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_MOUNTED)) + return true; + + if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED)) + this._notifyActionError(action, e.message); + + if (action === RemovableAction.MOUNT && this._isEncryptedMountError(e)) { + delete this._currentAction; + operation.close(); + return this.launchAction(action); + } + + if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { + logError(e, 'Impossible to %s removable %s'.format(action, + removable.get_name())); + } + + return false; + } finally { + delete this._currentAction; + this.notify('busy'); + this._update(); + operation.close(); + } + } + + _isEncryptedMountError(error) { + // FIXME: we will always get G_IO_ERROR_FAILED from the gvfs udisks + // backend, see https://bugs.freedesktop.org/show_bug.cgi?id=51271 + + if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED)) + return false; + + // cryptsetup + if (error.message.includes('No key available with this passphrase')) + return true; + + // udisks (no password) + if (error.message.includes('No key available to unlock device')) + return true; + + // libblockdev wrong password opening LUKS device + if (error.message.includes('Failed to activate device: Incorrect passphrase')) + return true; + + // cryptsetup returns EINVAL in many cases, including wrong TCRYPT password/parameters + if (error.message.includes('Failed to load device\'s parameters: Invalid argument') || + error.message.includes(`Failed to load device's parameters: ${GLib.strerror(22 /* EINVAL */)}`)) + return true; + + // cryptsetup returns EPERM when the TCRYPT header can't be decrypted + // with the provided password/parameters. + if (error.message.includes('Failed to load device\'s parameters: Operation not permitted') || + error.message.includes(`Failed to load device's parameters: ${GLib.strerror(1 /* EPERM */)}`)) + return true; + + return false; + } +}); + +const TrashAppInfo = GObject.registerClass({ + Implements: [Gio.AppInfo], + Properties: { + 'empty': GObject.ParamSpec.boolean( + 'empty', 'empty', 'empty', + GObject.ParamFlags.READWRITE, + true), + }, +}, +class TrashAppInfo extends LocationAppInfo { + static initPromises(file) { + if (TrashAppInfo._promisified) + return; + + const trashProto = file.constructor.prototype; + Gio._promisify(Gio.FileEnumerator.prototype, 'close_async', 'close_finish'); + Gio._promisify(Gio.FileEnumerator.prototype, 'next_files_async', 'next_files_finish'); + Gio._promisify(trashProto, 'enumerate_children_async', 'enumerate_children_finish'); + Gio._promisify(trashProto, 'query_info_async', 'query_info_finish'); + TrashAppInfo._promisified = true; + } + + _init(cancellable = null) { + super._init({ + location: Gio.file_new_for_uri(TRASH_URI), + name: __('Trash'), + icon: Gio.ThemedIcon.new(FALLBACK_TRASH_ICON), + cancellable, + }); + TrashAppInfo.initPromises(this.location); + + try { + this._monitor = this.location.monitor_directory(0, this.cancellable); + this._schedUpdateId = 0; + this._monitorChangedId = this._monitor.connect('changed', () => + this._onTrashChange()); + } catch (e) { + if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) + return; + logError(e, 'Impossible to monitor trash'); + } + this._updateTrash(); + + this.connect('notify::empty', () => this._updateLocationIcon()); + this.notify('empty'); + } + + destroy() { + if (this._schedUpdateId) { + GLib.source_remove(this._schedUpdateId); + this._schedUpdateId = 0; + } + this._updateTrashCancellable?.cancel(); + this._monitor?.disconnect(this._monitorChangedId); + this._monitor = null; + + super.destroy(); + } + + list_actions() { + return this.empty ? [] : ['empty-trash']; + } + + get_action_name(action) { + switch (action) { + case 'empty-trash': + return __('Empty Trash'); + default: + return null; + } + } + + _onTrashChange() { + if (this._schedUpdateId) { + GLib.source_remove(this._schedUpdateId); + this._schedUpdateId = 0; + } + + if (this._monitor.is_cancelled()) + return; + + this._schedUpdateId = GLib.timeout_add(GLib.PRIORITY_LOW, + UPDATE_TRASH_DELAY, () => { + this._schedUpdateId = 0; + this._updateTrash(); + return GLib.SOURCE_REMOVE; + }); + } + + async _updateTrash() { + const priority = GLib.PRIORITY_LOW; + this._updateTrashCancellable?.cancel(); + const cancellable = new Utils.CancellableChild(this.cancellable); + this._updateTrashCancellable = cancellable; + + try { + const trashInfo = await this.location.query_info_async( + Gio.FILE_ATTRIBUTE_TRASH_ITEM_COUNT, + Gio.FileQueryInfoFlags.NONE, + priority, cancellable); + this.empty = !trashInfo.get_attribute_uint32( + Gio.FILE_ATTRIBUTE_TRASH_ITEM_COUNT); + return; + } catch (e) { + if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) + logError(e, 'Impossible to get trash children from infos'); + } finally { + cancellable.cancel(); + if (this._updateIconCancellable === cancellable) + delete this._updateTrashCancellable; + } + + try { + const childrenEnumerator = await this.location.enumerate_children_async( + Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, + priority, cancellable); + const children = await childrenEnumerator.next_files_async(1, + priority, cancellable); + this.empty = !children.length; + + await childrenEnumerator.close_async(priority, null); + } catch (e) { + if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) + logError(e, 'Impossible to enumerate trash children'); + } finally { + cancellable.cancel(); + if (this._updateIconCancellable === cancellable) + delete this._updateTrashCancellable; + } + } + + launchAction(action, timestamp) { + if (!this.list_actions().includes(action)) + throw new Error('Action %s is not supported by %s', action, this); + + const nautilus = makeNautilusFileOperationsProxy(); + const askConfirmation = true; + nautilus.EmptyTrashRemote(askConfirmation, + nautilus.platformData({timestamp}), (_p, error) => { + if (error) + logError(error, 'Empty trash failed'); + }, this.cancellable); + } +}); + +/** + * @param shellApp + */ +function wrapWindowsBackedApp(shellApp) { + if (shellApp._dtdData) + throw new Error('%s has been already wrapped'.format(shellApp)); + + shellApp._dtdData = { + windows: [], + state: undefined, + startingWorkspace: 0, + isFocused: false, + proxyProperties: [], + sources: new Set(), + signalConnections: new Utils.GlobalSignalsHandler(), + methodInjections: new Utils.InjectionsHandler(), + propertyInjections: new Utils.PropertyInjectionsHandler(), + addProxyProperties(parent, proxyProperties) { + Object.entries(proxyProperties).forEach(([p, o]) => { + const publicProp = o.public ? p : `_${p}`; + const get = o.getter && o.value instanceof Function + ? () => this[p]() : () => this[p]; + Object.defineProperty(parent, publicProp, Object.assign({ + get, + set: v => (this[p] = v), + configurable: true, + enumerable: !!o.enumerable, + }, o.readOnly ? {set: undefined} : {})); + if (o.value) + this[p] = o.value; + this.proxyProperties.push(publicProp); + }); + }, + destroy() { + this.windows = []; + this.proxyProperties = []; + this.sources.forEach(s => GLib.source_remove(s)); + this.sources.clear(); + this.signalConnections.destroy(); + this.methodInjections.destroy(); + this.propertyInjections.destroy(); + }, + }; + + shellApp._dtdData.addProxyProperties(shellApp, { + windows: {}, + state: {}, + startingWorkspace: {}, + isFocused: {public: true}, + signalConnections: {readOnly: true}, + sources: {readOnly: true}, + checkFocused: {}, + setDtdData: {}, + }); + + shellApp._setDtdData = function (data, params = {}) { + for (const [name, value] of Object.entries(data)) { + if (params.readOnly && name in this._dtdData) + throw new Error('Property %s is already defined'.format(name)); + const defaultParams = {public: true, readOnly: true}; + this._dtdData.addProxyProperties(this, { + [name]: {...defaultParams, ...params, value}, + }); + } + }; + + const m = (...args) => shellApp._dtdData.methodInjections.add(shellApp, ...args); + const p = (...args) => shellApp._dtdData.propertyInjections.add(shellApp, ...args); + + // mi is Method injector, pi is Property injector + shellApp._setDtdData({mi: m, pi: p}, {public: false}); + + m('get_state', () => shellApp._state ?? shellApp._getStateByWindows()); + p('state', {get: () => shellApp.get_state()}); + + m('get_windows', () => shellApp._windows); + m('get_n_windows', () => shellApp._windows.length); + m('get_pids', () => shellApp._windows.reduce((pids, w) => { + if (w.get_pid() > 0 && !pids.includes(w.get_pid())) + pids.push(w.get_pid()); + return pids; + }, [])); + m('is_on_workspace', (_om, workspace) => shellApp._windows.some(w => + w.get_workspace() === workspace) || + (shellApp.state === Shell.AppState.STARTING && + [-1, workspace.index()].includes(shellApp._startingWorkspace))); + m('request_quit', () => shellApp._windows.filter(w => + w.can_close()).forEach(w => w.delete(global.get_current_time()))); + + shellApp._setDtdData({ + _getStateByWindows() { + return this.get_n_windows() ? Shell.AppState.RUNNING : Shell.AppState.STOPPED; + }, + + _updateWindows() { + throw new GObject.NotImplementedError(`_updateWindows in ${this.constructor.name}`); + }, + + _notifyStateChanged() { + Shell.AppSystem.get_default().emit('app-state-changed', this); + this.notify('state'); + }, + + _setState(state) { + const oldState = this.state; + this._state = state; + + if (this.state !== oldState) + this._notifyStateChanged(); + }, + + _setWindows(windows) { + const oldState = this.state; + const oldWindows = this._windows.slice(); + const result = {windowsChanged: false, stateChanged: false}; + this._state = undefined; + + if (windows.length !== oldWindows.length || + windows.some((win, index) => win !== oldWindows[index])) { + this._windows = windows.filter(w => !w.is_override_redirect()); + this.emit('windows-changed'); + result.windowsChanged = true; + } + + if (this.state !== oldState) { + this._notifyStateChanged(); + this._checkFocused(); + result.stateChanged = true; + } + + return result; + }, + }, {readOnly: false}); + + shellApp._sources.add(GLib.idle_add(GLib.DEFAULT_PRIORITY, () => { + shellApp._updateWindows(); + shellApp._sources.delete(GLib.main_current_source().source_id); + return GLib.SOURCE_REMOVE; + })); + + const windowTracker = Shell.WindowTracker.get_default(); + shellApp._checkFocused = function () { + if (this._windows.some(w => w.has_focus())) { + this.isFocused = true; + windowTracker.notify('focus-app'); + } else if (this.isFocused) { + this.isFocused = false; + windowTracker.notify('focus-app'); + } + }; + + shellApp._checkFocused(); + shellApp._signalConnections.add(global.display, 'notify::focus-window', () => + shellApp._checkFocused()); + + // Re-implements shell_app_activate_window for generic activation and alt-tab support + m('activate_window', function (_om, window, timestamp) { + /* eslint-disable no-invalid-this */ + if (!window) + [window] = this.get_windows(); + else if (!this._windows.includes(window)) + return; + + const currentWorkspace = global.workspace_manager.get_active_workspace(); + const workspace = window.get_workspace(); + const sameWorkspaceWindows = this.get_windows().filter(w => + w.get_workspace() === workspace); + sameWorkspaceWindows.forEach(w => w.raise()); + + if (workspace !== currentWorkspace) + workspace.activate_with_focus(window, timestamp); + else + window.activate(timestamp); + /* eslint-enable no-invalid-this */ + }); + + // Re-implements shell_app_activate_full for generic activation and dash support + m('activate_full', function (_om, workspace, timestamp) { + /* eslint-disable no-invalid-this */ + if (!timestamp) + timestamp = global.get_current_time(); + + switch (this.state) { + case Shell.AppState.STOPPED: + try { + this._startingWorkspace = workspace; + this._setState(Shell.AppState.STARTING); + this.launch(timestamp, workspace, Shell.AppLaunchGpu.APP_PREF); + } catch (e) { + logError(e); + this._setState(Shell.AppState.STOPPED); + global.notify_error(_('Failed to launch “%s”'.format( + this.get_name())), e.message); + } + break; + case Shell.AppState.RUNNING: + this.activate_window(null, timestamp); + break; + } + /* eslint-enable no-invalid-this */ + }); + + m('activate', () => shellApp.activate_full(-1, 0)); + + m('compare', (_om, other) => Utils.shellAppCompare(shellApp, other)); + + const {destroy: defaultDestroy} = shellApp; + shellApp.destroy = function () { + /* eslint-disable no-invalid-this */ + this._dtdData.proxyProperties.forEach(prop => delete this[prop]); + this._dtdData.destroy(); + this._dtdData = undefined; + this.appInfo.destroy?.(); + this.destroy = defaultDestroy; + defaultDestroy?.call(this); + /* eslint-enable no-invalid-this */ + }; + + return shellApp; +} + +/** + * We can't inherit from Shell.App as it's a final type, so let's patch it + * + * @param params + */ +function makeLocationApp(params) { + if (!(params?.appInfo instanceof LocationAppInfo)) + throw new TypeError('Invalid location'); + + const {fallbackIconName} = params; + delete params.fallbackIconName; + + const shellApp = new Shell.App(params); + wrapWindowsBackedApp(shellApp); + + shellApp._setDtdData({ + location: () => shellApp.appInfo.location, + isTrash: shellApp.appInfo instanceof TrashAppInfo, + }, {getter: true, enumerable: true}); + + shellApp._mi('toString', defaultToString => + '[LocationApp "%s" - %s]'.format(shellApp.get_id(), + defaultToString.call(shellApp))); + + shellApp._mi('launch', (_om, timestamp, workspace, _gpuPref) => + shellApp.appInfo.launch([], + global.create_app_launch_context(timestamp, workspace))); + + shellApp._mi('launch_action', (_om, actionName, ...args) => + shellApp.appInfo.launchAction(actionName, ...args)); + + shellApp._mi('create_icon_texture', (_om, iconSize) => new St.Icon({ + iconSize, + gicon: shellApp.icon, + fallbackIconName, + })); + + // FIXME: We need to add a new API to Nautilus to open new windows + shellApp._mi('can_open_new_window', () => { + try { + if (!shellApp.get_n_windows()) + return true; + + const handlerApp = shellApp.appInfo.getHandlerApp(); + + if (handlerApp.has_key('SingleMainWindow')) + return !handlerApp.get_boolean('SingleMainWindow'); + + if (handlerApp.has_key('X-GNOME-SingleWindow')) + return !handlerApp.get_boolean('X-GNOME-SingleWindow'); + + if (handlerApp.get_commandline()?.split(' ').includes('--new-window')) + return true; + + const [window] = shellApp.get_windows(); + if (window && window.get_gtk_window_object_path()) + return window.get_gtk_application_id() === null; + + return true; + } catch { + return false; + } + }); + + shellApp._mi('open_new_window', function (_om, workspace) { + /* eslint-disable no-invalid-this */ + const context = global.create_app_launch_context(0, workspace); + if (!this.get_n_windows()) { + this.appInfo.launch([], context); + return; + } + const appId = this.appInfo.get_id(); + Gio.AppInfo.create_from_commandline(this.appInfo.get_commandline(), + this.appInfo.get_id(), appId + ? Gio.AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION + : Gio.AppInfoCreateFlags.NONE).launch_uris( + [this.appInfo.location.get_uri()], context); + /* eslint-enable no-invalid-this */ + }); + + if (shellApp.appInfo instanceof MountableVolumeAppInfo) { + shellApp._mi('get_busy', function (parentGetBusy) { + /* eslint-disable no-invalid-this */ + if (this.appInfo.busy) + return true; + return parentGetBusy.call(this); + /* eslint-enable no-invalid-this */ + }); + shellApp._pi('busy', {get: () => shellApp.get_busy()}); + shellApp._signalConnections.add(shellApp.appInfo, 'notify::busy', _ => + shellApp.notify('busy')); + } + + shellApp._mi('get_windows', function () { + /* eslint-disable no-invalid-this */ + if (this._needsResort) + this._sortWindows(); + return this._windows; + /* eslint-enable no-invalid-this */ + }); + + const {fm1Client} = Docking.DockManager.getDefault(); + shellApp._setDtdData({ + _needsResort: true, + + _windowsOrderChanged() { + this._needsResort = true; + this.emit('windows-changed'); + }, + + _sortWindows() { + this._windows.sort(Utils.shellWindowsCompare); + this._needsResort = false; + }, + + _updateWindows() { + const windows = fm1Client.getWindows(this.location?.get_uri()).sort( + Utils.shellWindowsCompare); + const {windowsChanged} = this._setWindows(windows); + + if (!windowsChanged) + return; + + this._signalConnections.removeWithLabel(Labels.LOCATION_WINDOWS); + windows.forEach(w => + this._signalConnections.addWithLabel(Labels.LOCATION_WINDOWS, w, + 'notify::user-time', () => { + if (w !== this._windows[0]) + this._windowsOrderChanged(); + })); + }, + }, {readOnly: false}); + + shellApp._signalConnections.add(fm1Client, 'windows-changed', () => + shellApp._updateWindows()); + shellApp._signalConnections.add(shellApp.appInfo, 'notify::icon', () => + shellApp.notify('icon')); + shellApp._signalConnections.add(global.workspaceManager, + 'workspace-switched', () => shellApp._windowsOrderChanged()); + + return shellApp; +} + +/** + * + */ +function getFileManagerApp() { + return Shell.AppSystem.get_default().lookup_app(FILE_MANAGER_DESKTOP_APP_ID); +} + +/** + * + */ +export function wrapFileManagerApp() { + const fileManagerApp = getFileManagerApp(); + if (!fileManagerApp) + return null; + + if (fileManagerApp._dtdData) + return fileManagerApp; + + const originalGetWindows = fileManagerApp.get_windows; + wrapWindowsBackedApp(fileManagerApp); + + const {removables, trash} = Docking.DockManager.getDefault(); + fileManagerApp._signalConnections.addWithLabel(Labels.WINDOWS_CHANGED, + fileManagerApp, 'windows-changed', () => { + fileManagerApp.stop_emission_by_name('windows-changed'); + // Let's wait for the location app to take control before of us + const id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, () => { + fileManagerApp._sources.delete(id); + fileManagerApp._updateWindows(); + return GLib.SOURCE_REMOVE; + }); + fileManagerApp._sources.add(id); + }); + + fileManagerApp._signalConnections.add(global.workspaceManager, + 'workspace-switched', () => { + fileManagerApp._signalConnections.blockWithLabel(Labels.WINDOWS_CHANGED); + fileManagerApp.emit('windows-changed'); + fileManagerApp._signalConnections.unblockWithLabel(Labels.WINDOWS_CHANGED); + }); + + if (removables) { + fileManagerApp._signalConnections.add(removables, 'changed', () => + fileManagerApp._updateWindows()); + fileManagerApp._signalConnections.add(removables, 'windows-changed', () => + fileManagerApp._updateWindows()); + } + + if (trash?.getApp()) { + fileManagerApp._signalConnections.add(trash.getApp(), 'windows-changed', () => + fileManagerApp._updateWindows()); + } + + fileManagerApp._updateWindows = function () { + const locationWindows = []; + getRunningApps().forEach(a => locationWindows.push(...a.get_windows())); + const windows = originalGetWindows.call(this).filter(w => + !locationWindows.includes(w)); + + this._signalConnections.blockWithLabel(Labels.WINDOWS_CHANGED); + this._setWindows(windows); + this._signalConnections.unblockWithLabel(Labels.WINDOWS_CHANGED); + }; + + fileManagerApp._mi('toString', defaultToString => + '[FileManagerApp - %s]'.format(defaultToString.call(fileManagerApp))); + + return fileManagerApp; +} + +/** + * + */ +export function unWrapFileManagerApp() { + const fileManagerApp = getFileManagerApp(); + if (!fileManagerApp || !fileManagerApp._dtdData) + return; + + fileManagerApp.destroy(); +} + +/** + * This class maintains a Shell.App representing the Trash and keeps it + * up-to-date as the trash fills and is emptied over time. + */ +export class Trash { + destroy() { + this._trashApp?.destroy(); + } + + _ensureApp() { + if (this._trashApp) + return; + + this._trashApp = makeLocationApp({ + appInfo: new TrashAppInfo(new Gio.Cancellable()), + fallbackIconName: FALLBACK_TRASH_ICON, + }); + } + + getApp() { + this._ensureApp(); + return this._trashApp; + } +} + +/** + * This class maintains Shell.App representations for removable devices + * plugged into the system, and keeps the list of Apps up-to-date as + * devices come and go and are mounted and unmounted. + */ +export class Removables { + static initVolumePromises(object) { + // TODO: This can be simplified using actual interface type when we + // can depend on gjs 1.72 + if (!(object instanceof Gio.Volume) || object.constructor.prototype._d2dPromisified) + return; + + Gio._promisify(object.constructor.prototype, 'mount', 'mount_finish'); + Gio._promisify(object.constructor.prototype, 'eject_with_operation', + 'eject_with_operation_finish'); + object.constructor.prototype._d2dPromisified = true; + } + + static initMountPromises(object) { + // TODO: This can be simplified using actual interface type when we + // can depend on gjs 1.72 + if (!(object instanceof Gio.Mount) || object.constructor.prototype._d2dPromisified) + return; + + Gio._promisify(object.constructor.prototype, 'eject_with_operation', + 'eject_with_operation_finish'); + Gio._promisify(object.constructor.prototype, 'unmount_with_operation', + 'unmount_with_operation_finish'); + object.constructor.prototype._d2dPromisified = true; + } + + constructor() { + this._signalsHandler = new Utils.GlobalSignalsHandler(); + + this._monitor = Gio.VolumeMonitor.get(); + this._cancellable = new Gio.Cancellable(); + + this._monitor.get_mounts().forEach(m => Removables.initMountPromises(m)); + this._updateVolumes(); + + this._signalsHandler.add([ + this._monitor, + 'volume-added', + (_, volume) => this._onVolumeAdded(volume), + ], [ + this._monitor, + 'volume-removed', + (_, volume) => this._onVolumeRemoved(volume), + ], [ + this._monitor, + 'mount-added', + (_, mount) => this._onMountAdded(mount), + ], [ + Docking.DockManager.settings, + 'changed::show-mounts-only-mounted', + () => this._updateVolumes(), + ], [ + Docking.DockManager.settings, + 'changed::show-mounts-network', + () => this._updateVolumes(), + ]); + } + + destroy() { + this._volumeApps.forEach(a => a.destroy()); + this._volumeApps = []; + this._cancellable.cancel(); + this._cancellable = null; + this._signalsHandler.destroy(); + this._monitor = null; + } + + _updateVolumes() { + this._volumeApps?.forEach(a => a.destroy()); + this._volumeApps = []; + this.emit('changed'); + + this._monitor.get_volumes().forEach(v => this._onVolumeAdded(v)); + } + + _onVolumeAdded(volume) { + Removables.initVolumePromises(volume); + + if (!Docking.DockManager.settings.showMountsNetwork && + volume.get_identifier('class') === 'network') + return; + + + const mount = volume.get_mount(); + if (mount) { + if (mount.is_shadowed()) + return; + if (!mount.can_eject() && !mount.can_unmount()) + return; + } else { + if (Docking.DockManager.settings.showMountsOnlyMounted) + return; + if (!volume.can_mount() && !volume.can_eject()) + return; + } + + const appInfo = new MountableVolumeAppInfo(volume, + new Utils.CancellableChild(this._cancellable)); + const volumeApp = makeLocationApp({ + appInfo, + fallbackIconName: FALLBACK_REMOVABLE_MEDIA_ICON, + }); + + volumeApp._signalConnections.add(volumeApp, 'windows-changed', + () => this.emit('windows-changed', volumeApp)); + + if (Docking.DockManager.settings.showMountsOnlyMounted) { + volumeApp._signalConnections.add(appInfo, 'notify::mount', + () => !appInfo.mount && this._onVolumeRemoved(appInfo.volume)); + } + + this._volumeApps.push(volumeApp); + this.emit('changed'); + } + + _onVolumeRemoved(volume) { + const volumeIndex = this._volumeApps.findIndex(({appInfo}) => + appInfo.volume === volume); + if (volumeIndex !== -1) { + const [volumeApp] = this._volumeApps.splice(volumeIndex, 1); + // We don't care about cancelling the ongoing operations from now on. + volumeApp.appInfo.cancellable = null; + volumeApp.destroy(); + this.emit('changed'); + } + } + + _onMountAdded(mount) { + Removables.initMountPromises(mount); + + if (!Docking.DockManager.settings.showMountsOnlyMounted) + return; + + if (!this._volumeApps.find(({appInfo}) => appInfo.mount === mount)) { + // In some Gio.Mount implementations the volume may be set after + // mount is emitted, so we could just ignore it as we'll get it + // later via volume-added + const volume = mount.get_volume(); + if (volume) + this._onVolumeAdded(volume); + } + } + + getApps() { + return this._volumeApps; + } +} +Signals.addSignalMethods(Removables.prototype); + +/** + * + */ +function getApps() { + const dockManager = Docking.DockManager.getDefault(); + const locationApps = []; + + if (dockManager.removables) + locationApps.push(...dockManager.removables.getApps()); + + if (dockManager.trash) + locationApps.push(dockManager.trash.getApp()); + + return locationApps; +} + +/** + * + */ +export function getRunningApps() { + return getApps().filter(a => a.state === Shell.AppState.RUNNING); +} + +/** + * + */ +export function getStartingApps() { + return getApps().filter(a => a.state === Shell.AppState.STARTING); +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locationsWorker.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locationsWorker.js new file mode 100644 index 0000000..054c92c --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/locationsWorker.js @@ -0,0 +1,91 @@ +#!/usr/bin/env gjs + +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; + +const GJS_SUPPORTS_FILE_IFACE_PROMISES = imports.system.version >= 17101; + +if (GJS_SUPPORTS_FILE_IFACE_PROMISES) + Gio._promisify(Gio.File.prototype, 'query_default_handler_async'); + +function getHandlerAppAsync(location, cancellable) { + if (!location) + return null; + + if (!GJS_SUPPORTS_FILE_IFACE_PROMISES) { + Gio._promisify(location.constructor.prototype, + 'query_default_handler_async', + 'query_default_handler_finish'); + } + + return location.query_default_handler_async( + GLib.PRIORITY_DEFAULT, cancellable); +} + +async function mainAsync(argv) { + if (argv.length < 1) { + const currentBinary = GLib.path_get_basename(new Error().fileName); + printerr(`Usage: ${currentBinary} uri [ --timeout ]`); + return 1; + } + + const [action, uri] = argv; + let timeout = 200; + + if (action !== 'handler') + throw new TypeError(`Unexpected action ${action}`); + + for (let i = 1; i < argv.length; ++i) { + if (argv[i] === '--timeout' && i < argv.length - 1) + timeout = argv[++i]; + } + + const location = Gio.File.new_for_uri(uri); + const cancellable = new Gio.Cancellable(); + + // GVfs providers could hang when querying the file information, so we + // workaround this by using the async API in a sync way, but we need to + // use a timeout to avoid this to hang forever, better than hang the + // shell. + let launchMaxWaitId; + + try { + const handler = await Promise.race([ + getHandlerAppAsync(location, cancellable), + new Promise((_resolve, reject) => { + launchMaxWaitId = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, timeout, () => { + launchMaxWaitId = 0; + cancellable.cancel(); + reject(new GLib.Error(Gio.IOErrorEnum, + Gio.IOErrorEnum.TIMED_OUT, + `Searching for ${location.get_uri()} ` + + 'handler took too long')); + return GLib.SOURCE_REMOVE; + }); + }), + ]); + + print(handler.get_id()); + } catch (e) { + printerr(e.message); + logError(e); + return e.code ? e.code : GLib.MAXUINT8; + } finally { + if (launchMaxWaitId) + GLib.source_remove(launchMaxWaitId); + } + + return 0; +} + +function main(args) { + let ret; + const loop = new GLib.MainLoop(null, false); + mainAsync(args).then(r => (ret = r)).catch(logError).finally(() => loop.quit()); + loop.run(); + + return ret; +} + +imports.system.exit(main(ARGV)); diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/glossy.svg b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/glossy.svg new file mode 100644 index 0000000..55b71ba --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/glossy.svg @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/highlight_stacked_bg.svg b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/highlight_stacked_bg.svg new file mode 100644 index 0000000..19be5a9 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/highlight_stacked_bg.svg @@ -0,0 +1,82 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/highlight_stacked_bg_h.svg b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/highlight_stacked_bg_h.svg new file mode 100644 index 0000000..eeaa869 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/highlight_stacked_bg_h.svg @@ -0,0 +1,82 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/logo.svg b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/logo.svg new file mode 100644 index 0000000..eebd0b1 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/media/logo.svg @@ -0,0 +1,528 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Dash to Dock + Michele + + + + + + + + + + + + + + + + + + + + + + diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/metadata.json b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/metadata.json new file mode 100644 index 0000000..cbb546f --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/metadata.json @@ -0,0 +1,18 @@ +{ + "_generated": "Generated by SweetTooth, do not edit", + "description": "A dock for the Gnome Shell. This extension moves the dash out of the overview transforming it in a dock for an easier launching of applications and a faster switching between windows and desktops. Side and bottom placement options are available.", + "gettext-domain": "dashtodock", + "name": "Dash to Dock", + "original-author": "micxgx@gmail.com", + "shell-version": [ + "45", + "46", + "47", + "48", + "49", + "50" + ], + "url": "https://micheleg.github.io/dash-to-dock/", + "uuid": "dash-to-dock@micxgx.gmail.com", + "version": 104 +} \ No newline at end of file diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/notificationsMonitor.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/notificationsMonitor.js new file mode 100644 index 0000000..7dca35d --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/notificationsMonitor.js @@ -0,0 +1,121 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import {Gio} from './dependencies/gi.js'; +import {Main} from './dependencies/shell/ui.js'; + +import { + Docking, + Utils, +} from './imports.js'; + +const {signals: Signals} = imports; + +const Labels = Object.freeze({ + SOURCES: Symbol('sources'), + NOTIFICATIONS: Symbol('notifications'), +}); +export class NotificationsMonitor { + constructor() { + this._settings = new Gio.Settings({ + schema_id: 'org.gnome.desktop.notifications', + }); + + this._appNotifications = Object.create(null); + this._signalsHandler = new Utils.GlobalSignalsHandler(this); + + const getIsEnabled = () => !this.dndMode && + Docking.DockManager.settings.showIconsNotificationsCounter; + + this._isEnabled = getIsEnabled(); + const checkIsEnabled = () => { + const isEnabled = getIsEnabled(); + if (isEnabled !== this._isEnabled) { + this._isEnabled = isEnabled; + this.emit('state-changed'); + + this._updateState(); + } + }; + + this._dndMode = !this._settings.get_boolean('show-banners'); + this._signalsHandler.add(this._settings, 'changed::show-banners', () => { + this._dndMode = !this._settings.get_boolean('show-banners'); + checkIsEnabled(); + }); + this._signalsHandler.add(Docking.DockManager.settings, + 'changed::show-icons-notifications-counter', checkIsEnabled); + + this._updateState(); + } + + destroy() { + this.emit('destroy'); + this._signalsHandler?.destroy(); + this._signalsHandler = null; + this._appNotifications = null; + this._settings = null; + } + + get enabled() { + return this._isEnabled; + } + + get dndMode() { + return this._dndMode; + } + + getAppNotificationsCount(appId) { + return this._appNotifications[appId] ?? 0; + } + + _updateState() { + if (this.enabled) { + this._signalsHandler.addWithLabel(Labels.SOURCES, Main.messageTray, + 'source-added', () => this._checkNotifications()); + this._signalsHandler.addWithLabel(Labels.SOURCES, Main.messageTray, + 'source-removed', () => this._checkNotifications()); + } else { + this._signalsHandler.removeWithLabel(Labels.SOURCES); + } + + this._checkNotifications(); + } + + _checkNotifications() { + this._appNotifications = Object.create(null); + this._signalsHandler.removeWithLabel(Labels.NOTIFICATIONS); + + if (this.enabled) { + Main.messageTray.getSources().forEach(source => { + this._signalsHandler.addWithLabel(Labels.NOTIFICATIONS, source, + 'notification-added', () => this._checkNotifications()); + + source.notifications.forEach(notification => { + const app = notification.source?.app ?? notification.source?._app; + const appId = app?.id ?? app?._appId; + + if (appId) { + if (notification.resident) { + if (notification.acknowledged) + return; + + this._signalsHandler.addWithLabel(Labels.NOTIFICATIONS, + notification, 'notify::acknowledged', + () => this._checkNotifications()); + } + + this._signalsHandler.addWithLabel(Labels.NOTIFICATIONS, + notification, 'destroy', () => this._checkNotifications()); + + this._appNotifications[appId] = + (this._appNotifications[appId] ?? 0) + 1; + } + }); + }); + } + + this.emit('changed'); + } +} + +Signals.addSignalMethods(NotificationsMonitor.prototype); diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/prefs.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/prefs.js new file mode 100644 index 0000000..fae0b99 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/prefs.js @@ -0,0 +1,1168 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import GLib from 'gi://GLib'; +import GObject from 'gi://GObject'; +import Gdk from 'gi://Gdk'; +import Gio from 'gi://Gio'; +import Gtk from 'gi://Gtk'; + +import { + ExtensionPreferences, + + // Use __ () and N__() for the extension gettext domain, and reuse + // the shell domain with the default _() and N_() + gettext as __, +} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; + +const SCALE_UPDATE_TIMEOUT = 500; +const DEFAULT_ICONS_SIZES = [128, 96, 64, 48, 32, 24, 16]; + +const TransparencyMode = Object.freeze({ + DEFAULT: 0, + FIXED: 1, + DYNAMIC: 3, +}); + +const RunningIndicatorStyle = Object.freeze({ + DEFAULT: 0, + DOTS: 1, + SQUARES: 2, + DASHES: 3, + SEGMENTED: 4, + SOLID: 5, + CILIORA: 6, + METRO: 7, +}); + +const MonitorsConfig = GObject.registerClass({ + Signals: { + 'updated': {}, + }, +}, class MonitorsConfig extends GObject.Object { + static get XML_INTERFACE() { + return '\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + '; + } + + static get ProxyWrapper() { + return Gio.DBusProxy.makeProxyWrapper(MonitorsConfig.XML_INTERFACE); + } + + constructor() { + super(); + + this._monitorsConfigProxy = new MonitorsConfig.ProxyWrapper( + Gio.DBus.session, + 'org.gnome.Mutter.DisplayConfig', + '/org/gnome/Mutter/DisplayConfig' + ); + + // Connecting to a D-Bus signal + this._monitorsConfigProxy.connectSignal('MonitorsChanged', + () => this._updateResources()); + + this._primaryMonitor = null; + this._monitors = []; + this._logicalMonitors = []; + + this._updateResources(); + } + + _updateResources() { + this._monitorsConfigProxy.GetCurrentStateRemote((resources, err) => { + if (err) { + logError(err); + return; + } + + const [serial_, monitors, logicalMonitors] = resources; + let index = 0; + for (const monitor of monitors) { + const [monitorSpecs, modes_, props] = monitor; + const [connector, vendor, product, serial] = monitorSpecs; + this._monitors.push({ + index: index++, + active: false, + connector, vendor, product, serial, + displayName: props['display-name'].unpack(), + }); + } + + for (const logicalMonitor of logicalMonitors) { + const [x_, y_, scale_, transform_, isPrimary, monitorsSpecs] = + logicalMonitor; + + // We only care about the first one really + for (const monitorSpecs of monitorsSpecs) { + const [connector, vendor, product, serial] = monitorSpecs; + const monitor = this._monitors.find(m => + m.connector === connector && m.vendor === vendor && + m.product === product && m.serial === serial); + + if (monitor) { + monitor.active = true; + monitor.isPrimary = isPrimary; + if (monitor.isPrimary) + this._primaryMonitor = monitor; + break; + } + } + } + + const activeMonitors = this._monitors.filter(m => m.active); + if (activeMonitors.length > 1 && logicalMonitors.length === 1) { + // We're in cloning mode, so let's just activate the primary monitor + this._monitors.forEach(m => (m.active = false)); + this._primaryMonitor.active = true; + } + + this._updateMonitorsIndexes(); + this.emit('updated'); + }); + } + + _updateMonitorsIndexes() { + // This function ensures that we follow the old Gdk indexing strategy + // for monitors, it can be removed when we don't care about breaking + // old user configurations or external apps configuring this extension + // such as ubuntu's gnome-control-center. + const {index: primaryMonitorIndex} = this._primaryMonitor; + for (const monitor of this._monitors) { + let {index} = monitor; + // The The dock uses the Gdk index for monitors, where the primary monitor + // always has index 0, so let's follow what dash-to-dock does in docking.js + // (as part of _createDocks), but using inverted math + index -= primaryMonitorIndex; + + if (index < 0) + index += this._monitors.length; + + monitor.index = index; + } + } + + get primaryMonitor() { + return this._primaryMonitor; + } + + get monitors() { + return this._monitors; + } +}); + +/** + * @param settings + */ +function setShortcut(settings) { + const shortcutText = settings.get_string('shortcut-text'); + const [success, key, mods] = Gtk.accelerator_parse(shortcutText); + + if (success && Gtk.accelerator_valid(key, mods)) { + const shortcut = Gtk.accelerator_name(key, mods); + settings.set_strv('shortcut', [shortcut]); + } else { + settings.set_strv('shortcut', []); + } +} + +const DockSettings = GObject.registerClass({ + Implements: [Gtk.BuilderScope], +}, class DashToDockSettings extends GObject.Object { + _init(extensionPreferences) { + super._init(); + + this._extensionPreferences = extensionPreferences; + this._settings = extensionPreferences.getSettings( + 'org.gnome.shell.extensions.dash-to-dock'); + this._appSwitcherSettings = new Gio.Settings({schema_id: 'org.gnome.shell.app-switcher'}); + this._rtl = Gtk.Widget.get_default_direction() === Gtk.TextDirection.RTL; + + this._builder = new Gtk.Builder(); + this._builder.set_scope(this); + this._builder.set_translation_domain( + extensionPreferences.metadata['gettext-domain']); + this._builder.add_from_file(`${extensionPreferences.path}/Settings.ui`); + + this.widget = this._builder.get_object('settings_notebook'); + + // Set a reasonable initial window height + this.widget.connect('realize', () => { + const rootWindow = this.widget.get_root(); + rootWindow.set_default_size(-1, 850); + rootWindow.connect('close-request', () => this._onWindowsClosed()); + }); + + // Timeout to delay the update of the settings + this._dock_size_timeout = 0; + this._icon_size_timeout = 0; + this._opacity_timeout = 0; + + this._monitorsConfig = new MonitorsConfig(); + this._bindSettings(); + } + + _onWindowsClosed() { + if (this._dock_size_timeout) { + GLib.source_remove(this._dock_size_timeout); + delete this._dock_size_timeout; + } + + if (this._icon_size_timeout) { + GLib.source_remove(this._icon_size_timeout); + delete this._icon_size_timeout; + } + + if (this._opacity_timeout) { + GLib.source_remove(this._opacity_timeout); + delete this._opacity_timeout; + } + } + + vfunc_create_closure(builder, handlerName, flags, connectObject) { + if (flags & Gtk.BuilderClosureFlags.SWAPPED) + throw new Error('Unsupported template signal flag "swapped"'); + + if (typeof this[handlerName] === 'undefined') + throw new Error(`${handlerName} is undefined`); + + return this[handlerName].bind(connectObject || this); + } + + dock_display_combo_changed_cb(combo) { + if (!this._monitors?.length || this._updatingSettings) + return; + + const preferredMonitor = this._monitors[combo.get_active()].connector; + + this._updatingSettings = true; + this._settings.set_string('preferred-monitor-by-connector', preferredMonitor); + this._settings.set_int('preferred-monitor', -2); + this._updatingSettings = false; + } + + position_top_button_toggled_cb(button) { + if (button.get_active()) + this._settings.set_enum('dock-position', 0); + } + + position_right_button_toggled_cb(button) { + if (button.get_active()) + this._settings.set_enum('dock-position', 1); + } + + position_bottom_button_toggled_cb(button) { + if (button.get_active()) + this._settings.set_enum('dock-position', 2); + } + + position_left_button_toggled_cb(button) { + if (button.get_active()) + this._settings.set_enum('dock-position', 3); + } + + icon_size_combo_changed_cb(combo) { + this._settings.set_int('dash-max-icon-size', this._allIconSizes[combo.get_active()]); + } + + dock_size_scale_value_changed_cb(scale) { + // Avoid settings the size continuously + if (this._dock_size_timeout > 0) + GLib.source_remove(this._dock_size_timeout); + this._dock_size_timeout = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, SCALE_UPDATE_TIMEOUT, () => { + this._settings.set_double('height-fraction', scale.get_value()); + this._dock_size_timeout = 0; + return GLib.SOURCE_REMOVE; + }); + } + + icon_size_scale_value_changed_cb(scale) { + // Avoid settings the size consinuosly + if (this._icon_size_timeout > 0) + GLib.source_remove(this._icon_size_timeout); + this._icon_size_timeout = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, SCALE_UPDATE_TIMEOUT, () => { + this._settings.set_int('dash-max-icon-size', scale.get_value()); + this._icon_size_timeout = 0; + return GLib.SOURCE_REMOVE; + }); + } + + preview_size_scale_format_value_cb(scale, value) { + return value === 0 ? 'auto' : value; + } + + preview_size_scale_value_changed_cb(scale) { + this._settings.set_double('preview-size-scale', scale.get_value()); + } + + custom_opacity_scale_value_changed_cb(scale) { + // Avoid settings the opacity consinuosly as it's change is animated + if (this._opacity_timeout > 0) + GLib.source_remove(this._opacity_timeout); + this._opacity_timeout = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, SCALE_UPDATE_TIMEOUT, () => { + this._settings.set_double('background-opacity', scale.get_value()); + this._opacity_timeout = 0; + return GLib.SOURCE_REMOVE; + }); + } + + min_opacity_scale_value_changed_cb(scale) { + // Avoid settings the opacity consinuosly as it's change is animated + if (this._opacity_timeout > 0) + GLib.source_remove(this._opacity_timeout); + this._opacity_timeout = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, SCALE_UPDATE_TIMEOUT, () => { + this._settings.set_double('min-alpha', scale.get_value()); + this._opacity_timeout = 0; + return GLib.SOURCE_REMOVE; + }); + } + + max_opacity_scale_value_changed_cb(scale) { + // Avoid settings the opacity consinuosly as it's change is animated + if (this._opacity_timeout > 0) + GLib.source_remove(this._opacity_timeout); + this._opacity_timeout = GLib.timeout_add( + GLib.PRIORITY_DEFAULT, SCALE_UPDATE_TIMEOUT, () => { + this._settings.set_double('max-alpha', scale.get_value()); + this._opacity_timeout = 0; + return GLib.SOURCE_REMOVE; + }); + } + + all_windows_radio_button_toggled_cb(button) { + if (button.get_active()) + this._settings.set_enum('intellihide-mode', 0); + } + + focus_application_windows_radio_button_toggled_cb(button) { + if (button.get_active()) + this._settings.set_enum('intellihide-mode', 1); + } + + maximized_windows_radio_button_toggled_cb(button) { + if (button.get_active()) + this._settings.set_enum('intellihide-mode', 2); + } + + always_on_top_radio_button_toggled_cb(button) { + if (button.get_active()) + this._settings.set_enum('intellihide-mode', 3); + } + + _updateMonitorsSettings() { + // Monitor options + const preferredMonitor = this._settings.get_int('preferred-monitor'); + const preferredMonitorByConnector = this._settings.get_string('preferred-monitor-by-connector'); + const dockMonitorCombo = this._builder.get_object('dock_monitor_combo'); + + this._monitors = []; + dockMonitorCombo.remove_all(); + let primaryIndex = -1; + + // Add connected monitors + for (const monitor of this._monitorsConfig.monitors) { + if (!monitor.active && monitor.index !== preferredMonitor) + continue; + + if (monitor.isPrimary) { + dockMonitorCombo.append_text( + /* Translators: This will be followed by Display Name - Connector. */ + `${__('Primary monitor: ') + monitor.displayName} - ${ + monitor.connector}`); + primaryIndex = this._monitors.length; + } else { + dockMonitorCombo.append_text( + /* Translators: Followed by monitor index, Display Name - Connector. */ + `${__('Secondary monitor ') + (monitor.index + 1)} - ${ + monitor.displayName} - ${monitor.connector}`); + } + + this._monitors.push(monitor); + + if (monitor.index === preferredMonitor || + (preferredMonitor === -2 && preferredMonitorByConnector === monitor.connector)) + dockMonitorCombo.set_active(this._monitors.length - 1); + } + + if (dockMonitorCombo.get_active() < 0 && primaryIndex >= 0) + dockMonitorCombo.set_active(primaryIndex); + } + + _bindSettings() { + // Position and size panel + + this._updateMonitorsSettings(); + this._monitorsConfig.connect('updated', + () => this._updateMonitorsSettings()); + this._settings.connect('changed::preferred-monitor', + () => this._updateMonitorsSettings()); + this._settings.connect('changed::preferred-monitor-by-connector', + () => this._updateMonitorsSettings()); + + // Position option + const position = this._settings.get_enum('dock-position'); + + switch (position) { + case 0: + this._builder.get_object('position_top_button').set_active(true); + break; + case 1: + this._builder.get_object('position_right_button').set_active(true); + break; + case 2: + this._builder.get_object('position_bottom_button').set_active(true); + break; + case 3: + this._builder.get_object('position_left_button').set_active(true); + break; + } + + if (this._rtl) { + /* Left is Right in rtl as a setting */ + this._builder.get_object('position_left_button').set_label(__('Right')); + this._builder.get_object('position_right_button').set_label(__('Left')); + } + + // Intelligent autohide options + this._settings.bind('dock-fixed', + this._builder.get_object('intelligent_autohide_switch'), + 'active', + Gio.SettingsBindFlags.INVERT_BOOLEAN); + this._settings.bind('dock-fixed', + this._builder.get_object('intelligent_autohide_button'), + 'sensitive', + Gio.SettingsBindFlags.INVERT_BOOLEAN); + this._settings.bind('autohide', + this._builder.get_object('autohide_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('autohide-in-fullscreen', + this._builder.get_object('autohide_enable_in_fullscreen_checkbutton'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-dock-urgent-notify', + this._builder.get_object('show_dock_urgent_notify_checkbutton'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('require-pressure-to-show', + this._builder.get_object('require_pressure_checkbutton'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('intellihide', + this._builder.get_object('intellihide_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('animation-time', + this._builder.get_object('animation_duration_spinbutton'), + 'value', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('hide-delay', + this._builder.get_object('hide_timeout_spinbutton'), + 'value', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-delay', + this._builder.get_object('show_timeout_spinbutton'), + 'value', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('pressure-threshold', + this._builder.get_object('pressure_threshold_spinbutton'), + 'value', + Gio.SettingsBindFlags.DEFAULT); + + // this._builder.get_object('animation_duration_spinbutton').set_value( + // this._settings.get_double('animation-time')); + + // Create dialog for intelligent autohide advanced settings + this._builder.get_object('intelligent_autohide_button').connect('clicked', () => { + const dialog = new Gtk.Dialog({ + title: __('Intelligent autohide customization'), + transient_for: this.widget.get_root(), + use_header_bar: true, + modal: true, + }); + + // GTK+ leaves positive values for application-defined response ids. + // Use +1 for the reset action + dialog.add_button(__('Reset to defaults'), 1); + + const box = this._builder.get_object('intelligent_autohide_advanced_settings_box'); + dialog.get_content_area().append(box); + + this._settings.bind('intellihide', + this._builder.get_object('intellihide_mode_box'), + 'sensitive', + Gio.SettingsBindFlags.GET); + + // intellihide mode + + const intellihideModeRadioButtons = [ + this._builder.get_object('all_windows_radio_button'), + this._builder.get_object('focus_application_windows_radio_button'), + this._builder.get_object('maximized_windows_radio_button'), + this._builder.get_object('always_on_top_radio_button'), + ]; + + intellihideModeRadioButtons[this._settings.get_enum('intellihide-mode')].set_active(true); + + this._settings.bind('autohide', + this._builder.get_object('require_pressure_checkbutton'), + 'sensitive', + Gio.SettingsBindFlags.GET); + + this._settings.bind('autohide', + this._builder.get_object('autohide_enable_in_fullscreen_checkbutton'), + 'sensitive', + Gio.SettingsBindFlags.GET); + + this._settings.bind('autohide', + this._builder.get_object('show_dock_urgent_notify_checkbutton'), + 'sensitive', + Gio.SettingsBindFlags.GET); + + this._settings.bind('require-pressure-to-show', + this._builder.get_object('show_timeout_spinbutton'), + 'sensitive', + Gio.SettingsBindFlags.INVERT_BOOLEAN); + this._settings.bind('require-pressure-to-show', + this._builder.get_object('show_timeout_label'), + 'sensitive', + Gio.SettingsBindFlags.INVERT_BOOLEAN); + this._settings.bind('require-pressure-to-show', + this._builder.get_object('pressure_threshold_spinbutton'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('require-pressure-to-show', + this._builder.get_object('pressure_threshold_label'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT); + + dialog.connect('response', (_, id) => { + if (id === 1) { + // restore default settings for the relevant keys + const keys = ['intellihide', 'autohide', 'intellihide-mode', + 'autohide-in-fullscreen', 'show-dock-urgent-notify', + 'require-pressure-to-show', 'animation-time', + 'show-delay', 'hide-delay', 'pressure-threshold']; + keys.forEach(function (val) { + this._settings.set_value(val, this._settings.get_default_value(val)); + }, this); + intellihideModeRadioButtons[this._settings.get_enum('intellihide-mode')].set_active(true); + } else { + // remove the settings box so it doesn't get destroyed; + dialog.get_content_area().remove(box); + dialog.destroy(); + } + }); + + dialog.present(); + }); + + // size options + const dockSizeScale = this._builder.get_object('dock_size_scale'); + dockSizeScale.set_value(this._settings.get_double('height-fraction')); + dockSizeScale.add_mark(0.9, Gtk.PositionType.TOP, null); + dockSizeScale.set_format_value_func((_, value) => { + return `${Math.round(value * 100)} %`; + }); + const iconSizeScale = this._builder.get_object('icon_size_scale'); + iconSizeScale.set_range(8, DEFAULT_ICONS_SIZES[0]); + iconSizeScale.set_value(this._settings.get_int('dash-max-icon-size')); + DEFAULT_ICONS_SIZES.forEach(val => { + iconSizeScale.add_mark(val, Gtk.PositionType.TOP, val.toString()); + }); + iconSizeScale.set_format_value_func((_, value) => { + return `${value} px`; + }); + this._builder.get_object('preview_size_scale').set_value( + this._settings.get_double('preview-size-scale')); + + // Corrent for rtl languages + if (this._rtl) { + // Flip value position: this is not done automatically + dockSizeScale.set_value_pos(Gtk.PositionType.LEFT); + iconSizeScale.set_value_pos(Gtk.PositionType.LEFT); + // I suppose due to a bug, having a more than one mark and one above + // a value of 100 makes the rendering of the marks wrong in rtl. + // This doesn't happen setting the scale as not flippable + // and then manually inverting it + iconSizeScale.set_flippable(false); + iconSizeScale.set_inverted(true); + } + + this._settings.bind('icon-size-fixed', + this._builder.get_object('icon_size_fixed_checkbutton'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('extend-height', + this._builder.get_object('dock_size_extend_checkbutton'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('extend-height', + this._builder.get_object('dock_size_scale'), + 'sensitive', + Gio.SettingsBindFlags.INVERT_BOOLEAN); + this._settings.bind('always-center-icons', + this._builder.get_object('dock_center_icons_check'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('extend-height', + this._builder.get_object('dock_center_icons_check'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT); + + this._settings.bind('multi-monitor', + this._builder.get_object('dock_monitor_combo'), + 'sensitive', + Gio.SettingsBindFlags.INVERT_BOOLEAN); + + + // Apps panel + + this._settings.bind('show-running', + this._builder.get_object('show_running_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + const applicationButtonIsolationButton = + this._builder.get_object('application_button_isolation_button'); + this._settings.bind('isolate-workspaces', + applicationButtonIsolationButton, + 'active', + Gio.SettingsBindFlags.DEFAULT); + applicationButtonIsolationButton.connect( + 'notify::sensitive', check => { + if (check.sensitive) { + [check.label] = check.label.split('\n'); + } else { + check.label += `\n${ + __('Managed by GNOME Multitasking\'s Application Switching setting.')}`; + } + }); + this._appSwitcherSettings.bind('current-workspace-only', + applicationButtonIsolationButton, + 'sensitive', + Gio.SettingsBindFlags.INVERT_BOOLEAN | + Gio.SettingsBindFlags.SYNC_CREATE); + this._settings.bind('workspace-agnostic-urgent-windows', + this._builder.get_object('application_button_urgent_button'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('isolate-monitors', + this._builder.get_object('application_button_monitor_isolation_button'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-windows-preview', + this._builder.get_object('windows_preview_button'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('multi-monitor', + this._builder.get_object('multi_monitor_button'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-favorites', + this._builder.get_object('show_favorite_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-trash', + this._builder.get_object('show_trash_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-mounts', + this._builder.get_object('show_mounts_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-mounts-only-mounted', + this._builder.get_object('show_only_mounted_devices_check'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-mounts-network', + this._builder.get_object('show_network_volumes_check'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('isolate-locations', + this._builder.get_object('isolate_locations_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + const isolateLocationsBindings = ['show_trash_switch', 'show_mounts_switch']; + const updateIsolateLocations = () => { + this._builder.get_object('isolate_locations_row').sensitive = + isolateLocationsBindings.some(s => this._builder.get_object(s).active); + }; + updateIsolateLocations(); + isolateLocationsBindings.forEach(s => this._builder.get_object(s).connect( + 'notify::active', () => updateIsolateLocations())); + this._settings.bind('dance-urgent-applications', + this._builder.get_object('wiggle_urgent_applications_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('hide-tooltip', + this._builder.get_object('hide_tooltip_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-icons-emblems', + this._builder.get_object('show_icons_emblems_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + const notificationsCounterCheck = this._builder.get_object( + 'notifications_counter_check'); + this._settings.bind('show-icons-notifications-counter', + notificationsCounterCheck, + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-icons-emblems', + notificationsCounterCheck, + 'sensitive', + Gio.SettingsBindFlags.GET); + + const applicationsOverrideCounter = + this._builder.get_object('applications_override_counter'); + this._settings.bind('application-counter-overrides-notifications', + applicationsOverrideCounter, + 'active', + Gio.SettingsBindFlags.DEFAULT); + notificationsCounterCheck.bind_property('active', + applicationsOverrideCounter, 'sensitive', + GObject.BindingFlags.SYNC_CREATE); + this._settings.connect('changed::show-icons-emblems', () => { + if (this._settings.get_boolean('show-icons-emblems')) + applicationsOverrideCounter.sensitive = notificationsCounterCheck.active; + else + applicationsOverrideCounter.sensitive = false; + }); + this._settings.bind('show-show-apps-button', + this._builder.get_object('show_applications_button_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-apps-at-top', + this._builder.get_object('application_button_first_button'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-show-apps-button', + this._builder.get_object('application_button_first_button'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-show-apps-button', + this._builder.get_object('application_button_animation_button'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-apps-always-in-the-edge', + this._builder.get_object('show_apps_always_in_the_edge'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('show-show-apps-button', + this._builder.get_object('show_apps_always_in_the_edge'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('scroll-to-focused-application', + this._builder.get_object('scroll_to_icon_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + + + // Behavior panel + + this._settings.bind('hot-keys', + this._builder.get_object('hot_keys_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('hot-keys', + this._builder.get_object('overlay_button'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT); + + this._builder.get_object('click_action_combo').set_active(this._settings.get_enum('click-action')); + this._builder.get_object('click_action_combo').connect('changed', widget => { + this._settings.set_enum('click-action', widget.get_active()); + }); + + this._builder.get_object('scroll_action_combo').set_active(this._settings.get_enum('scroll-action')); + this._builder.get_object('scroll_action_combo').connect('changed', widget => { + this._settings.set_enum('scroll-action', widget.get_active()); + }); + + this._builder.get_object('shift_click_action_combo').connect('changed', widget => { + this._settings.set_enum('shift-click-action', widget.get_active()); + }); + + this._builder.get_object('middle_click_action_combo').connect('changed', widget => { + this._settings.set_enum('middle-click-action', widget.get_active()); + }); + this._builder.get_object('shift_middle_click_action_combo').connect('changed', widget => { + this._settings.set_enum('shift-middle-click-action', widget.get_active()); + }); + + // Create dialog for number overlay options + this._builder.get_object('overlay_button').connect('clicked', () => { + const dialog = new Gtk.Dialog({ + title: __('Show dock and application numbers'), + transient_for: this.widget.get_root(), + use_header_bar: true, + modal: true, + }); + + // GTK+ leaves positive values for application-defined response ids. + // Use +1 for the reset action + dialog.add_button(__('Reset to defaults'), 1); + + const box = this._builder.get_object('box_overlay_shortcut'); + dialog.get_content_area().append(box); + + this._builder.get_object('overlay_switch').set_active( + this._settings.get_boolean('hotkeys-overlay')); + this._builder.get_object('show_dock_switch').set_active( + this._settings.get_boolean('hotkeys-show-dock')); + + // We need to update the shortcut 'strv' when the text is modified + this._settings.connect('changed::shortcut-text', () => setShortcut(this._settings)); + this._settings.bind('shortcut-text', + this._builder.get_object('shortcut_entry'), + 'text', + Gio.SettingsBindFlags.DEFAULT); + + this._settings.bind('hotkeys-overlay', + this._builder.get_object('overlay_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('hotkeys-show-dock', + this._builder.get_object('show_dock_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('shortcut-timeout', + this._builder.get_object('timeout_spinbutton'), + 'value', + Gio.SettingsBindFlags.DEFAULT); + + dialog.connect('response', (_, id) => { + if (id === 1) { + // restore default settings for the relevant keys + const keys = ['shortcut-text', 'hotkeys-overlay', + 'hotkeys-show-dock', 'shortcut-timeout']; + keys.forEach(function (val) { + this._settings.set_value(val, this._settings.get_default_value(val)); + }, this); + } else { + // remove the settings box so it doesn't get destroyed; + dialog.get_content_area().remove(box); + dialog.destroy(); + } + }); + + dialog.present(); + }); + + // Create dialog for middle-click options + this._builder.get_object('middle_click_options_button').connect('clicked', () => { + const dialog = new Gtk.Dialog({ + title: __('Customize middle-click behavior'), + transient_for: this.widget.get_root(), + use_header_bar: true, + modal: true, + }); + + // GTK+ leaves positive values for application-defined response ids. + // Use +1 for the reset action + dialog.add_button(__('Reset to defaults'), 1); + + const box = this._builder.get_object('box_middle_click_options'); + dialog.get_content_area().append(box); + + this._builder.get_object('shift_click_action_combo').set_active( + this._settings.get_enum('shift-click-action')); + + this._builder.get_object('middle_click_action_combo').set_active( + this._settings.get_enum('middle-click-action')); + + this._builder.get_object('shift_middle_click_action_combo').set_active( + this._settings.get_enum('shift-middle-click-action')); + + this._settings.bind('shift-click-action', + this._builder.get_object('shift_click_action_combo'), + 'active-id', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('middle-click-action', + this._builder.get_object('middle_click_action_combo'), + 'active-id', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('shift-middle-click-action', + this._builder.get_object('shift_middle_click_action_combo'), + 'active-id', + Gio.SettingsBindFlags.DEFAULT); + + dialog.connect('response', (_, id) => { + if (id === 1) { + // restore default settings for the relevant keys + const keys = ['shift-click-action', 'middle-click-action', 'shift-middle-click-action']; + keys.forEach(function (val) { + this._settings.set_value(val, this._settings.get_default_value(val)); + }, this); + this._builder.get_object('shift_click_action_combo').set_active( + this._settings.get_enum('shift-click-action')); + this._builder.get_object('middle_click_action_combo').set_active( + this._settings.get_enum('middle-click-action')); + this._builder.get_object('shift_middle_click_action_combo').set_active( + this._settings.get_enum('shift-middle-click-action')); + } else { + // remove the settings box so it doesn't get destroyed; + dialog.get_content_area().remove(box); + dialog.destroy(); + } + }); + + dialog.present(); + }); + + // Appearance Panel + + this._settings.bind('apply-custom-theme', + this._builder.get_object('customize_theme'), + 'sensitive', + Gio.SettingsBindFlags.INVERT_BOOLEAN | Gio.SettingsBindFlags.GET); + this._settings.bind('apply-custom-theme', + this._builder.get_object('builtin_theme_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('custom-theme-shrink', + this._builder.get_object('shrink_dash_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + + // Running indicators + this._builder.get_object('running_indicators_combo').set_active( + this._settings.get_enum('running-indicator-style') + ); + this._builder.get_object('running_indicators_combo').connect( + 'changed', + widget => { + this._settings.set_enum('running-indicator-style', widget.get_active()); + } + ); + + if (this._settings.get_enum('running-indicator-style') === RunningIndicatorStyle.DEFAULT) + this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(false); + + this._settings.connect('changed::running-indicator-style', () => { + if (this._settings.get_enum('running-indicator-style') === RunningIndicatorStyle.DEFAULT) + this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(false); + else + this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(true); + }); + + // Create dialog for running indicators advanced settings + this._builder.get_object('running_indicators_advance_settings_button').connect('clicked', () => { + const dialog = new Gtk.Dialog({ + title: __('Customize running indicators'), + transient_for: this.widget.get_root(), + use_header_bar: true, + modal: true, + }); + + const box = this._builder.get_object('running_dots_advance_settings_box'); + dialog.get_content_area().append(box); + + this._settings.bind('running-indicator-dominant-color', + this._builder.get_object('dominant_color_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + + this._settings.bind('custom-theme-customize-running-dots', + this._builder.get_object('dot_style_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('custom-theme-customize-running-dots', + this._builder.get_object('dot_style_settings_box'), + 'sensitive', Gio.SettingsBindFlags.DEFAULT); + + const rgba = new Gdk.RGBA(); + rgba.parse(this._settings.get_string('custom-theme-running-dots-color')); + this._builder.get_object('dot_color_colorbutton').set_rgba(rgba); + + this._builder.get_object('dot_color_colorbutton').connect('notify::rgba', button => { + const css = button.rgba.to_string(); + + this._settings.set_string('custom-theme-running-dots-color', css); + }); + + rgba.parse(this._settings.get_string('custom-theme-running-dots-border-color')); + this._builder.get_object('dot_border_color_colorbutton').set_rgba(rgba); + + this._builder.get_object('dot_border_color_colorbutton').connect('notify::rgba', button => { + const css = button.rgba.to_string(); + + this._settings.set_string('custom-theme-running-dots-border-color', css); + }); + + this._settings.bind('custom-theme-running-dots-border-width', + this._builder.get_object('dot_border_width_spin_button'), + 'value', + Gio.SettingsBindFlags.DEFAULT); + + + dialog.connect('response', () => { + // remove the settings box so it doesn't get destroyed; + dialog.get_content_area().remove(box); + dialog.destroy(); + }); + + dialog.present(); + }); + + this._settings.bind('custom-background-color', + this._builder.get_object('custom_background_color_switch'), + 'active', Gio.SettingsBindFlags.DEFAULT); + this._settings.bind('custom-background-color', + this._builder.get_object('custom_background_color'), + 'sensitive', Gio.SettingsBindFlags.DEFAULT); + + const rgba = new Gdk.RGBA(); + rgba.parse(this._settings.get_string('background-color')); + this._builder.get_object('custom_background_color').set_rgba(rgba); + + this._builder.get_object('custom_background_color').connect('notify::rgba', button => { + const css = button.rgba.to_string(); + + this._settings.set_string('background-color', css); + }); + + // Opacity + this._builder.get_object('customize_opacity_combo').set_active_id( + this._settings.get_enum('transparency-mode').toString() + ); + this._builder.get_object('customize_opacity_combo').connect( + 'changed', + widget => { + this._settings.set_enum('transparency-mode', parseInt(widget.get_active_id())); + } + ); + + const customOpacityScale = this._builder.get_object('custom_opacity_scale'); + customOpacityScale.set_value(this._settings.get_double('background-opacity')); + customOpacityScale.set_format_value_func((_, value) => { + return `${Math.round(value * 100)}%`; + }); + + if (this._settings.get_enum('transparency-mode') !== TransparencyMode.FIXED) + this._builder.get_object('custom_opacity_scale').set_sensitive(false); + + this._settings.connect('changed::transparency-mode', () => { + if (this._settings.get_enum('transparency-mode') !== TransparencyMode.FIXED) + this._builder.get_object('custom_opacity_scale').set_sensitive(false); + else + this._builder.get_object('custom_opacity_scale').set_sensitive(true); + }); + + if (this._settings.get_enum('transparency-mode') !== TransparencyMode.DYNAMIC) + this._builder.get_object('dynamic_opacity_button').set_sensitive(false); + + + this._settings.connect('changed::transparency-mode', () => { + if (this._settings.get_enum('transparency-mode') !== TransparencyMode.DYNAMIC) + this._builder.get_object('dynamic_opacity_button').set_sensitive(false); + + else + this._builder.get_object('dynamic_opacity_button').set_sensitive(true); + }); + + // Create dialog for transparency advanced settings + this._builder.get_object('dynamic_opacity_button').connect('clicked', () => { + const dialog = new Gtk.Dialog({ + title: __('Customize opacity'), + transient_for: this.widget.get_root(), + use_header_bar: true, + modal: true, + }); + + const box = this._builder.get_object('advanced_transparency_dialog'); + dialog.get_content_area().append(box); + + this._settings.bind( + 'customize-alphas', + this._builder.get_object('customize_alphas_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT + ); + this._settings.bind( + 'customize-alphas', + this._builder.get_object('min_alpha_scale'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT + ); + this._settings.bind( + 'customize-alphas', + this._builder.get_object('max_alpha_scale'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT + ); + + const minAlphaScale = this._builder.get_object('min_alpha_scale'); + const maxAlphaScale = this._builder.get_object('max_alpha_scale'); + minAlphaScale.set_value( + this._settings.get_double('min-alpha') + ); + minAlphaScale.set_format_value_func((_, value) => { + return `${Math.round(value * 100)} %`; + }); + maxAlphaScale.set_format_value_func((_, value) => { + return `${Math.round(value * 100)} %`; + }); + + maxAlphaScale.set_value( + this._settings.get_double('max-alpha') + ); + + dialog.connect('response', () => { + // remove the settings box so it doesn't get destroyed; + dialog.get_content_area().remove(box); + dialog.destroy(); + }); + + dialog.present(); + }); + + + this._settings.bind('unity-backlit-items', + this._builder.get_object('unity_backlit_items_switch'), + 'active', Gio.SettingsBindFlags.DEFAULT + ); + this._settings.bind('apply-glossy-effect', + this._builder.get_object('apply_gloss_effect_checkbutton'), + 'active', Gio.SettingsBindFlags.DEFAULT + ); + this._settings.bind('unity-backlit-items', + this._builder.get_object('apply_gloss_effect_checkbutton'), + 'sensitive', + Gio.SettingsBindFlags.DEFAULT + ); + + this._settings.bind('force-straight-corner', + this._builder.get_object('force_straight_corner_switch'), + 'active', Gio.SettingsBindFlags.DEFAULT); + + this._settings.bind('disable-overview-on-startup', + this._builder.get_object('show_overview_on_startup_switch'), + 'active', Gio.SettingsBindFlags.INVERT_BOOLEAN); + + // About Panel + + this._builder.get_object('extension_version').set_label( + `${this._extensionPreferences.metadata.version}`); + } +}); + +export default class DockPreferences extends ExtensionPreferences { + getPreferencesWidget() { + const settings = new DockSettings(this); + const {widget} = settings; + return widget; + } +} diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/schemas/gschemas.compiled b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/schemas/gschemas.compiled new file mode 100644 index 0000000..a67f68f Binary files /dev/null and b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/schemas/gschemas.compiled differ diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml new file mode 100644 index 0000000..3a762c3 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml @@ -0,0 +1,644 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'BOTTOM' + Dock position + Dock is shown on the Left, Right, Top or Bottom side of the screen. + + + 0.2 + Animation time + Sets the time duration of the autohide effect. + + + 0.25 + Show delay + Sets the delay after the mouse reaches the screen border before showing the dock. + + + 0.20 + Show delay + Sets the delay after the mouse left the dock before hiding it. + + + false + Do not show overview on startup + Whether to show or not the overview during startup animation. + + + false + Set a custom dash background background color + Sets the color for the dash background. + + + "#ffffff" + Dash background color. + Customize the background color of the dash. + + + 'DEFAULT' + Transparency mode for the dock + FIXED: constant transparency. DYNAMIC: dock takes the opaque style only when windows are close to it. + + + 'DEFAULT' + ... + DEFAULT: .... DOTS: .... + + + false + Use application icon dominant color for the indicator color + + + + false + Manually set the min and max opacity + For the dynamic mode, the min/max opacity values will be given by 'min-alpha' and 'max-alpha'. + + + 0.2 + Opacity of the dash background when free-floating + Sets the opacity of the dash background when no windows are close. + + + 0.8 + Opacity of the dash background when windows are close. + Sets the opacity of the dash background when windows are close. + + + 0.8 + Opacity of the dash background + Sets the opacity of the dash background when in autohide mode. + + + false + Dock not shown on desktop + Enable or disable manualhide mode + + + true + Dock dodges windows + Enable or disable intellihide mode + + + 'FOCUS_APPLICATION_WINDOWS' + Define which windows are considered for intellihide. + + + + true + Dock shown on mouse over + Enable or disable autohide mode + + + true + Require pressure to show dash + Enable or disable requiring pressure to show the dash + + + 100 + Pressure threshold + Sets how much pressure is needed to show the dash. + + + false + Enable autohide in fullscreen mode. + Enable autohide in fullscreen mode. + + + true + Show dock for urgent notifications. + Show dock when urgent notifications are received. + + + false + Dock always visible + Dock is always visible + + + true + Switch workspace by scrolling over the dock + Add the possibility to switch workspace by mouse scrolling over the dock. + + + 48 + Maximum dash icon size + Set the allowed maximum dash icon size. Allowed range: 16..64. + + + 0 + Preview size scale + Set the allowed maximum dash preview size scale. Allowed range: 0,00..1,00. + + + false + Fixed icon size + Keep the icon size fixed by scrolling the dock. + + + false + Apply custom theme + Apply customization to the dash appearance + + + false + TODO + TODO + + + false + Customize the style of the running application indicators. + Customize the style of the running application indicators. + + + "#ffffff" + Running application indicators color + Customize the color of the running application indicators. + + + "#ffffff" + Running application indicators border color. + Customize the border color of the running application indicators. + + + 0 + Running application indicators border width. + Customize the border width of the running application indicators. + + + true + Show running apps + Show or hide running applications icons in the dash + + + false + Provide workspace isolation + Dash shows only windows from the current workspace, this is applied only if gnome-shell 'current-workspace-only' app-switcher settings is disabled + + + true + Show urgent windows on each workspace + Show or hide urgent windows on each workspace in the dash + + + false + Provide monitor isolation + Dash shows only windows from the monitor + + + true + Scroll to focused application + Ensure that the focused application icon is always visible in the dash + + + true + Show preview of the open windows + Replace open windows list with windows previews + + + false + Open windows preview by default + Window previews will show windows by default, rather than hide them behind a drop dowm menu. + + + true + Show favorites apps + Show or hide favorite applications icons in the dash + + + true + Show trash can + Show or hide the trash can icon in the dash + + + true + Show mounted volumes and devices + Show or hide mounted volume and device icons in the dash + + + true + Only show mounted volumes and devices + Show or hide unmounted volume and device icons in the dash + + + false + Show network mounted volumes + Show or hide network volumes in the dash + + + true + Isolate volumes, devices and trash windows + Consider volume, devices and trash as different application windows and not part of the file manager + + + true + Wiggle urgent applications + Wiggle urgent application icons in the dash + + + true + Show applications button + Show applications button in the dash + + + false + Show application button on the left + Show application button on the left of the dash + + + true + Show application button on the edge when using centered panel mode + + + true + Basic compatibility with bolt extensions + Make the extension work properly when bolt extensions is enabled + + + 0.90 + Dock max height/width (fraction of available space) + + + false + Extend the dock container to all the available height/width + + + false + Center icons when dock container is extended to all the available size + + + -2 + DEPRECATED: Monitor on which putting the dock (by index) + Set on which monitor to put the dock, use -1 for the primary one. Use -2 refer to the 'preferred-monitor-by-connector' setting instead (this is the preferred value). + + + "primary" + Monitor on which putting the dock (by connector) + Set on which monitor to put the dock, for example 'DisplayPort-0'. Use 'primary' for primary one. This setting is only used if 'preferred-monitor' is -2. + + + false + Enable multi-monitor docks + Show a dock on every monitor + + + true + Minimize on shift+click + + + true + Activate only one window + + + 'cycle-windows' + Action when clicking on a running app + Set the action that is executed when clicking on the icon of a running application + + + 'do-nothing' + Action when scrolling app + Set the action that is executed when scrolling on the application icon + + + 'minimize' + Action when shift+clicking on a running app + Set the action that is executed when shift+clicking on the icon of a running application + + + 'launch' + Action when clicking on a running app + Set the action that is executed when middle-clicking on the icon of a running application + + + 'launch' + Action when clicking on a running app + Set the action that is executed when shift+middle-clicking on the icon of a running application + + + true + Super Hot-Keys + Launch and switch between dash items using Super+(0-9) + + + true + Show the dock when using the hotkeys + The dock will be quickly shown so that the number-overlay is visible and app activation is easier + + + "<Super>q" + Keybinding to show the dock and the number overlay. + Behavior depends on hotkeys-show-dock and hotkeys-overlay. + + + q']]]> + Keybinding to show the dock and the number overlay. + Behavior depends on hotkeys-show-dock and hotkeys-overlay. + + + 2 + Timeout to hide the dock + Sets the time duration before the dock is hidden again. + + + true + Show the dock when using the hotkeys + The dock will be quickly shown so that the number-overlay is visible and app activation is easier + + + 1']]]> + Keybinding to launch 1st dash app + + Keybinding to launch 1st app. + + + + 2']]]> + Keybinding to launch 2nd dash app + + Keybinding to launch 2nd app. + + + + 3']]]> + Keybinding to launch 3rd dash app + + Keybinding to launch 3rd app. + + + + 4']]]> + Keybinding to launch 4th dash app + + Keybinding to launch 4th app. + + + + 5']]]> + Keybinding to launch 5th dash app + + Keybinding to launch 5th app. + + + + 6']]]> + Keybinding to launch 6th dash app + + Keybinding to launch 6th app. + + + + 7']]]> + Keybinding to launch 7th dash app + + Keybinding to launch 7th app. + + + + 8']]]> + Keybinding to launch 8th dash app + + Keybinding to launch 8th app. + + + + 9']]]> + Keybinding to launch 9th dash app + + Keybinding to launch 9th app. + + + + 0']]]> + Keybinding to launch 10th dash app + + Keybinding to launch 10th app. + + + + 1']]]> + Keybinding to trigger 1st dash app with shift behavior + + Keybinding to trigger 1st app with shift behavior. + + + + 2']]]> + Keybinding to trigger 2nd dash app with shift behavior + + Keybinding to trigger 2nd app with shift behavior. + + + + 3']]]> + Keybinding to trigger 3rd dash app with shift behavior + + Keybinding to trigger 3rd app with shift behavior. + + + + 4']]]> + Keybinding to trigger 4th dash app with shift behavior + + Keybinding to trigger 4th app with shift behavior. + + + + 5']]]> + Keybinding to trigger 5th dash app with shift behavior + + Keybinding to trigger 5th app with shift behavior. + + + + 6']]]> + Keybinding to trigger 6th dash app with shift behavior + + Keybinding to trigger 6th app with shift behavior. + + + + 7']]]> + Keybinding to trigger 7th dash app with shift behavior + + Keybinding to trigger 7th app with shift behavior. + + + + 8']]]> + Keybinding to trigger 8th dash app with shift behavior + + Keybinding to trigger 8th app with shift behavior. + + + + 9']]]> + Keybinding to trigger 9th dash app with shift behavior + + Keybinding to trigger 9th app with shift behavior. + + + + 0']]]> + Keybinding to trigger 10th dash app with shift behavior + + Keybinding to trigger 10th app with shift behavior. + + + + 1']]]> + Keybinding to trigger 1st dash app + + Keybinding to either show or launch the 1st application in the dash. + + + + 2']]]> + Keybinding to trigger 2nd dash app + + Keybinding to either show or launch the 2nd application in the dash. + + + + 3']]]> + Keybinding to trigger 3rd dash app + + Keybinding to either show or launch the 3rd application in the dash. + + + + 4']]]> + Keybinding to trigger 4th dash app + + Keybinding to either show or launch the 4th application in the dash. + + + + 5']]]> + Keybinding to trigger 5th dash app + + Keybinding to either show or launch the 5th application in the dash. + + + + 6']]]> + Keybinding to trigger 6th dash app + + Keybinding to either show or launch the 6th application in the dash. + + + + 7']]]> + Keybinding to trigger 7th dash app + + Keybinding to either show or launch the 7th application in the dash. + + + + 8']]]> + Keybinding to trigger 8th dash app + + Keybinding to either show or launch the 8th application in the dash. + + + + 9']]]> + Keybinding to trigger 9th dash app + + Keybinding to either show or launch the 9th application in the dash. + + + + 0']]]> + Keybinding to trigger 10th dash app + + Keybinding to either show or launch the 10th application in the dash. + + + + false + Force straight corners in dash + Make the borders in the dash non rounded + + + false + Enable unity7 like glossy backlit items + Emulate the unity7 backlit glossy items behaviour + + + true + Enable glossy effect + Toggle the glossy effect of the unity7 backlit feature. + + + false + Hide application tooltip + Hide tooltip when hovering application in the dash. + + + true + Show application icons emblems + Show application icons emblems such as notification counters an progress bars. + + + true + Show the number of unread notifications in icons + Show application icons emblems such as notification + + + true + Wether the application-provioded counter via Unity API should override the notifications or be summed to it + If an application provides a counter through the Unity API, then such value will be used. Otherwise the counter will be summed with the unread notification numbers (if enabled) + + + diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/stylesheet.css b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/stylesheet.css new file mode 100644 index 0000000..b8d1935 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/stylesheet.css @@ -0,0 +1,1478 @@ +.dash-item-container .overview-tile .overview-icon, +.dash-item-container .show-apps .overview-icon { + background-color: rgba(255, 255, 255, 0); } + +.dash-item-container .overview-tile:hover .overview-icon, +.dash-item-container .overview-tile.focused .overview-icon, +.dash-item-container .show-apps:hover .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + +.dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 8px; + offset-y: 2px; } + +#dashtodockContainer.bottom #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.bottom #dash .dash-background { + margin: 0; + margin-bottom: 4px; + padding: 0; + border-radius: 24px; + spacing: 6px; } + #dashtodockContainer.bottom #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + margin: 0 4px; + width: 1px; + height: 0; + margin-bottom: 4px; } + #dashtodockContainer.bottom #dash #dashtodockDashContainer { + padding: 10px; + padding-bottom: 0; + padding-top: 0; } + #dashtodockContainer.bottom #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 0 2px; } + #dashtodockContainer.bottom #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.bottom #dash .dash-item-container:ltr:first-child, #dashtodockContainer.bottom #dash .dash-item-container:rtl:last-child { + margin-left: 0; } + #dashtodockContainer.bottom #dash .dash-item-container:ltr:last-child, #dashtodockContainer.bottom #dash .dash-item-container:rtl:first-child { + margin-right: 0; } + #dashtodockContainer.bottom #dash .dash-item-container .app-well-app, + #dashtodockContainer.bottom #dash .dash-item-container .show-apps, + #dashtodockContainer.bottom #dash .dash-item-container .overview-tile { + padding: 2px 2px; + padding-bottom: 14px; + padding-top: 10px; } + #dashtodockContainer.bottom #dash .dash-item-container .app-well-app:ltr:first-child, #dashtodockContainer.bottom #dash .dash-item-container .app-well-app:rtl:last-child, + #dashtodockContainer.bottom #dash .dash-item-container .show-apps:ltr:first-child, + #dashtodockContainer.bottom #dash .dash-item-container .show-apps:rtl:last-child, + #dashtodockContainer.bottom #dash .dash-item-container .overview-tile:ltr:first-child, + #dashtodockContainer.bottom #dash .dash-item-container .overview-tile:rtl:last-child { + padding-left: 0; } + #dashtodockContainer.bottom #dash .dash-item-container .app-well-app:ltr:last-child, #dashtodockContainer.bottom #dash .dash-item-container .app-well-app:rtl:first-child, + #dashtodockContainer.bottom #dash .dash-item-container .show-apps:ltr:last-child, + #dashtodockContainer.bottom #dash .dash-item-container .show-apps:rtl:first-child, + #dashtodockContainer.bottom #dash .dash-item-container .overview-tile:ltr:last-child, + #dashtodockContainer.bottom #dash .dash-item-container .overview-tile:rtl:first-child { + padding-right: 0; } + #dashtodockContainer.bottom #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.bottom #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.bottom #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.bottom #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.bottom #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.bottom #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.bottom #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.bottom #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.bottom #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.bottom #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.bottom #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.bottom #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.bottom.fixed #dash .dash-background { + margin-top: 4px; } + +#dashtodockContainer.bottom.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.bottom.fixed #dash .dash-item-container .show-apps { + padding-top: 14px; } + +.dash-label.bottom { + margin-bottom: 10px; + -x-offset: 0; + -y-offset: 0; } + +#dashtodockContainer.bottom.shrink #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.bottom.shrink #dash .dash-background { + margin: 0; + margin-bottom: 4px; + padding: 0; + border-radius: 18px; + spacing: 2px; } + #dashtodockContainer.bottom.shrink #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + margin: 0 1px; + width: 1px; + height: 0; + margin-bottom: 4px; } + #dashtodockContainer.bottom.shrink #dash #dashtodockDashContainer { + padding: 3px; + padding-bottom: 0; + padding-top: 0; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 0 2px; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container:ltr:first-child, #dashtodockContainer.bottom.shrink #dash .dash-item-container:rtl:last-child { + margin-left: 0; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container:ltr:last-child, #dashtodockContainer.bottom.shrink #dash .dash-item-container:rtl:first-child { + margin-right: 0; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container .app-well-app, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .show-apps, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .overview-tile { + padding: 1px 1px; + padding-bottom: 7px; + padding-top: 3px; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container .app-well-app:ltr:first-child, #dashtodockContainer.bottom.shrink #dash .dash-item-container .app-well-app:rtl:last-child, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .show-apps:ltr:first-child, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .show-apps:rtl:last-child, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .overview-tile:ltr:first-child, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .overview-tile:rtl:last-child { + padding-left: 0; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container .app-well-app:ltr:last-child, #dashtodockContainer.bottom.shrink #dash .dash-item-container .app-well-app:rtl:first-child, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .show-apps:ltr:last-child, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .show-apps:rtl:first-child, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .overview-tile:ltr:last-child, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .overview-tile:rtl:first-child { + padding-right: 0; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.bottom.shrink #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.bottom.shrink #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.bottom.shrink #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.bottom.shrink.fixed #dash .dash-background { + margin-top: 1px; } + +#dashtodockContainer.bottom.shrink.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.bottom.shrink.fixed #dash .dash-item-container .show-apps { + padding-top: 4px; } + +.dash-label.bottom.shrink { + margin-bottom: 3px; + -x-offset: 0; + -y-offset: 0; } + +#dashtodockContainer.bottom.extended #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.bottom.extended #dash .dash-background { + margin: 0; + margin-bottom: 0; + padding: 0; + border-radius: 0; + spacing: 6px; } + #dashtodockContainer.bottom.extended #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + margin: 0 4px; + width: 1px; + height: 0; + margin-bottom: 0; } + #dashtodockContainer.bottom.extended #dash #dashtodockDashContainer { + padding: 0; + padding-bottom: 0; + padding-top: 0; } + #dashtodockContainer.bottom.extended #dash #dashtodockDashContainer > :first-child .show-apps { + padding-left: 8px; } + #dashtodockContainer.bottom.extended #dash #dashtodockDashContainer #dashtodockDashScrollview:first-child { + padding-left: 8px; } + #dashtodockContainer.bottom.extended #dash #dashtodockDashContainer > :last-child .show-apps { + padding-right: 8px; } + #dashtodockContainer.bottom.extended #dash #dashtodockDashContainer #dashtodockDashScrollview:last-child { + padding-right: 8px; } + #dashtodockContainer.bottom.extended #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 0 2px; } + #dashtodockContainer.bottom.extended #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.bottom.extended #dash .dash-item-container:ltr:first-child, #dashtodockContainer.bottom.extended #dash .dash-item-container:rtl:last-child { + margin-left: 0; } + #dashtodockContainer.bottom.extended #dash .dash-item-container:ltr:last-child, #dashtodockContainer.bottom.extended #dash .dash-item-container:rtl:first-child { + margin-right: 0; } + #dashtodockContainer.bottom.extended #dash .dash-item-container .app-well-app, + #dashtodockContainer.bottom.extended #dash .dash-item-container .show-apps, + #dashtodockContainer.bottom.extended #dash .dash-item-container .overview-tile { + padding: 2px 2px; + padding-bottom: 10px; + padding-top: 10px; } + #dashtodockContainer.bottom.extended #dash .dash-item-container .app-well-app:ltr:first-child, #dashtodockContainer.bottom.extended #dash .dash-item-container .app-well-app:rtl:last-child, + #dashtodockContainer.bottom.extended #dash .dash-item-container .show-apps:ltr:first-child, + #dashtodockContainer.bottom.extended #dash .dash-item-container .show-apps:rtl:last-child, + #dashtodockContainer.bottom.extended #dash .dash-item-container .overview-tile:ltr:first-child, + #dashtodockContainer.bottom.extended #dash .dash-item-container .overview-tile:rtl:last-child { + padding-left: 0; } + #dashtodockContainer.bottom.extended #dash .dash-item-container .app-well-app:ltr:last-child, #dashtodockContainer.bottom.extended #dash .dash-item-container .app-well-app:rtl:first-child, + #dashtodockContainer.bottom.extended #dash .dash-item-container .show-apps:ltr:last-child, + #dashtodockContainer.bottom.extended #dash .dash-item-container .show-apps:rtl:first-child, + #dashtodockContainer.bottom.extended #dash .dash-item-container .overview-tile:ltr:last-child, + #dashtodockContainer.bottom.extended #dash .dash-item-container .overview-tile:rtl:first-child { + padding-right: 0; } + #dashtodockContainer.bottom.extended #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.bottom.extended #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.bottom.extended #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.bottom.extended #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.bottom.extended #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.bottom.extended #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.bottom.extended #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.bottom.extended #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.bottom.extended #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.bottom.extended #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.bottom.extended #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.bottom.extended #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.bottom.extended.fixed #dash .dash-background { + margin-top: 0; } + +#dashtodockContainer.bottom.extended.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.bottom.extended.fixed #dash .dash-item-container .show-apps { + padding-top: 10px; } + +.dash-label.bottom { + margin-bottom: 10px; + -x-offset: 0; + -y-offset: 0; } + +#dashtodockContainer.bottom.extended.shrink #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-background { + margin: 0; + margin-bottom: 0; + padding: 0; + border-radius: 0; + spacing: 2px; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + margin: 0 1px; + width: 1px; + height: 0; + margin-bottom: 0; } + #dashtodockContainer.bottom.extended.shrink #dash #dashtodockDashContainer { + padding: 0; + padding-bottom: 0; + padding-top: 0; } + #dashtodockContainer.bottom.extended.shrink #dash #dashtodockDashContainer > :first-child .show-apps { + padding-left: 6px; } + #dashtodockContainer.bottom.extended.shrink #dash #dashtodockDashContainer #dashtodockDashScrollview:first-child { + padding-left: 6px; } + #dashtodockContainer.bottom.extended.shrink #dash #dashtodockDashContainer > :last-child .show-apps { + padding-right: 6px; } + #dashtodockContainer.bottom.extended.shrink #dash #dashtodockDashContainer #dashtodockDashScrollview:last-child { + padding-right: 6px; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 0 2px; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container:ltr:first-child, #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container:rtl:last-child { + margin-left: 0; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container:ltr:last-child, #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container:rtl:first-child { + margin-right: 0; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .app-well-app, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .show-apps, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .overview-tile { + padding: 1px 1px; + padding-bottom: 3px; + padding-top: 3px; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .app-well-app:ltr:first-child, #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .app-well-app:rtl:last-child, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .show-apps:ltr:first-child, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .show-apps:rtl:last-child, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .overview-tile:ltr:first-child, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .overview-tile:rtl:last-child { + padding-left: 0; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .app-well-app:ltr:last-child, #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .app-well-app:rtl:first-child, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .show-apps:ltr:last-child, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .show-apps:rtl:first-child, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .overview-tile:ltr:last-child, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .overview-tile:rtl:first-child { + padding-right: 0; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.bottom.extended.shrink #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.bottom.extended.shrink.fixed #dash .dash-background { + margin-top: 0; } + +#dashtodockContainer.bottom.extended.shrink.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.bottom.extended.shrink.fixed #dash .dash-item-container .show-apps { + padding-top: 3px; } + +.dash-label.bottom.shrink { + margin-bottom: 3px; + -x-offset: 0; + -y-offset: 0; } + +.dashtodock-app-well-preview-menu-item.bottom { + padding: 0.5em 0.5em; } + .dashtodock-app-well-preview-menu-item.bottom:ltr:first-child, .dashtodock-app-well-preview-menu-item.bottom:rtl:last-child { + padding-left: 1em; } + .dashtodock-app-well-preview-menu-item.bottom:ltr:last-child, .dashtodock-app-well-preview-menu-item.bottom:rtl:first-child { + padding-right: 1em; } + +#dashtodockContainer.top #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.top #dash .dash-background { + margin: 0; + margin-top: 4px; + padding: 0; + border-radius: 24px; + spacing: 6px; } + #dashtodockContainer.top #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + margin: 0 4px; + width: 1px; + height: 0; + margin-top: 4px; } + #dashtodockContainer.top #dash #dashtodockDashContainer { + padding: 10px; + padding-top: 0; + padding-bottom: 0; } + #dashtodockContainer.top #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 0 2px; } + #dashtodockContainer.top #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.top #dash .dash-item-container:ltr:first-child, #dashtodockContainer.top #dash .dash-item-container:rtl:last-child { + margin-left: 0; } + #dashtodockContainer.top #dash .dash-item-container:ltr:last-child, #dashtodockContainer.top #dash .dash-item-container:rtl:first-child { + margin-right: 0; } + #dashtodockContainer.top #dash .dash-item-container .app-well-app, + #dashtodockContainer.top #dash .dash-item-container .show-apps, + #dashtodockContainer.top #dash .dash-item-container .overview-tile { + padding: 2px 2px; + padding-top: 14px; + padding-bottom: 10px; } + #dashtodockContainer.top #dash .dash-item-container .app-well-app:ltr:first-child, #dashtodockContainer.top #dash .dash-item-container .app-well-app:rtl:last-child, + #dashtodockContainer.top #dash .dash-item-container .show-apps:ltr:first-child, + #dashtodockContainer.top #dash .dash-item-container .show-apps:rtl:last-child, + #dashtodockContainer.top #dash .dash-item-container .overview-tile:ltr:first-child, + #dashtodockContainer.top #dash .dash-item-container .overview-tile:rtl:last-child { + padding-left: 0; } + #dashtodockContainer.top #dash .dash-item-container .app-well-app:ltr:last-child, #dashtodockContainer.top #dash .dash-item-container .app-well-app:rtl:first-child, + #dashtodockContainer.top #dash .dash-item-container .show-apps:ltr:last-child, + #dashtodockContainer.top #dash .dash-item-container .show-apps:rtl:first-child, + #dashtodockContainer.top #dash .dash-item-container .overview-tile:ltr:last-child, + #dashtodockContainer.top #dash .dash-item-container .overview-tile:rtl:first-child { + padding-right: 0; } + #dashtodockContainer.top #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.top #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.top #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.top #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.top #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.top #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.top #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.top #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.top #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.top #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.top #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.top #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.top.fixed #dash .dash-background { + margin-bottom: 4px; } + +#dashtodockContainer.top.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.top.fixed #dash .dash-item-container .show-apps { + padding-bottom: 14px; } + +.dash-label.top { + margin-top: 10px; + -x-offset: 0; + -y-offset: 0; } + +#dashtodockContainer.top.shrink #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.top.shrink #dash .dash-background { + margin: 0; + margin-top: 4px; + padding: 0; + border-radius: 18px; + spacing: 2px; } + #dashtodockContainer.top.shrink #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + margin: 0 1px; + width: 1px; + height: 0; + margin-top: 4px; } + #dashtodockContainer.top.shrink #dash #dashtodockDashContainer { + padding: 3px; + padding-top: 0; + padding-bottom: 0; } + #dashtodockContainer.top.shrink #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 0 2px; } + #dashtodockContainer.top.shrink #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.top.shrink #dash .dash-item-container:ltr:first-child, #dashtodockContainer.top.shrink #dash .dash-item-container:rtl:last-child { + margin-left: 0; } + #dashtodockContainer.top.shrink #dash .dash-item-container:ltr:last-child, #dashtodockContainer.top.shrink #dash .dash-item-container:rtl:first-child { + margin-right: 0; } + #dashtodockContainer.top.shrink #dash .dash-item-container .app-well-app, + #dashtodockContainer.top.shrink #dash .dash-item-container .show-apps, + #dashtodockContainer.top.shrink #dash .dash-item-container .overview-tile { + padding: 1px 1px; + padding-top: 7px; + padding-bottom: 3px; } + #dashtodockContainer.top.shrink #dash .dash-item-container .app-well-app:ltr:first-child, #dashtodockContainer.top.shrink #dash .dash-item-container .app-well-app:rtl:last-child, + #dashtodockContainer.top.shrink #dash .dash-item-container .show-apps:ltr:first-child, + #dashtodockContainer.top.shrink #dash .dash-item-container .show-apps:rtl:last-child, + #dashtodockContainer.top.shrink #dash .dash-item-container .overview-tile:ltr:first-child, + #dashtodockContainer.top.shrink #dash .dash-item-container .overview-tile:rtl:last-child { + padding-left: 0; } + #dashtodockContainer.top.shrink #dash .dash-item-container .app-well-app:ltr:last-child, #dashtodockContainer.top.shrink #dash .dash-item-container .app-well-app:rtl:first-child, + #dashtodockContainer.top.shrink #dash .dash-item-container .show-apps:ltr:last-child, + #dashtodockContainer.top.shrink #dash .dash-item-container .show-apps:rtl:first-child, + #dashtodockContainer.top.shrink #dash .dash-item-container .overview-tile:ltr:last-child, + #dashtodockContainer.top.shrink #dash .dash-item-container .overview-tile:rtl:first-child { + padding-right: 0; } + #dashtodockContainer.top.shrink #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.top.shrink #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.top.shrink #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.top.shrink #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.top.shrink #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.top.shrink #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.top.shrink #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.top.shrink #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.top.shrink #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.top.shrink #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.top.shrink #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.top.shrink #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.top.shrink.fixed #dash .dash-background { + margin-bottom: 1px; } + +#dashtodockContainer.top.shrink.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.top.shrink.fixed #dash .dash-item-container .show-apps { + padding-bottom: 4px; } + +.dash-label.top.shrink { + margin-top: 3px; + -x-offset: 0; + -y-offset: 0; } + +#dashtodockContainer.top.extended #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.top.extended #dash .dash-background { + margin: 0; + margin-top: 0; + padding: 0; + border-radius: 0; + spacing: 6px; } + #dashtodockContainer.top.extended #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + margin: 0 4px; + width: 1px; + height: 0; + margin-top: 0; } + #dashtodockContainer.top.extended #dash #dashtodockDashContainer { + padding: 0; + padding-top: 0; + padding-bottom: 0; } + #dashtodockContainer.top.extended #dash #dashtodockDashContainer > :first-child .show-apps { + padding-left: 8px; } + #dashtodockContainer.top.extended #dash #dashtodockDashContainer #dashtodockDashScrollview:first-child { + padding-left: 8px; } + #dashtodockContainer.top.extended #dash #dashtodockDashContainer > :last-child .show-apps { + padding-right: 8px; } + #dashtodockContainer.top.extended #dash #dashtodockDashContainer #dashtodockDashScrollview:last-child { + padding-right: 8px; } + #dashtodockContainer.top.extended #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 0 2px; } + #dashtodockContainer.top.extended #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.top.extended #dash .dash-item-container:ltr:first-child, #dashtodockContainer.top.extended #dash .dash-item-container:rtl:last-child { + margin-left: 0; } + #dashtodockContainer.top.extended #dash .dash-item-container:ltr:last-child, #dashtodockContainer.top.extended #dash .dash-item-container:rtl:first-child { + margin-right: 0; } + #dashtodockContainer.top.extended #dash .dash-item-container .app-well-app, + #dashtodockContainer.top.extended #dash .dash-item-container .show-apps, + #dashtodockContainer.top.extended #dash .dash-item-container .overview-tile { + padding: 2px 2px; + padding-top: 10px; + padding-bottom: 10px; } + #dashtodockContainer.top.extended #dash .dash-item-container .app-well-app:ltr:first-child, #dashtodockContainer.top.extended #dash .dash-item-container .app-well-app:rtl:last-child, + #dashtodockContainer.top.extended #dash .dash-item-container .show-apps:ltr:first-child, + #dashtodockContainer.top.extended #dash .dash-item-container .show-apps:rtl:last-child, + #dashtodockContainer.top.extended #dash .dash-item-container .overview-tile:ltr:first-child, + #dashtodockContainer.top.extended #dash .dash-item-container .overview-tile:rtl:last-child { + padding-left: 0; } + #dashtodockContainer.top.extended #dash .dash-item-container .app-well-app:ltr:last-child, #dashtodockContainer.top.extended #dash .dash-item-container .app-well-app:rtl:first-child, + #dashtodockContainer.top.extended #dash .dash-item-container .show-apps:ltr:last-child, + #dashtodockContainer.top.extended #dash .dash-item-container .show-apps:rtl:first-child, + #dashtodockContainer.top.extended #dash .dash-item-container .overview-tile:ltr:last-child, + #dashtodockContainer.top.extended #dash .dash-item-container .overview-tile:rtl:first-child { + padding-right: 0; } + #dashtodockContainer.top.extended #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.top.extended #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.top.extended #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.top.extended #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.top.extended #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.top.extended #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.top.extended #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.top.extended #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.top.extended #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.top.extended #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.top.extended #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.top.extended #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.top.extended.fixed #dash .dash-background { + margin-bottom: 0; } + +#dashtodockContainer.top.extended.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.top.extended.fixed #dash .dash-item-container .show-apps { + padding-bottom: 10px; } + +.dash-label.top { + margin-top: 10px; + -x-offset: 0; + -y-offset: 0; } + +#dashtodockContainer.top.extended.shrink #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.top.extended.shrink #dash .dash-background { + margin: 0; + margin-top: 0; + padding: 0; + border-radius: 0; + spacing: 2px; } + #dashtodockContainer.top.extended.shrink #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + margin: 0 1px; + width: 1px; + height: 0; + margin-top: 0; } + #dashtodockContainer.top.extended.shrink #dash #dashtodockDashContainer { + padding: 0; + padding-top: 0; + padding-bottom: 0; } + #dashtodockContainer.top.extended.shrink #dash #dashtodockDashContainer > :first-child .show-apps { + padding-left: 6px; } + #dashtodockContainer.top.extended.shrink #dash #dashtodockDashContainer #dashtodockDashScrollview:first-child { + padding-left: 6px; } + #dashtodockContainer.top.extended.shrink #dash #dashtodockDashContainer > :last-child .show-apps { + padding-right: 6px; } + #dashtodockContainer.top.extended.shrink #dash #dashtodockDashContainer #dashtodockDashScrollview:last-child { + padding-right: 6px; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 0 2px; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container:ltr:first-child, #dashtodockContainer.top.extended.shrink #dash .dash-item-container:rtl:last-child { + margin-left: 0; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container:ltr:last-child, #dashtodockContainer.top.extended.shrink #dash .dash-item-container:rtl:first-child { + margin-right: 0; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .app-well-app, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .show-apps, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .overview-tile { + padding: 1px 1px; + padding-top: 3px; + padding-bottom: 3px; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .app-well-app:ltr:first-child, #dashtodockContainer.top.extended.shrink #dash .dash-item-container .app-well-app:rtl:last-child, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .show-apps:ltr:first-child, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .show-apps:rtl:last-child, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .overview-tile:ltr:first-child, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .overview-tile:rtl:last-child { + padding-left: 0; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .app-well-app:ltr:last-child, #dashtodockContainer.top.extended.shrink #dash .dash-item-container .app-well-app:rtl:first-child, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .show-apps:ltr:last-child, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .show-apps:rtl:first-child, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .overview-tile:ltr:last-child, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .overview-tile:rtl:first-child { + padding-right: 0; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.top.extended.shrink #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.top.extended.shrink #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.top.extended.shrink.fixed #dash .dash-background { + margin-bottom: 0; } + +#dashtodockContainer.top.extended.shrink.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.top.extended.shrink.fixed #dash .dash-item-container .show-apps { + padding-bottom: 3px; } + +.dash-label.top.shrink { + margin-top: 3px; + -x-offset: 0; + -y-offset: 0; } + +.dashtodock-app-well-preview-menu-item.top { + padding: 0.5em 0.5em; } + .dashtodock-app-well-preview-menu-item.top:ltr:first-child, .dashtodock-app-well-preview-menu-item.top:rtl:last-child { + padding-left: 1em; } + .dashtodock-app-well-preview-menu-item.top:ltr:last-child, .dashtodock-app-well-preview-menu-item.top:rtl:first-child { + padding-right: 1em; } + +#dashtodockContainer.left #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.left #dash .dash-background { + margin: 0; + margin-left: 4px; + padding: 0; + border-radius: 24px; + spacing: 6px; } + #dashtodockContainer.left #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + width: 0; + height: 1px; + margin: 4px 0; + margin-left: 4px; } + #dashtodockContainer.left #dash #dashtodockDashContainer { + padding: 10px; + padding-left: 0; + padding-right: 0; } + #dashtodockContainer.left #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 2px 0; } + #dashtodockContainer.left #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.left #dash .dash-item-container:first-child { + margin-top: 0; } + #dashtodockContainer.left #dash .dash-item-container:last-child { + margin-bottom: 0; } + #dashtodockContainer.left #dash .dash-item-container .app-well-app, + #dashtodockContainer.left #dash .dash-item-container .show-apps, + #dashtodockContainer.left #dash .dash-item-container .overview-tile { + padding: 2px 2px; + padding-left: 14px; + padding-right: 10px; } + #dashtodockContainer.left #dash .dash-item-container .app-well-app:first-child, + #dashtodockContainer.left #dash .dash-item-container .show-apps:first-child, + #dashtodockContainer.left #dash .dash-item-container .overview-tile:first-child { + padding-top: 0; } + #dashtodockContainer.left #dash .dash-item-container .app-well-app:last-child, + #dashtodockContainer.left #dash .dash-item-container .show-apps:last-child, + #dashtodockContainer.left #dash .dash-item-container .overview-tile:last-child { + padding-bottom: 0; } + #dashtodockContainer.left #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.left #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.left #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.left #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.left #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.left #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.left #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.left #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.left #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.left #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.left #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.left #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.left.fixed #dash .dash-background { + margin-right: 4px; } + +#dashtodockContainer.left.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.left.fixed #dash .dash-item-container .show-apps { + padding-right: 14px; } + +.dash-label.left { + margin: 0; + -x-offset: 10px; + -y-offset: 0; } + +#dashtodockContainer.left.shrink #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.left.shrink #dash .dash-background { + margin: 0; + margin-left: 4px; + padding: 0; + border-radius: 18px; + spacing: 2px; } + #dashtodockContainer.left.shrink #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + width: 0; + height: 1px; + margin: 1px 0; + margin-left: 4px; } + #dashtodockContainer.left.shrink #dash #dashtodockDashContainer { + padding: 3px; + padding-left: 0; + padding-right: 0; } + #dashtodockContainer.left.shrink #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 2px 0; } + #dashtodockContainer.left.shrink #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.left.shrink #dash .dash-item-container:first-child { + margin-top: 0; } + #dashtodockContainer.left.shrink #dash .dash-item-container:last-child { + margin-bottom: 0; } + #dashtodockContainer.left.shrink #dash .dash-item-container .app-well-app, + #dashtodockContainer.left.shrink #dash .dash-item-container .show-apps, + #dashtodockContainer.left.shrink #dash .dash-item-container .overview-tile { + padding: 1px 1px; + padding-left: 7px; + padding-right: 3px; } + #dashtodockContainer.left.shrink #dash .dash-item-container .app-well-app:first-child, + #dashtodockContainer.left.shrink #dash .dash-item-container .show-apps:first-child, + #dashtodockContainer.left.shrink #dash .dash-item-container .overview-tile:first-child { + padding-top: 0; } + #dashtodockContainer.left.shrink #dash .dash-item-container .app-well-app:last-child, + #dashtodockContainer.left.shrink #dash .dash-item-container .show-apps:last-child, + #dashtodockContainer.left.shrink #dash .dash-item-container .overview-tile:last-child { + padding-bottom: 0; } + #dashtodockContainer.left.shrink #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.left.shrink #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.left.shrink #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.left.shrink #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.left.shrink #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.left.shrink #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.left.shrink #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.left.shrink #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.left.shrink #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.left.shrink #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.left.shrink #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.left.shrink #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.left.shrink.fixed #dash .dash-background { + margin-right: 1px; } + +#dashtodockContainer.left.shrink.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.left.shrink.fixed #dash .dash-item-container .show-apps { + padding-right: 4px; } + +.dash-label.left.shrink { + margin: 0; + -x-offset: 3px; + -y-offset: 0; } + +#dashtodockContainer.left.extended #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.left.extended #dash .dash-background { + margin: 0; + margin-left: 0; + padding: 0; + border-radius: 0; + spacing: 6px; } + #dashtodockContainer.left.extended #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + width: 0; + height: 1px; + margin: 4px 0; + margin-left: 0; } + #dashtodockContainer.left.extended #dash #dashtodockDashContainer { + padding: 0; + padding-left: 0; + padding-right: 0; } + #dashtodockContainer.left.extended #dash #dashtodockDashContainer > :first-child .show-apps { + padding-top: 8px; } + #dashtodockContainer.left.extended #dash #dashtodockDashContainer #dashtodockDashScrollview:first-child { + padding-top: 8px; } + #dashtodockContainer.left.extended #dash #dashtodockDashContainer > :last-child .show-apps { + padding-bottom: 8px; } + #dashtodockContainer.left.extended #dash #dashtodockDashContainer #dashtodockDashScrollview:last-child { + padding-bottom: 8px; } + #dashtodockContainer.left.extended #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 2px 0; } + #dashtodockContainer.left.extended #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.left.extended #dash .dash-item-container:first-child { + margin-top: 0; } + #dashtodockContainer.left.extended #dash .dash-item-container:last-child { + margin-bottom: 0; } + #dashtodockContainer.left.extended #dash .dash-item-container .app-well-app, + #dashtodockContainer.left.extended #dash .dash-item-container .show-apps, + #dashtodockContainer.left.extended #dash .dash-item-container .overview-tile { + padding: 2px 2px; + padding-left: 10px; + padding-right: 10px; } + #dashtodockContainer.left.extended #dash .dash-item-container .app-well-app:first-child, + #dashtodockContainer.left.extended #dash .dash-item-container .show-apps:first-child, + #dashtodockContainer.left.extended #dash .dash-item-container .overview-tile:first-child { + padding-top: 0; } + #dashtodockContainer.left.extended #dash .dash-item-container .app-well-app:last-child, + #dashtodockContainer.left.extended #dash .dash-item-container .show-apps:last-child, + #dashtodockContainer.left.extended #dash .dash-item-container .overview-tile:last-child { + padding-bottom: 0; } + #dashtodockContainer.left.extended #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.left.extended #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.left.extended #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.left.extended #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.left.extended #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.left.extended #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.left.extended #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.left.extended #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.left.extended #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.left.extended #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.left.extended #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.left.extended #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.left.extended.fixed #dash .dash-background { + margin-right: 0; } + +#dashtodockContainer.left.extended.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.left.extended.fixed #dash .dash-item-container .show-apps { + padding-right: 10px; } + +.dash-label.left { + margin: 0; + -x-offset: 10px; + -y-offset: 0; } + +#dashtodockContainer.left.extended.shrink #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.left.extended.shrink #dash .dash-background { + margin: 0; + margin-left: 0; + padding: 0; + border-radius: 0; + spacing: 2px; } + #dashtodockContainer.left.extended.shrink #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + width: 0; + height: 1px; + margin: 1px 0; + margin-left: 0; } + #dashtodockContainer.left.extended.shrink #dash #dashtodockDashContainer { + padding: 0; + padding-left: 0; + padding-right: 0; } + #dashtodockContainer.left.extended.shrink #dash #dashtodockDashContainer > :first-child .show-apps { + padding-top: 6px; } + #dashtodockContainer.left.extended.shrink #dash #dashtodockDashContainer #dashtodockDashScrollview:first-child { + padding-top: 6px; } + #dashtodockContainer.left.extended.shrink #dash #dashtodockDashContainer > :last-child .show-apps { + padding-bottom: 6px; } + #dashtodockContainer.left.extended.shrink #dash #dashtodockDashContainer #dashtodockDashScrollview:last-child { + padding-bottom: 6px; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 2px 0; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container:first-child { + margin-top: 0; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container:last-child { + margin-bottom: 0; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .app-well-app, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .show-apps, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .overview-tile { + padding: 1px 1px; + padding-left: 3px; + padding-right: 3px; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .app-well-app:first-child, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .show-apps:first-child, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .overview-tile:first-child { + padding-top: 0; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .app-well-app:last-child, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .show-apps:last-child, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .overview-tile:last-child { + padding-bottom: 0; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.left.extended.shrink #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.left.extended.shrink #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.left.extended.shrink.fixed #dash .dash-background { + margin-right: 0; } + +#dashtodockContainer.left.extended.shrink.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.left.extended.shrink.fixed #dash .dash-item-container .show-apps { + padding-right: 3px; } + +.dash-label.left.shrink { + margin: 0; + -x-offset: 3px; + -y-offset: 0; } + +.dashtodock-app-well-preview-menu-item.left { + padding: 0.5em 0.5em; } + .dashtodock-app-well-preview-menu-item.left:first-child { + padding-top: 1em; } + .dashtodock-app-well-preview-menu-item.left:last-child { + padding-bottom: 1em; } + +#dashtodockContainer.right #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.right #dash .dash-background { + margin: 0; + margin-right: 4px; + padding: 0; + border-radius: 24px; + spacing: 6px; } + #dashtodockContainer.right #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + width: 0; + height: 1px; + margin: 4px 0; + margin-right: 4px; } + #dashtodockContainer.right #dash #dashtodockDashContainer { + padding: 10px; + padding-right: 0; + padding-left: 0; } + #dashtodockContainer.right #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 2px 0; } + #dashtodockContainer.right #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.right #dash .dash-item-container:first-child { + margin-top: 0; } + #dashtodockContainer.right #dash .dash-item-container:last-child { + margin-bottom: 0; } + #dashtodockContainer.right #dash .dash-item-container .app-well-app, + #dashtodockContainer.right #dash .dash-item-container .show-apps, + #dashtodockContainer.right #dash .dash-item-container .overview-tile { + padding: 2px 2px; + padding-right: 14px; + padding-left: 10px; } + #dashtodockContainer.right #dash .dash-item-container .app-well-app:first-child, + #dashtodockContainer.right #dash .dash-item-container .show-apps:first-child, + #dashtodockContainer.right #dash .dash-item-container .overview-tile:first-child { + padding-top: 0; } + #dashtodockContainer.right #dash .dash-item-container .app-well-app:last-child, + #dashtodockContainer.right #dash .dash-item-container .show-apps:last-child, + #dashtodockContainer.right #dash .dash-item-container .overview-tile:last-child { + padding-bottom: 0; } + #dashtodockContainer.right #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.right #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.right #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.right #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.right #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.right #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.right #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.right #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.right #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.right #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.right #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.right #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.right.fixed #dash .dash-background { + margin-left: 4px; } + +#dashtodockContainer.right.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.right.fixed #dash .dash-item-container .show-apps { + padding-left: 14px; } + +.dash-label.right { + margin: 0; + -x-offset: 10px; + -y-offset: 0; } + +#dashtodockContainer.right.shrink #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.right.shrink #dash .dash-background { + margin: 0; + margin-right: 4px; + padding: 0; + border-radius: 18px; + spacing: 2px; } + #dashtodockContainer.right.shrink #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + width: 0; + height: 1px; + margin: 1px 0; + margin-right: 4px; } + #dashtodockContainer.right.shrink #dash #dashtodockDashContainer { + padding: 3px; + padding-right: 0; + padding-left: 0; } + #dashtodockContainer.right.shrink #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 2px 0; } + #dashtodockContainer.right.shrink #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.right.shrink #dash .dash-item-container:first-child { + margin-top: 0; } + #dashtodockContainer.right.shrink #dash .dash-item-container:last-child { + margin-bottom: 0; } + #dashtodockContainer.right.shrink #dash .dash-item-container .app-well-app, + #dashtodockContainer.right.shrink #dash .dash-item-container .show-apps, + #dashtodockContainer.right.shrink #dash .dash-item-container .overview-tile { + padding: 1px 1px; + padding-right: 7px; + padding-left: 3px; } + #dashtodockContainer.right.shrink #dash .dash-item-container .app-well-app:first-child, + #dashtodockContainer.right.shrink #dash .dash-item-container .show-apps:first-child, + #dashtodockContainer.right.shrink #dash .dash-item-container .overview-tile:first-child { + padding-top: 0; } + #dashtodockContainer.right.shrink #dash .dash-item-container .app-well-app:last-child, + #dashtodockContainer.right.shrink #dash .dash-item-container .show-apps:last-child, + #dashtodockContainer.right.shrink #dash .dash-item-container .overview-tile:last-child { + padding-bottom: 0; } + #dashtodockContainer.right.shrink #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.right.shrink #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.right.shrink #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.right.shrink #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.right.shrink #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.right.shrink #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.right.shrink #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.right.shrink #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.right.shrink #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.right.shrink #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.right.shrink #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.right.shrink #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.right.shrink.fixed #dash .dash-background { + margin-left: 1px; } + +#dashtodockContainer.right.shrink.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.right.shrink.fixed #dash .dash-item-container .show-apps { + padding-left: 4px; } + +.dash-label.right.shrink { + margin: 0; + -x-offset: 3px; + -y-offset: 0; } + +#dashtodockContainer.right.extended #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.right.extended #dash .dash-background { + margin: 0; + margin-right: 0; + padding: 0; + border-radius: 0; + spacing: 6px; } + #dashtodockContainer.right.extended #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + width: 0; + height: 1px; + margin: 4px 0; + margin-right: 0; } + #dashtodockContainer.right.extended #dash #dashtodockDashContainer { + padding: 0; + padding-right: 0; + padding-left: 0; } + #dashtodockContainer.right.extended #dash #dashtodockDashContainer > :first-child .show-apps { + padding-top: 8px; } + #dashtodockContainer.right.extended #dash #dashtodockDashContainer #dashtodockDashScrollview:first-child { + padding-top: 8px; } + #dashtodockContainer.right.extended #dash #dashtodockDashContainer > :last-child .show-apps { + padding-bottom: 8px; } + #dashtodockContainer.right.extended #dash #dashtodockDashContainer #dashtodockDashScrollview:last-child { + padding-bottom: 8px; } + #dashtodockContainer.right.extended #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 2px 0; } + #dashtodockContainer.right.extended #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.right.extended #dash .dash-item-container:first-child { + margin-top: 0; } + #dashtodockContainer.right.extended #dash .dash-item-container:last-child { + margin-bottom: 0; } + #dashtodockContainer.right.extended #dash .dash-item-container .app-well-app, + #dashtodockContainer.right.extended #dash .dash-item-container .show-apps, + #dashtodockContainer.right.extended #dash .dash-item-container .overview-tile { + padding: 2px 2px; + padding-right: 10px; + padding-left: 10px; } + #dashtodockContainer.right.extended #dash .dash-item-container .app-well-app:first-child, + #dashtodockContainer.right.extended #dash .dash-item-container .show-apps:first-child, + #dashtodockContainer.right.extended #dash .dash-item-container .overview-tile:first-child { + padding-top: 0; } + #dashtodockContainer.right.extended #dash .dash-item-container .app-well-app:last-child, + #dashtodockContainer.right.extended #dash .dash-item-container .show-apps:last-child, + #dashtodockContainer.right.extended #dash .dash-item-container .overview-tile:last-child { + padding-bottom: 0; } + #dashtodockContainer.right.extended #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.right.extended #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.right.extended #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.right.extended #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.right.extended #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.right.extended #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.right.extended #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.right.extended #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.right.extended #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.right.extended #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.right.extended #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.right.extended #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.right.extended.fixed #dash .dash-background { + margin-left: 0; } + +#dashtodockContainer.right.extended.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.right.extended.fixed #dash .dash-item-container .show-apps { + padding-left: 10px; } + +.dash-label.right { + margin: 0; + -x-offset: 10px; + -y-offset: 0; } + +#dashtodockContainer.right.extended.shrink #dash { + margin: 0px; + padding: 0px; } + #dashtodockContainer.right.extended.shrink #dash .dash-background { + margin: 0; + margin-right: 0; + padding: 0; + border-radius: 0; + spacing: 2px; } + #dashtodockContainer.right.extended.shrink #dash .dash-separator { + background-color: rgba(255, 255, 255, 0.1); + width: 0; + height: 1px; + margin: 1px 0; + margin-right: 0; } + #dashtodockContainer.right.extended.shrink #dash #dashtodockDashContainer { + padding: 0; + padding-right: 0; + padding-left: 0; } + #dashtodockContainer.right.extended.shrink #dash #dashtodockDashContainer > :first-child .show-apps { + padding-top: 6px; } + #dashtodockContainer.right.extended.shrink #dash #dashtodockDashContainer #dashtodockDashScrollview:first-child { + padding-top: 6px; } + #dashtodockContainer.right.extended.shrink #dash #dashtodockDashContainer > :last-child .show-apps { + padding-bottom: 6px; } + #dashtodockContainer.right.extended.shrink #dash #dashtodockDashContainer #dashtodockDashScrollview:last-child { + padding-bottom: 6px; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container { + /* Disable all margins defined upstream, we handle them here */ + margin: 2px 0; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container > * { + margin: 0; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container:first-child { + margin-top: 0; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container:last-child { + margin-bottom: 0; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .app-well-app, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .show-apps, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .overview-tile { + padding: 1px 1px; + padding-right: 3px; + padding-left: 3px; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .app-well-app:first-child, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .show-apps:first-child, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .overview-tile:first-child { + padding-top: 0; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .app-well-app:last-child, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .show-apps:last-child, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .overview-tile:last-child { + padding-bottom: 0; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .app-well-app.updating, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .show-apps.updating, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .overview-tile.updating { + opacity: 0.5; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .app-well-app.running .overview-icon, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .overview-tile.running .overview-icon { + background-image: none; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .app-well-app.focused .overview-icon, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .overview-tile.focused .overview-icon { + background-color: rgba(238, 238, 238, 0.2); } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .app-well-app .app-well-app-running-dot, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .app-well-app .app-grid-running-dot, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .overview-tile .app-well-app-running-dot, + #dashtodockContainer.right.extended.shrink #dash .dash-item-container .overview-tile .app-grid-running-dot { + margin-bottom: 2px; } + #dashtodockContainer.right.extended.shrink #dash .dash-item-container > StButton { + transition-duration: 250; + background-size: contain; } + +#dashtodockContainer.right.extended.shrink.fixed #dash .dash-background { + margin-left: 0; } + +#dashtodockContainer.right.extended.shrink.fixed #dash .dash-item-container .app-well-app, +#dashtodockContainer.right.extended.shrink.fixed #dash .dash-item-container .show-apps { + padding-left: 3px; } + +.dash-label.right.shrink { + margin: 0; + -x-offset: 3px; + -y-offset: 0; } + +.dashtodock-app-well-preview-menu-item.right { + padding: 0.5em 0.5em; } + .dashtodock-app-well-preview-menu-item.right:first-child { + padding-top: 1em; } + .dashtodock-app-well-preview-menu-item.right:last-child { + padding-bottom: 1em; } + +#dashtodockContainer.top.shrink #dash .dash-background { + margin-top: 4px; + margin-bottom: 0; } + +#dashtodockContainer.straight-corner #dash .dash-background, +#dashtodockContainer.shrink.straight-corner #dash .dash-background { + border-radius: 0px; } + +/* Scrollview style */ +.bottom #dashtodockDashScrollview, +.top #dashtodockDashScrollview { + -st-hfade-offset: 24px; } + +.left #dashtodockDashScrollview, +.right #dashtodockDashScrollview { + -st-vfade-offset: 24px; } + +#dashtodockContainer.dashtodock #dash .dash-background { + background: #303030; } + +#dashtodockContainer.dashtodock .progress-bar, +.overview-tile .progress-bar, +.icon-grid .progress-bar { + /* Customization of the progress bar style. The possible elements + * are: + * + * -XXXXXX-YYYYYY: plain color + * -XXXXXX-YYYYYY-color-start: start color for gradient + * -XXXXXX-YYYYYY-color-end: end color for gradient + * -XXXXXX-YYYYYY-offset-start: offset for start color in gradient (0-1) + * -XXXXXX-YYYYYY-offset-end: offset for end color in gradient (0-1) + * -XXXXXX-line-width: width value for the line used for the border + * + * XXXXXX can be either + * + * progress-bar-track: the "track" over which the progress bar is shown + * progress-bar: the progress bar itself + * + * YYYYYY can be either + * + * background: for the background color + * border: for the border color + * + * plain color entries have priority over gradients, so if both are set for + * an element, the plain color will be used. + * + * Examples: + * + * -progress-bar-track-background: rgba(128, 128, 0, 0.75); + * -progress-bar-track-border: rgba(0, 0, 255, 0.25); + * + * -progress-bar-background: rgba(0, 204, 204, 1); + * -progress-bar-border: rgba(230, 230, 230, 1); + * + * Default values when these entries aren't set are equivalent to: + * -progress-bar-track-background-color-start: 64, 64, 64, 1.0 + * -progress-bar-track-background-color-end: 89, 89, 89, 1.0 + * -progress-bar-track-background-start-offset: 0.4 + * -progress-bar-track-background-end-offset: 0.9 + * -progress-bar-track-border-color-start: 128, 128, 128, 0.1 + * -progress-bar-track-border-color-end: 89, 89, 89, 0.4 + * -progress-bar-track-border-start-offset: 0.5 + * -progress-bar-track-border-end-offset: 0.9 + * -progress-bar-track-line-width: 1 + * -progress-bar-background: 204, 204, 204, 1.0 + * -progress-bar-border: 230, 230, 230, 1.0 + * -progress-bar-height-factor: (0.20) 20% of the icon size + * -progress-bar-line-width: 1 + * -progress-bar-top-offset: undefined + * -progress-bar-valign: 1.0 + * -progress-bar-horizontal-padding: 0.05 (5% of icon size) + * -progress-bar-vertical-padding: 0.05 (5% of icon size) + */ + -progress-bar-track-background: rgba(0, 0, 0, 0.45); + -progress-bar-track-border: rgba(0, 0, 0, 0.7); + -progress-bar-background: white; + -progress-bar-border: white; } + +#overview .progress-bar, +.apps-scroll-view .progress-bar { + -progress-bar-height-factor: 0.15; + -progress-bar-top-offset: 0; + -progress-bar-valign: 0.95; } + +#overview .grid-search-results .overview-tile .progress-bar, +#overview .app-folder-dialog .progress-bar { + -progress-bar-height-factor: 0.13; } + +#dashtodockContainer.top #dash .placeholder, +#dashtodockContainer.bottom #dash .placeholder { + width: 32px; + height: 1px; } + +/* + * This is applied to a dummy actor. Only the alpha value for the background and border color + * and the transition-duration are used + */ +#dashtodockContainer.dummy-opaque { + background-color: rgba(0, 0, 0, 0.8); + border-color: rgba(0, 0, 0, 0.4); + transition-duration: 300ms; } + +/* + * This is applied to a dummy actor. Only the alpha value for the background and border color + * and the transition-duration are used + */ +#dashtodockContainer.dummy-transparent { + background-color: rgba(0, 0, 0, 0.2); + border-color: rgba(0, 0, 0, 0.1); + transition-duration: 500ms; } + +#dashtodockContainer .number-overlay, +.overview-tile .number-overlay, +.icon-grid .number-overlay { + color: white; + background-color: rgba(0, 0, 0, 0.8); + text-align: center; } + +#dashtodockContainer .notification-badge, +.overview-tile .notification-badge, +.icon-grid .notification-badge { + color: white; + background-color: red; + padding: 0.2em 0.5em; + border-radius: 1em; + font-weight: bold; + text-align: center; + margin: 2px; } + +#dashtodockPreviewSeparator.popup-separator-menu-item-horizontal { + width: 1px; + height: auto; + border-right-width: 1px; + margin: 32px 0px; } + +#dashtodockContainer .metro .overview-icon { + border-radius: 0px; } + +#dashtodockContainer.bottom .metro.running2.focused, +#dashtodockContainer.top .metro.running2.focused { + background-image: url("./media/highlight_stacked_bg.svg"); + background-position: 0px 0px; + background-size: contain; } + +#dashtodockContainer.left .metro.running2.focused, +#dashtodockContainer.right .metro.running2.focused { + background-image: url("./media/highlight_stacked_bg_h.svg"); + background-position: 0px 0px; + background-size: contain; } + +#dashtodockContainer.bottom .metro.running3.focused, +#dashtodockContainer.top .metro.running3.focused { + background-image: url("./media/highlight_stacked_bg.svg"); + background-position: 0px 0px; + background-size: contain; } + +#dashtodockContainer.left .metro.running3.focused, +#dashtodockContainer.right .metro.running3.focused { + background-image: url("./media/highlight_stacked_bg_h.svg"); + background-position: 0px 0px; + background-size: contain; } + +#dashtodockContainer.bottom .metro.running4.focused, +#dashtodockContainer.top .metro.running4.focused { + background-image: url("./media/highlight_stacked_bg.svg"); + background-position: 0px 0px; + background-size: contain; } + +#dashtodockContainer.left .metro.running4.focused, +#dashtodockContainer.right .metro.running4.focused { + background-image: url("./media/highlight_stacked_bg_h.svg"); + background-position: 0px 0px; + background-size: contain; } diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/theming.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/theming.js new file mode 100644 index 0000000..611eb40 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/theming.js @@ -0,0 +1,572 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +import { + Clutter, + Cogl, + GObject, + Meta, + St, +} from './dependencies/gi.js'; + +import {Main} from './dependencies/shell/ui.js'; + +import { + Docking, + Utils, +} from './imports.js'; + +const {signals: Signals} = imports; + +/* + * DEFAULT: transparency given by theme + * FIXED: constant transparency chosen by user + * DYNAMIC: apply 'transparent' style when no windows are close to the dock + * */ +const TransparencyMode = { + DEFAULT: 0, + FIXED: 1, + DYNAMIC: 3, +}; + +const Labels = Object.freeze({ + TRANSPARENCY: Symbol('transparency'), +}); + +export const PositionStyleClass = Object.freeze([ + 'top', + 'right', + 'bottom', + 'left', +]); + +/** + * Manage theme customization and custom theme support + */ +export class ThemeManager { + constructor(dock) { + this._signalsHandler = new Utils.GlobalSignalsHandler(this); + this._bindSettingsChanges(); + this._actor = dock; + this._dash = dock.dash; + + // initialize colors with generic values + this._customizedBackground = {red: 0, green: 0, blue: 0, alpha: 0}; + this._customizedBorder = {red: 0, green: 0, blue: 0, alpha: 0}; + this._transparency = new Transparency(dock); + + this._signalsHandler.add([ + // When theme changes re-obtain default background color + St.ThemeContext.get_for_stage(global.stage), + 'changed', + this.updateCustomTheme.bind(this), + ], [ + // update :overview pseudoclass + Main.overview, + 'showing', + this._onOverviewShowing.bind(this), + ], [ + Main.overview, + 'hiding', + this._onOverviewHiding.bind(this), + ]); + + this._updateCustomStyleClasses(); + + // destroy themeManager when the managed actor is destroyed (e.g. extension unload) + // in order to disconnect signals + this._signalsHandler.add(this._actor, 'destroy', () => this.destroy()); + } + + destroy() { + this.emit('destroy'); + this._transparency.destroy(); + this._destroyed = true; + } + + _onOverviewShowing() { + this._actor.add_style_pseudo_class('overview'); + } + + _onOverviewHiding() { + this._actor.remove_style_pseudo_class('overview'); + } + + _updateDashOpacity() { + const newAlpha = Docking.DockManager.settings.backgroundOpacity; + + const [backgroundColor, borderColor] = this._getDefaultColors(); + + if (!backgroundColor) + return; + + // Get the background and border alphas. We check the background alpha + // for a minimum of .001 to prevent division by 0 errors + const backgroundAlpha = Math.max(Math.round(backgroundColor.alpha / 2.55) / 100, .001); + let borderAlpha = Math.round(borderColor.alpha / 2.55) / 100; + + // The border and background alphas should remain in sync + // We also limit the borderAlpha to a maximum of 1 (full opacity) + borderAlpha = Math.min((borderAlpha / backgroundAlpha) * newAlpha, 1); + + this._customizedBackground = `rgba(${ + backgroundColor.red},${ + backgroundColor.green},${ + backgroundColor.blue},${ + newAlpha})`; + + this._customizedBorder = `rgba(${ + borderColor.red},${ + borderColor.green},${ + borderColor.blue},${ + borderAlpha})`; + } + + _getDefaultColors() { + // Prevent shell crash if the actor is not on the stage. + // It happens enabling/disabling repeatedly the extension + if (!this._dash._background.get_stage()) + return [null, null]; + + // Remove custom style + const oldStyle = this._dash._background.get_style(); + this._dash._background.set_style(null); + + const themeNode = this._dash._background.get_theme_node(); + this._dash._background.set_style(oldStyle); + + const backgroundColor = themeNode.get_background_color(); + + // Just in case the theme has different border colors .. + // We want to find the inside border-color of the dock because it is + // the side most visible to the user. We do this by finding the side + // opposite the position + const position = Utils.getPosition(); + let side = position + 2; + if (side > 3) + side = Math.abs(side - 4); + + const borderColor = themeNode.get_border_color(side); + + return [backgroundColor, borderColor]; + } + + _updateDashColor() { + // Retrieve the color. If needed we will adjust it before passing it to + // this._transparency. + let [backgroundColor] = this._getDefaultColors(); + + if (!backgroundColor) + return; + + const {settings} = Docking.DockManager; + + if (settings.customBackgroundColor) { + // When applying a custom color, we need to check the alpha value, + // if not the opacity will always be overridden by the color below. + // Note that if using 'dynamic' transparency modes, + // the opacity will be set by the opaque/transparent styles anyway. + let newAlpha = Math.round(backgroundColor.alpha / 2.55) / 100; + + ({backgroundColor} = settings); + // backgroundColor is a string like rgb(0,0,0) + const Color = Clutter.Color ?? Cogl.Color; + const [ret, color] = Color.from_string(backgroundColor); + if (!ret) { + logError(new Error(`${backgroundColor} is not a valid color string`)); + return; + } + + if (settings.transparencyMode === TransparencyMode.FIXED) { + newAlpha = settings.backgroundOpacity; + this._customizedBackground = + `rgba(${color.red}, ${color.green}, ${color.blue}, ${newAlpha})`; + } else { + this._customizedBackground = backgroundColor; + } + + this._customizedBorder = this._customizedBackground; + + color.alpha = newAlpha * 255; + this._transparency.setColor(color); + } else { + // backgroundColor is a {Clutter,Cogl}.Color object + this._transparency.setColor(backgroundColor); + } + } + + _updateCustomStyleClasses() { + const {settings} = Docking.DockManager; + + if (settings.applyCustomTheme) + this._actor.add_style_class_name('dashtodock'); + else + this._actor.remove_style_class_name('dashtodock'); + + if (settings.customThemeShrink) + this._actor.add_style_class_name('shrink'); + else + this._actor.remove_style_class_name('shrink'); + + if (settings.runningIndicatorStyle !== 0) + this._actor.add_style_class_name('running-dots'); + else + this._actor.remove_style_class_name('running-dots'); + + // If not the built-in theme option is not selected + if (!settings.applyCustomTheme) { + if (settings.forceStraightCorner) + this._actor.add_style_class_name('straight-corner'); + else + this._actor.remove_style_class_name('straight-corner'); + } else { + this._actor.remove_style_class_name('straight-corner'); + } + } + + updateCustomTheme() { + if (this._destroyed) + throw new Error(`Impossible to update a destroyed ${this.constructor.name}`); + this._updateCustomStyleClasses(); + this._updateDashOpacity(); + this._updateDashColor(); + this._adjustTheme(); + this.emit('updated'); + } + + /** + * Reimported back and adapted from atomdock + */ + _adjustTheme() { + // Prevent shell crash if the actor is not on the stage. + // It happens enabling/disabling repeatedly the extension + if (!this._dash._background.get_stage()) + return; + + const {settings} = Docking.DockManager; + + // Remove prior style edits + this._dash._background.set_style(null); + this._transparency.disable(); + + // If built-in theme is enabled do nothing else + if (settings.applyCustomTheme) + return; + + let newStyle = ''; + const position = Utils.getPosition(settings); + + // obtain theme border settings + const themeNode = this._dash._background.get_theme_node(); + const borderColor = themeNode.get_border_color(St.Side.TOP); + const borderWidth = themeNode.get_border_width(St.Side.TOP); + + // We're copying border and corner styles to left border and top-left + // corner, also removing bottom border and bottom-right corner styles + let borderMissingStyle = ''; + + if (this._rtl && (position !== St.Side.RIGHT)) { + borderMissingStyle = `border-right: ${borderWidth}px solid ${ + borderColor.to_string()};`; + } else if (!this._rtl && (position !== St.Side.LEFT)) { + borderMissingStyle = `border-left: ${borderWidth}px solid ${ + borderColor.to_string()};`; + } + + newStyle = borderMissingStyle; + + if (newStyle) { + // I do call set_style possibly twice so that only the background gets the transition. + // The transition-property css rules seems to be unsupported + this._dash._background.set_style(newStyle); + } + + // Customize background + const fixedTransparency = settings.transparencyMode === TransparencyMode.FIXED; + const defaultTransparency = settings.transparencyMode === TransparencyMode.DEFAULT; + if (!defaultTransparency && !fixedTransparency) { + this._transparency.enable(); + } else if (!defaultTransparency || settings.customBackgroundColor) { + newStyle = `${newStyle}background-color:${this._customizedBackground}; ` + + `border-color:${this._customizedBorder}; ` + + 'transition-delay: 0s; transition-duration: 0.250s;'; + this._dash._background.set_style(newStyle); + } + } + + _bindSettingsChanges() { + const keys = ['transparency-mode', + 'customize-alphas', + 'min-alpha', + 'max-alpha', + 'background-opacity', + 'custom-background-color', + 'background-color', + 'apply-custom-theme', + 'custom-theme-shrink', + 'custom-theme-running-dots', + 'extend-height', + 'force-straight-corner']; + + this._signalsHandler.add(...keys.map(key => [ + Docking.DockManager.settings, + `changed::${key}`, + () => this.updateCustomTheme(), + ])); + } +} +Signals.addSignalMethods(ThemeManager.prototype); + +/** + * The following class is based on the following upstream commit: + * https://git.gnome.org/browse/gnome-shell/commit/?id=447bf55e45b00426ed908b1b1035f472c2466956 + * Transparency when free-floating + */ +class Transparency { + constructor(dock) { + this._dash = dock.dash; + this._actor = this._dash._container; + this._backgroundActor = this._dash._background; + this._dockActor = dock; + this._dock = dock; + this._panel = Main.panel; + this._position = Utils.getPosition(); + + // All these properties are replaced with the ones in the .dummy-opaque + // and .dummy-transparent css classes + this._backgroundColor = '0,0,0'; + this._transparentAlpha = '0.2'; + this._opaqueAlpha = '1'; + this._transparentAlphaBorder = '0.1'; + this._opaqueAlphaBorder = '0.5'; + this._transparentTransition = '0ms'; + this._opaqueTransition = '0ms'; + this._base_actor_style = ''; + + this._signalsHandler = new Utils.GlobalSignalsHandler(); + this._trackedWindows = new Map(); + } + + enable() { + // ensure I never double-register/inject + // although it should never happen + this.disable(); + + this._base_actor_style = this._actor.get_style(); + if (!this._base_actor_style) + this._base_actor_style = ''; + + + let addedSignal = 'child-added'; + let removedSignal = 'child-removed'; + + // for compatibility with Gnome Shell 45 + if (GObject.signal_lookup('actor-added', global.window_group)) { + addedSignal = 'actor-added'; + removedSignal = 'actor-removed'; + } + + this._signalsHandler.addWithLabel(Labels.TRANSPARENCY, [ + global.window_group, + addedSignal, + this._onWindowActorAdded.bind(this), + ], [ + global.window_group, + removedSignal, + this._onWindowActorRemoved.bind(this), + ], [ + global.window_manager, + 'switch-workspace', + this._updateSolidStyle.bind(this), + ], [ + Main.overview, + 'hiding', + this._updateSolidStyle.bind(this), + ], [ + Main.overview, + 'showing', + this._updateSolidStyle.bind(this), + ]); + + // Window signals + global.window_group.get_children().filter(child => { + // An irrelevant window actor ('Gnome-shell') produces an error when the signals are + // disconnected, therefore do not add signals to it. + return child instanceof Meta.WindowActor && + child.get_meta_window().get_wm_class() !== 'Gnome-shell'; + }).forEach(function (win) { + this._onWindowActorAdded(null, win); + }, this); + + if (this._actor.get_stage()) + this._updateSolidStyle(); + + this._updateStyles(); + this._updateSolidStyle(); + + this.emit('transparency-enabled'); + } + + disable() { + // ensure I never double-register/inject + // although it should never happen + this._signalsHandler.removeWithLabel(Labels.TRANSPARENCY); + + for (const key of this._trackedWindows.keys()) { + this._trackedWindows.get(key).forEach(id => { + key.disconnect(id); + }); + } + this._trackedWindows.clear(); + + this.emit('transparency-disabled'); + } + + destroy() { + this.disable(); + this._signalsHandler.destroy(); + } + + _onWindowActorAdded(container, metaWindowActor) { + const signalIds = []; + ['notify::allocation', 'notify::visible'].forEach(s => { + signalIds.push(metaWindowActor.connect(s, this._updateSolidStyle.bind(this))); + }); + this._trackedWindows.set(metaWindowActor, signalIds); + } + + _onWindowActorRemoved(container, metaWindowActor) { + if (!this._trackedWindows.get(metaWindowActor)) + return; + + this._trackedWindows.get(metaWindowActor).forEach(id => { + metaWindowActor.disconnect(id); + }); + this._trackedWindows.delete(metaWindowActor); + this._updateSolidStyle(); + } + + _updateSolidStyle() { + const isNear = this._dockIsNear(); + if (isNear) { + this._backgroundActor.set_style(this._opaque_style); + this._dockActor.remove_style_class_name('transparent'); + this._dockActor.add_style_class_name('opaque'); + } else { + this._backgroundActor.set_style(this._transparent_style); + this._dockActor.remove_style_class_name('opaque'); + this._dockActor.add_style_class_name('transparent'); + } + + this.emit('solid-style-updated', isNear); + } + + _dockIsNear() { + if (this._dockActor.has_style_pseudo_class('overview')) + return false; + /* Get all the windows in the active workspace that are in the primary monitor and visible */ + const activeWorkspace = global.workspace_manager.get_active_workspace(); + const dash = this._dash; + const windows = activeWorkspace.list_windows().filter(metaWindow => { + return metaWindow.get_monitor() === dash._monitorIndex && + metaWindow.showing_on_its_workspace() && + metaWindow.get_window_type() !== Meta.WindowType.DESKTOP && + !metaWindow.skip_taskbar; + }); + + /* Check if at least one window is near enough to the panel. + * If the dock is hidden, we need to account for the space it would take + * up when it slides out. This is avoid an ugly transition. + * */ + let factor = 0; + if (!Docking.DockManager.settings.dockFixed && + this._dock.getDockState() === Docking.State.HIDDEN) + factor = 1; + const [leftCoord, topCoord] = this._actor.get_transformed_position(); + let threshold; + if (this._position === St.Side.LEFT) + threshold = leftCoord + this._actor.get_width() * (factor + 1); + else if (this._position === St.Side.RIGHT) + threshold = leftCoord - this._actor.get_width() * factor; + else if (this._position === St.Side.TOP) + threshold = topCoord + this._actor.get_height() * (factor + 1); + else + threshold = topCoord - this._actor.get_height() * factor; + + const scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; + const isNearEnough = windows.some(metaWindow => { + let coord; + if (this._position === St.Side.LEFT) { + coord = metaWindow.get_frame_rect().x; + return coord < threshold + 5 * scale; + } else if (this._position === St.Side.RIGHT) { + coord = metaWindow.get_frame_rect().x + metaWindow.get_frame_rect().width; + return coord > threshold - 5 * scale; + } else if (this._position === St.Side.TOP) { + coord = metaWindow.get_frame_rect().y; + return coord < threshold + 5 * scale; + } else { + coord = metaWindow.get_frame_rect().y + metaWindow.get_frame_rect().height; + return coord > threshold - 5 * scale; + } + }); + + return isNearEnough; + } + + _updateStyles() { + this._getAlphas(); + + this._transparent_style = `${this._base_actor_style + }background-color: rgba(${ + this._backgroundColor}, ${this._transparentAlpha});` + + `border-color: rgba(${ + this._backgroundColor}, ${this._transparentAlphaBorder});` + + `transition-duration: ${this._transparentTransition}ms;`; + + this._opaque_style = `${this._base_actor_style + }background-color: rgba(${ + this._backgroundColor}, ${this._opaqueAlpha});` + + `border-color: rgba(${ + this._backgroundColor},${this._opaqueAlphaBorder});` + + `transition-duration: ${this._opaqueTransition}ms;`; + + this.emit('styles-updated'); + } + + setColor(color) { + this._backgroundColor = `${color.red},${color.green},${color.blue}`; + this._updateStyles(); + } + + _getAlphas() { + // Create dummy object and add to the uiGroup to get it to the stage + const dummyObject = new St.Bin({ + name: 'dashtodockContainer', + }); + Main.uiGroup.add_child(dummyObject); + + dummyObject.add_style_class_name('dummy-opaque'); + let themeNode = dummyObject.get_theme_node(); + this._opaqueAlpha = themeNode.get_background_color().alpha / 255; + this._opaqueAlphaBorder = themeNode.get_border_color(0).alpha / 255; + this._opaqueTransition = themeNode.get_transition_duration(); + + dummyObject.add_style_class_name('dummy-transparent'); + themeNode = dummyObject.get_theme_node(); + this._transparentAlpha = themeNode.get_background_color().alpha / 255; + this._transparentAlphaBorder = themeNode.get_border_color(0).alpha / 255; + this._transparentTransition = themeNode.get_transition_duration(); + + Main.uiGroup.remove_child(dummyObject); + + const {settings} = Docking.DockManager; + + if (settings.customizeAlphas) { + this._opaqueAlpha = settings.maxAlpha; + this._opaqueAlphaBorder = this._opaqueAlpha / 2; + this._transparentAlpha = settings.minAlpha; + this._transparentAlphaBorder = this._transparentAlpha / 2; + } + } +} +Signals.addSignalMethods(Transparency.prototype); diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/utils.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/utils.js new file mode 100644 index 0000000..67b4d58 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/utils.js @@ -0,0 +1,735 @@ +import { + Clutter, + GLib, + Gio, + GObject, + Meta, + Shell, + St, +} from './dependencies/gi.js'; + +import { + Docking, +} from './imports.js'; + +const {_gi: Gi} = imports; + +export const SignalsHandlerFlags = Object.freeze({ + NONE: 0, + CONNECT_AFTER: 1, +}); + +const GENERIC_KEY = Symbol('generic'); + +/** + * Simplify global signals and function injections handling + * abstract class + */ +const BasicHandler = class DashToDockBasicHandler { + static get genericKey() { + return GENERIC_KEY; + } + + constructor(parentObject) { + this._storage = Object.create(null); + + if (parentObject) { + if (!(parentObject.connect instanceof Function)) + throw new TypeError('Not a valid parent object'); + + if (!(parentObject instanceof GObject.Object) || + GObject.signal_lookup('destroy', parentObject.constructor.$gtype)) { + this._parentObject = parentObject; + this._destroyId = parentObject.connect('destroy', () => this.destroy()); + } + } + } + + add(...args) { + // Convert arguments object to array, concatenate with generic + // Call addWithLabel with ags as if they were passed arguments + this.addWithLabel(GENERIC_KEY, ...args); + } + + clear() { + Object.getOwnPropertySymbols(this._storage).forEach(label => + this.removeWithLabel(label)); + } + + destroy() { + this._parentObject?.disconnect(this._destroyId); + this._parentObject = null; + + this.clear(); + } + + block() { + Object.getOwnPropertySymbols(this._storage).forEach(label => + this.blockWithLabel(label)); + } + + unblock() { + Object.getOwnPropertySymbols(this._storage).forEach(label => + this.unblockWithLabel(label)); + } + + addWithLabel(label, ...args) { + if (typeof label !== 'symbol') + throw new Error(`Invalid label ${label}, must be a symbol`); + + let argsArray = [...args]; + if (argsArray.every(arg => !Array.isArray(arg))) + argsArray = [argsArray]; + + if (this._storage[label] === undefined) + this._storage[label] = []; + + // Skip first element of the arguments + for (const argArray of argsArray) { + if (argArray.length < 3) + throw new Error('Unexpected number of arguments'); + const item = this._storage[label]; + try { + item.push(this._create(...argArray)); + } catch (e) { + logError(e); + } + } + } + + removeWithLabel(label) { + this._storage[label]?.reverse().forEach(item => this._remove(item)); + delete this._storage[label]; + } + + blockWithLabel(label) { + (this._storage[label] || []).forEach(item => this._block(item)); + } + + unblockWithLabel(label) { + (this._storage[label] || []).forEach(item => this._unblock(item)); + } + + _removeByItem(item) { + Object.getOwnPropertySymbols(this._storage).forEach(label => + (this._storage[label] = this._storage[label].filter(it => { + if (!this._itemsEqual(it, item)) + return true; + this._remove(item); + return false; + }))); + } + + // Virtual methods to be implemented by subclass + + /** + * Create single element to be stored in the storage structure + * + * @param _object + * @param _element + * @param _callback + */ + _create(_object, _element, _callback) { + throw new GObject.NotImplementedError(`_create in ${this.constructor.name}`); + } + + /** + * Correctly delete single element + * + * @param _item + */ + _remove(_item) { + throw new GObject.NotImplementedError(`_remove in ${this.constructor.name}`); + } + + /** + * Block single element + * + * @param _item + */ + _block(_item) { + throw new GObject.NotImplementedError(`_block in ${this.constructor.name}`); + } + + /** + * Unblock single element + * + * @param _item + */ + _unblock(_item) { + throw new GObject.NotImplementedError(`_unblock in ${this.constructor.name}`); + } + + _itemsEqual(itemA, itemB) { + if (itemA === itemB) + return true; + + if (itemA.length !== itemB.length) + return false; + + return itemA.every((_, idx) => itemA[idx] === itemB[idx]); + } +}; + +/** + * Manage global signals + */ +export class GlobalSignalsHandler extends BasicHandler { + _create(object, event, callback, flags = SignalsHandlerFlags.NONE) { + if (!object) + throw new Error('Impossible to connect to an invalid object'); + + const after = flags === SignalsHandlerFlags.CONNECT_AFTER; + const connector = after ? object.connect_after : object.connect; + + if (!connector) { + throw new Error(`Requested to connect to signal '${event}', ` + + `but no implementation for 'connect${after ? '_after' : ''}' ` + + `found in ${object.constructor.name}`); + } + + const item = [object]; + const isDestroy = event === 'destroy'; + const isParentObject = object === this._parentObject; + + if (isDestroy && !isParentObject) { + const originalCallback = callback; + callback = () => { + this._removeByItem(item); + originalCallback(); + }; + } + const id = connector.call(object, event, callback); + item.push(id); + + if (isDestroy && isParentObject) { + this._parentObject.disconnect(this._destroyId); + this._destroyId = + this._parentObject.connect('destroy', () => this.destroy()); + } + + return item; + } + + _remove(item) { + const [object, id] = item; + object.disconnect(id); + } + + _block(item) { + const [object, id] = item; + + if (object instanceof GObject.Object) + GObject.Object.prototype.block_signal_handler.call(object, id); + } + + _unblock(item) { + const [object, id] = item; + + if (object instanceof GObject.Object) + GObject.Object.prototype.unblock_signal_handler.call(object, id); + } +} + +/** + * Color manipulation utilities + */ +export class ColorUtils { + // Darken or brigthen color by a fraction dlum + // Each rgb value is modified by the same fraction. + // Return "#rrggbb" string + static ColorLuminance(r, g, b, dlum) { + let rgbString = '#'; + + rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(r * (1 + dlum), 0), 255)), 2); + rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(g * (1 + dlum), 0), 255)), 2); + rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(b * (1 + dlum), 0), 255)), 2); + + return rgbString; + } + + // Convert decimal to an hexadecimal string adding the desired padding + static _decimalToHex(d, padding) { + let hex = d.toString(16); + while (hex.length < padding) + hex = `0${hex}`; + return hex; + } + + // Convert hsv ([0-1, 0-1, 0-1]) to rgb ([0-255, 0-255, 0-255]). + // Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV + // here with h = [0,1] instead of [0, 360] + // Accept either (h,s,v) independently or {h:h, s:s, v:v} object. + // Return {r:r, g:g, b:b} object. + static HSVtoRGB(h, s, v) { + if (arguments.length === 1) + ({s, v, h} = h); + + let r, g, b; + const c = v * s; + const h1 = h * 6; + const x = c * (1 - Math.abs(h1 % 2 - 1)); + const m = v - c; + + if (h1 <= 1) { + r = c + m; + g = x + m; + b = m; + } else if (h1 <= 2) { + r = x + m; + g = c + m; + b = m; + } else if (h1 <= 3) { + r = m; + g = c + m; + b = x + m; + } else if (h1 <= 4) { + r = m; + g = x + m; + b = c + m; + } else if (h1 <= 5) { + r = x + m; + g = m; + b = c + m; + } else { + r = c + m; + g = m; + b = x + m; + } + + return { + r: Math.round(r * 255), + g: Math.round(g * 255), + b: Math.round(b * 255), + }; + } + + // Convert rgb ([0-255, 0-255, 0-255]) to hsv ([0-1, 0-1, 0-1]). + // Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV + // here with h = [0,1] instead of [0, 360] + // Accept either (r,g,b) independently or {r:r, g:g, b:b} object. + // Return {h:h, s:s, v:v} object. + static RGBtoHSV(r, g, b) { + if (arguments.length === 1) + ({r, g, b} = r); + + let h, s; + + const M = Math.max(r, g, b); + const m = Math.min(r, g, b); + const c = M - m; + + if (c === 0) + h = 0; + else if (M === r) + h = ((g - b) / c) % 6; + else if (M === g) + h = (b - r) / c + 2; + else + h = (r - g) / c + 4; + + h /= 6; + const v = M / 255; + if (M !== 0) + s = c / M; + else + s = 0; + + return { + h, + s, + v, + }; + } +} + +/** + * Manage function injection: both instances and prototype can be overridden + * and restored + */ +export class InjectionsHandler extends BasicHandler { + _create(object, name, injectedFunction) { + const original = object[name]; + + if (!(original instanceof Function)) + throw new Error(`Function ${name}() is not available for ${object}`); + + object[name] = function (...args) { + return injectedFunction.call(this, original, ...args); + }; + return [object, name, original]; + } + + _remove(item) { + const [object, name, original] = item; + object[name] = original; + } +} + +/** + * Manage vfunction injection: both instances and prototype can be overridden + * and restored + */ +export class VFuncInjectionsHandler extends BasicHandler { + _create(prototype, name, injectedFunction) { + const original = prototype[`vfunc_${name}`]; + if (!(original instanceof Function)) + throw new Error(`Virtual function ${name} is not available for ${prototype}`); + this._replaceVFunc(prototype, name, injectedFunction); + return [prototype, name]; + } + + _remove(item) { + const [prototype, name] = item; + const originalVFunc = prototype[`vfunc_${name}`]; + try { + // This may fail if trying to reset to a never-overridden vfunc + // as gjs doesn't consider it a function, even if it's true that + // originalVFunc instanceof Function. + this._replaceVFunc(prototype, name, originalVFunc); + } catch { + try { + this._replaceVFunc(prototype, name, function (...args) { + // eslint-disable-next-line no-invalid-this + return originalVFunc.call(this, ...args); + }); + } catch (e) { + logError(e, `Removing vfunc_${name}`); + } + } + } + + _replaceVFunc(prototype, name, func) { + if (Gi.gobject_prototype_symbol && Gi.gobject_prototype_symbol in prototype) + prototype = prototype[Gi.gobject_prototype_symbol]; + + return prototype[Gi.hook_up_vfunc_symbol](name, func); + } +} + +/** + * Manage properties injection: both instances and prototype can be overridden + * and restored + */ +export class PropertyInjectionsHandler extends BasicHandler { + constructor(parentObject, params) { + super(parentObject); + this._params = params; + } + + _create(instance, name, injectedPropertyDescriptor) { + if (!this._params?.allowNewProperty && !(name in instance)) + throw new Error(`Object ${instance} has no '${name}' property`); + + const {prototype} = instance.constructor; + const originalPropertyDescriptor = Object.getOwnPropertyDescriptor(prototype, name) ?? + Object.getOwnPropertyDescriptor(instance, name); + + Object.defineProperty(instance, name, { + ...originalPropertyDescriptor, + ...injectedPropertyDescriptor, + ...{configurable: true}, + }); + return [instance, name, originalPropertyDescriptor]; + } + + _remove(item) { + const [instance, name, originalPropertyDescriptor] = item; + if (originalPropertyDescriptor) + Object.defineProperty(instance, name, originalPropertyDescriptor); + else + delete instance[name]; + } +} + +/** + * Return the actual position reverseing left and right in rtl + */ +export function getPosition() { + const position = Docking.DockManager.settings.dockPosition; + if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL) { + if (position === St.Side.LEFT) + return St.Side.RIGHT; + else if (position === St.Side.RIGHT) + return St.Side.LEFT; + } + return position; +} + +/** + * @param cr + * @param x + * @param y + * @param width + * @param height + * @param isRoundLeft + * @param isRoundRight + * @param stroke + * @param fill + */ +export function drawRoundedLine(cr, x, y, width, height, isRoundLeft, isRoundRight, stroke, fill) { + if (height > width) { + y += Math.floor((height - width) / 2.0); + height = width; + } + + height = 2.0 * Math.floor(height / 2.0); + + const leftRadius = isRoundLeft ? height / 2.0 : 0.0; + const rightRadius = isRoundRight ? height / 2.0 : 0.0; + + cr.moveTo(x + width - rightRadius, y); + cr.lineTo(x + leftRadius, y); + if (isRoundLeft) + cr.arcNegative(x + leftRadius, y + leftRadius, leftRadius, -Math.PI / 2, Math.PI / 2); + else + cr.lineTo(x, y + height); + cr.lineTo(x + width - rightRadius, y + height); + if (isRoundRight) + cr.arcNegative(x + width - rightRadius, y + rightRadius, rightRadius, Math.PI / 2, -Math.PI / 2); + else + cr.lineTo(x + width, y); + cr.closePath(); + + if (fill) { + cr.setSource(fill); + cr.fillPreserve(); + } + if (stroke) + cr.setSource(stroke); + cr.stroke(); +} + +/** + * Convert a signal handler with n value parameters (that is, excluding the + * signal source parameter) to an array of n handlers that are each responsible + * for receiving one of the n values and calling the original handler with the + * most up-to-date arguments. + * + * @param handler + */ +export function splitHandler(handler) { + if (handler.length > 30) + throw new Error('too many parameters'); + + const count = handler.length - 1; + let missingValueBits = (1 << count) - 1; + const values = Array.from({length: count}); + return values.map((_ignored, i) => { + const mask = ~(1 << i); + return (obj, value) => { + values[i] = value; + missingValueBits &= mask; + if (missingValueBits === 0) + handler(obj, ...values); + }; + }); +} + +/** + * Construct a map of gtk application window object paths to MetaWindows. + */ +export function getWindowsByObjectPath() { + const windowsByObjectPath = new Map(); + const {workspaceManager} = global; + const workspaces = [...new Array(workspaceManager.nWorkspaces)].map( + (_c, i) => workspaceManager.get_workspace_by_index(i)); + + workspaces.forEach(ws => { + ws.list_windows().forEach(w => { + const path = w.get_gtk_window_object_path(); + if (path) + windowsByObjectPath.set(path, w); + }); + }); + + return windowsByObjectPath; +} + +/** + * Re-implements shell_app_compare so that can be used to resort running apps + * + * @param appA + * @param appB + */ +export function shellAppCompare(appA, appB) { + if (appA.state !== appB.state) { + if (appA.state === Shell.AppState.RUNNING) + return -1; + return 1; + } + + const windowsA = appA.get_windows(); + const windowsB = appB.get_windows(); + + const isMinimized = windows => !windows.some(w => w.showing_on_its_workspace()); + const minimizedB = isMinimized(windowsB); + if (isMinimized(windowsA) !== minimizedB) { + if (minimizedB) + return -1; + return 1; + } + + if (appA.state === Shell.AppState.RUNNING) { + if (windowsA.length && !windowsB.length) + return -1; + else if (!windowsA.length && windowsB.length) + return 1; + + const lastUserTime = windows => + Math.max(...windows.map(w => w.get_user_time())); + return lastUserTime(windowsB) - lastUserTime(windowsA); + } + + return 0; +} + +/** + * Re-implements shell_app_compare_windows + * + * @param winA + * @param winB + */ +export function shellWindowsCompare(winA, winB) { + const activeWorkspace = global.workspaceManager.get_active_workspace(); + const wsA = winA.get_workspace() === activeWorkspace; + const wsB = winB.get_workspace() === activeWorkspace; + + if (wsA && !wsB) + return -1; + else if (!wsA && wsB) + return 1; + + const visA = winA.showing_on_its_workspace(); + const visB = winB.showing_on_its_workspace(); + + if (visA && !visB) + return -1; + else if (!visA && visB) + return 1; + + return winB.get_user_time() - winA.get_user_time(); +} + +export const CancellableChild = GObject.registerClass({ + Properties: { + 'parent': GObject.ParamSpec.object( + 'parent', 'parent', 'parent', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + Gio.Cancellable.$gtype), + }, +}, +class CancellableChild extends Gio.Cancellable { + _init(parent) { + if (parent && !(parent instanceof Gio.Cancellable)) + throw TypeError('Not a valid cancellable'); + + super._init({parent}); + + if (parent?.is_cancelled()) { + this.cancel(); + return; + } + + this._connectToParent(); + } + + _connectToParent() { + this._connectId = this.parent?.connect(() => { + this._realCancel(); + + if (this._disconnectIdle) + return; + + this._disconnectIdle = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => { + delete this._disconnectIdle; + this._disconnectFromParent(); + return GLib.SOURCE_REMOVE; + }); + }); + } + + _disconnectFromParent() { + if (this._connectId && !this._disconnectIdle) { + this.parent.disconnect(this._connectId); + delete this._connectId; + } + } + + _realCancel() { + Gio.Cancellable.prototype.cancel.call(this); + } + + cancel() { + this._disconnectFromParent(); + this._realCancel(); + } +}); + +/** + * + */ +export function getMonitorManager() { + return global.backend.get_monitor_manager?.() ?? Meta.MonitorManager.get(); +} + +/** + * @param laterType + * @param callback + */ +export function laterAdd(laterType, callback) { + return global.compositor?.get_laters?.().add(laterType, callback) ?? + Meta.later_add(laterType, callback); +} + +/** + * @param id + */ +export function laterRemove(id) { + if (global.compositor?.get_laters) + global.compositor?.get_laters().remove(id); + else + Meta.later_remove(id); +} + +/** + * Up to Gnome Shell 45, the Cairo Context object didn't export the + * `setSourceColor()` method, so Clutter included a function call for + * that, written in C. In Gnome Shell 46, the method was finally exported, + * so that function was removed. + * + * This function is, thus, required for Gnome Shell 45 compatibility. + * + * @param {*} cr A cairo context + * @param {*} sourceColor The new color for source + */ +export function cairoSetSourceColor(cr, sourceColor) { + if (Clutter.cairo_set_source_color) + Clutter.cairo_set_source_color(cr, sourceColor); + else + cr.setSourceColor(sourceColor); +} + +/** + * Specifies if the system supports extended barriers. This function + * is required for Gnome Shell 45 compatibility, which used + * `global.display.supports_extended_barriers`. Gnome Shell 46 moved + * that into global.backend.capabilities. + * + * @returns True if the system supports extended barriers. + */ +export function supportsExtendedBarriers() { + if (global.display.supports_extended_barriers) + return global.display.supports_extended_barriers(); + return !!(global.backend.capabilities & Meta.BackendCapabilities.BARRIERS); +} + +export function addActor(element, actor) { + if (element.add_actor) + element.add_actor(actor); + else + element.add_child(actor); +} + +export const clamp = (v, m, M) => Math.min(Math.max(v, m), M); +export const clampDouble = v => clamp(v, 0, 1); diff --git a/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/windowPreview.js b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/windowPreview.js new file mode 100644 index 0000000..337d5f1 --- /dev/null +++ b/raveos-gnome-theme/theme-data/extensions/installed/dash-to-dock@micxgx.gmail.com/windowPreview.js @@ -0,0 +1,678 @@ +/* + * Credits: + * This file is based on code from the Dash to Panel extension by Jason DeRose + * and code from the Taskbar extension by Zorin OS + * Some code was also adapted from the upstream Gnome Shell source code. + */ + +import { + Clutter, + GLib, + GObject, + Meta, + St, +} from './dependencies/gi.js'; + +import { + BoxPointer, + Main, + PopupMenu, + Workspace, +} from './dependencies/shell/ui.js'; + +import { + Docking, + Theming, + Utils, +} from './imports.js'; + +const PREVIEW_MAX_WIDTH = 250; +const PREVIEW_MAX_HEIGHT = 150; + +const PREVIEW_ANIMATION_DURATION = 250; +const MAX_PREVIEW_GENERATION_ATTEMPTS = 15; + +const MENU_MARGINS = 10; + +export class WindowPreviewMenu extends PopupMenu.PopupMenu { + constructor(source) { + super(source, 0.5, Utils.getPosition()); + + // We want to keep the item hovered while the menu is up + this.blockSourceEvents = true; + + this._source = source; + this._app = this._source.app; + const workArea = Main.layoutManager.getWorkAreaForMonitor( + this._source.monitorIndex); + const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage); + + this.actor.add_style_class_name('app-menu'); + this.actor.set_style( + `max-width: ${Math.round(workArea.width / scaleFactor) - MENU_MARGINS}px; ` + + `max-height: ${Math.round(workArea.height / scaleFactor) - MENU_MARGINS}px;`); + this.actor.hide(); + + // Chain our visibility and lifecycle to that of the source + this._mappedId = this._source.connect('notify::mapped', () => { + if (!this._source.mapped) + this.close(); + }); + this._destroyId = this._source.connect('destroy', this.destroy.bind(this)); + + Utils.addActor(Main.uiGroup, this.actor); + + this.connect('destroy', this._onDestroy.bind(this)); + } + + _redisplay() { + if (this._previewBox) + this._previewBox.destroy(); + this._previewBox = new WindowPreviewList(this._source); + this.addMenuItem(this._previewBox); + this._previewBox._redisplay(); + } + + popup() { + const windows = this._source.getInterestingWindows(); + if (windows.length > 0) { + this._redisplay(); + this.open(BoxPointer.PopupAnimation.FULL); + this.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false); + this._source.emit('sync-tooltip'); + } + } + + _onDestroy() { + if (this._mappedId) + this._source.disconnect(this._mappedId); + + if (this._destroyId) + this._source.disconnect(this._destroyId); + } +} + +class WindowPreviewList extends PopupMenu.PopupMenuSection { + constructor(source) { + super(); + this.actor = new St.ScrollView({ + name: 'dashtodockWindowScrollview', + hscrollbar_policy: St.PolicyType.NEVER, + vscrollbar_policy: St.PolicyType.NEVER, + overlay_scrollbars: true, + enable_mouse_scrolling: true, + }); + + this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); + + const position = Utils.getPosition(); + this.isHorizontal = position === St.Side.BOTTOM || position === St.Side.TOP; + this.box.set_vertical(!this.isHorizontal); + this.box.set_name('dashtodockWindowList'); + Utils.addActor(this.actor, this.box); + this.actor._delegate = this; + + this._shownInitially = false; + + this._source = source; + this.app = source.app; + + this._redisplayId = Main.initializeDeferredWork(this.actor, this._redisplay.bind(this)); + + this.actor.connect('destroy', this._onDestroy.bind(this)); + this._stateChangedId = this.app.connect('windows-changed', + this._queueRedisplay.bind(this)); + } + + _queueRedisplay() { + Main.queueDeferredWork(this._redisplayId); + } + + _onScrollEvent(actor, event) { + // Event coordinates are relative to the stage but can be transformed + // as the actor will only receive events within his bounds. + const [stageX, stageY] = event.get_coords(); + const [,, eventY] = actor.transform_stage_point(stageX, stageY); + const [, actorH] = actor.get_size(); + + // If the scroll event is within a 1px margin from + // the relevant edge of the actor, let the event propagate. + if (eventY >= actorH - 2) + return Clutter.EVENT_PROPAGATE; + + // Skip to avoid double events mouse + if (event.is_pointer_emulated()) + return Clutter.EVENT_STOP; + + let adjustment, delta; + + if (this.isHorizontal) + adjustment = this.actor.get_hscroll_bar().get_adjustment(); + else + adjustment = this.actor.get_vscroll_bar().get_adjustment(); + + const increment = adjustment.step_increment; + + switch (event.get_scroll_direction()) { + case Clutter.ScrollDirection.UP: + delta = -increment; + break; + case Clutter.ScrollDirection.DOWN: + delta = Number(increment); + break; + case Clutter.ScrollDirection.SMOOTH: { + const [dx, dy] = event.get_scroll_delta(); + delta = dy * increment; + delta += dx * increment; + break; + } + } + + adjustment.set_value(adjustment.get_value() + delta); + + return Clutter.EVENT_STOP; + } + + _onDestroy() { + this.app.disconnect(this._stateChangedId); + this._stateChangedId = 0; + } + + _createPreviewItem(window) { + const preview = new WindowPreviewMenuItem(window, Utils.getPosition()); + return preview; + } + + _redisplay() { + const children = this._getMenuItems().filter(actor => { + return actor._window; + }); + + // Windows currently on the menu + const oldWin = children.map(actor => { + return actor._window; + }); + + // All app windows with a static order + const newWin = this._source.getInterestingWindows().sort((a, b) => + a.get_stable_sequence() > b.get_stable_sequence()); + + const addedItems = []; + const removedActors = []; + + let newIndex = 0; + let oldIndex = 0; + + while (newIndex < newWin.length || oldIndex < oldWin.length) { + const currentOldWin = oldWin[oldIndex]; + const currentNewWin = newWin[newIndex]; + + // No change at oldIndex/newIndex + if (currentOldWin === currentNewWin) { + oldIndex++; + newIndex++; + continue; + } + + // Window removed at oldIndex + if (currentOldWin && !newWin.includes(currentOldWin)) { + removedActors.push(children[oldIndex]); + oldIndex++; + continue; + } + + // Window added at newIndex + if (currentNewWin && !oldWin.includes(currentNewWin)) { + addedItems.push({ + item: this._createPreviewItem(currentNewWin), + pos: newIndex, + }); + newIndex++; + continue; + } + + // Window moved + const insertHere = newWin[newIndex + 1] && + newWin[newIndex + 1] === currentOldWin; + const alreadyRemoved = removedActors.reduce((result, actor) => + result || actor._window === currentNewWin, false); + + if (insertHere || alreadyRemoved) { + addedItems.push({ + item: this._createPreviewItem(currentNewWin), + pos: newIndex + removedActors.length, + }); + newIndex++; + } else { + removedActors.push(children[oldIndex]); + oldIndex++; + } + } + + for (let i = 0; i < addedItems.length; i++) { + this.addMenuItem(addedItems[i].item, + addedItems[i].pos); + } + + for (let i = 0; i < removedActors.length; i++) { + const item = removedActors[i]; + if (this._shownInitially) + item._animateOutAndDestroy(); + else + item.actor.destroy(); + } + + // Skip animations on first run when adding the initial set + // of items, to avoid all items zooming in at once + const animate = this._shownInitially; + + if (!this._shownInitially) + this._shownInitially = true; + + for (let i = 0; i < addedItems.length; i++) + addedItems[i].item.show(animate); + + // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 + // Without it, StBoxLayout may use a stale size cache + this.box.queue_relayout(); + + if (newWin.length < 1) + this._getTopMenu().close(~0); + + // As for upstream: + // St.ScrollView always requests space horizontally for a possible vertical + // scrollbar if in AUTOMATIC mode. Doing better would require implementation + // of width-for-height in St.BoxLayout and St.ScrollView. This looks bad + // when we *don't* need it, so turn off the scrollbar when that's true. + // Dynamic changes in whether we need it aren't handled properly. + const needsScrollbar = this._needsScrollbar(); + const scrollbarPolicy = needsScrollbar + ? St.PolicyType.AUTOMATIC : St.PolicyType.NEVER; + if (this.isHorizontal) + this.actor.hscrollbarPolicy = scrollbarPolicy; + else + this.actor.vscrollbarPolicy = scrollbarPolicy; + + if (needsScrollbar) + this.actor.add_style_pseudo_class('scrolled'); + else + this.actor.remove_style_pseudo_class('scrolled'); + } + + _needsScrollbar() { + const topMenu = this._getTopMenu(); + const topThemeNode = topMenu.actor.get_theme_node(); + if (this.isHorizontal) { + const [topMinWidth_, topNaturalWidth] = + topMenu.actor.get_preferred_width(-1); + const topMaxWidth = topThemeNode.get_max_width(); + return topMaxWidth >= 0 && topNaturalWidth >= topMaxWidth; + } else { + const [topMinHeight_, topNaturalHeight] = + topMenu.actor.get_preferred_height(-1); + const topMaxHeight = topThemeNode.get_max_height(); + return topMaxHeight >= 0 && topNaturalHeight >= topMaxHeight; + } + } + + isAnimatingOut() { + return this.actor.get_children().reduce((result, actor) => { + return result || actor.animatingOut; + }, false); + } +} + +export const WindowPreviewMenuItem = GObject.registerClass( +class WindowPreviewMenuItem extends PopupMenu.PopupBaseMenuItem { + _init(window, position, params) { + super._init(params); + + this._window = window; + this._destroyId = 0; + this._windowAddedId = 0; + + // We don't want this: it adds spacing on the left of the item. + this.remove_child(this._ornamentIcon); + this.add_style_class_name('dashtodock-app-well-preview-menu-item'); + this.add_style_class_name(Theming.PositionStyleClass[position]); + if (Docking.DockManager.settings.customThemeShrink) + this.add_style_class_name('shrink'); + + // Now we don't have to set PREVIEW_MAX_WIDTH and PREVIEW_MAX_HEIGHT as + // preview size - that made all kinds of windows either stretched or + // squished (aspect ratio problem) + this._cloneBin = new St.Bin(); + + this._updateWindowPreviewSize(); + + // TODO: improve the way the closebutton is layout. Just use some padding + // for the moment. + this._cloneBin.set_style('padding-bottom: 0.5em'); + + const buttonLayout = Meta.prefs_get_button_layout(); + this.closeButton = new St.Button({ + style_class: 'window-close', + opacity: 0, + x_expand: true, + y_expand: true, + x_align: buttonLayout.left_buttons.includes(Meta.ButtonFunction.CLOSE) + ? Clutter.ActorAlign.START : Clutter.ActorAlign.END, + y_align: Clutter.ActorAlign.START, + }); + Utils.addActor(this.closeButton, new St.Icon({icon_name: 'window-close-symbolic'})); + this.closeButton.connect('clicked', () => this._closeWindow()); + + const overlayGroup = new Clutter.Actor({ + layout_manager: new Clutter.BinLayout(), + y_expand: true, + }); + + overlayGroup.add_child(this._cloneBin); + overlayGroup.add_child(this.closeButton); + + const label = new St.Label({text: window.get_title()}); + label.set_style(`max-width: ${PREVIEW_MAX_WIDTH}px`); + const labelBin = new St.Bin({ + child: label, + x_align: Clutter.ActorAlign.CENTER, + }); + + this._windowTitleId = this._window.connect('notify::title', () => { + label.set_text(this._window.get_title()); + }); + + const box = new St.BoxLayout({ + vertical: true, + reactive: true, + x_expand: true, + }); + + if (box.add) { + box.add(overlayGroup); + box.add(labelBin); + } else { + box.add_child(overlayGroup); + box.add_child(labelBin); + } + this._box = box; + this.add_child(box); + + this._cloneTexture(window); + + this.connect('destroy', this._onDestroy.bind(this)); + } + + vfunc_style_changed() { + super.vfunc_style_changed(); + + // For some crazy clutter / St reason we can't just have this handled + // automatically or here via vfunc_allocate + vfunc_get_preferred_* + // because if we do so, the St paddings on first / last child are lost + const themeNode = this.get_theme_node(); + let [minWidth, naturalWidth] = this._box.get_preferred_width(-1); + let [minHeight, naturalHeight] = this._box.get_preferred_height(naturalWidth); + [minWidth, naturalWidth] = themeNode.adjust_preferred_width(minWidth, naturalWidth); + [minHeight, naturalHeight] = themeNode.adjust_preferred_height(minHeight, naturalHeight); + this.set({minWidth, naturalWidth, minHeight, naturalHeight}); + } + + _getWindowPreviewSize() { + const emptySize = [0, 0, 0]; + + const mutterWindow = this._window.get_compositor_private(); + if (!mutterWindow?.get_texture()) + return emptySize; + + const [width, height] = mutterWindow.get_size(); + if (!width || !height) + return emptySize; + + let {previewSizeScale: scale} = Docking.DockManager.settings; + if (!scale) { + // a simple example with 1680x1050: + // * 250/1680 = 0,1488 + // * 150/1050 = 0,1429 + // => scale is 0,1429 + scale = Math.min(1.0, PREVIEW_MAX_WIDTH / width, PREVIEW_MAX_HEIGHT / height); + } + + scale *= St.ThemeContext.get_for_stage(global.stage).scaleFactor; + + // width and height that we wanna multiply by scale + return [width, height, scale]; + } + + _updateWindowPreviewSize() { + // This gets the actual windows size for the preview + [this._width, this._height, this._scale] = this._getWindowPreviewSize(); + this._cloneBin.set_size(this._width * this._scale, this._height * this._scale); + } + + _cloneTexture(metaWin) { + // Newly-created windows are added to a workspace before + // the compositor finds out about them... + if (!this._width || !this._height) { + this._cloneTextureLater = Utils.laterAdd(Meta.LaterType.BEFORE_REDRAW, () => { + // Check if there's still a point in getting the texture, + // otherwise this could go on indefinitely + this._updateWindowPreviewSize(); + + if (this._width && this._height) { + this._cloneTexture(metaWin); + } else { + this._cloneAttempt = (this._cloneAttempt || 0) + 1; + if (this._cloneAttempt < MAX_PREVIEW_GENERATION_ATTEMPTS) + return GLib.SOURCE_CONTINUE; + } + delete this._cloneTextureLater; + return GLib.SOURCE_REMOVE; + }); + return; + } + + const mutterWindow = metaWin.get_compositor_private(); + const clone = new Clutter.Clone({ + source: mutterWindow, + reactive: true, + width: this._width * this._scale, + height: this._height * this._scale, + }); + + // when the source actor is destroyed, i.e. the window closed, first destroy the clone + // and then destroy the menu item (do this animating out) + this._destroyId = mutterWindow.connect('destroy', () => { + clone.destroy(); + this._destroyId = 0; // avoid to try to disconnect this signal from mutterWindow in _onDestroy(), + // as the object was just destroyed + this._animateOutAndDestroy(); + }); + + this._clone = clone; + this._mutterWindow = mutterWindow; + this._cloneBin.set_child(this._clone); + + this._clone.connect('destroy', () => { + if (this._destroyId) { + mutterWindow.disconnect(this._destroyId); + this._destroyId = 0; + } + this._clone = null; + }); + } + + _windowCanClose() { + return this._window.can_close() && + !this._hasAttachedDialogs(); + } + + _closeWindow() { + this._workspace = this._window.get_workspace(); + + // This mechanism is copied from the workspace.js upstream code + // It forces window activation if the windows don't get closed, + // for instance because asking user confirmation, by monitoring the opening of + // such additional confirmation window + this._windowAddedId = this._workspace.connect('window-added', + this._onWindowAdded.bind(this)); + + this.deleteAllWindows(); + } + + deleteAllWindows() { + // Delete all windows, starting from the bottom-most (most-modal) one + // let windows = this._window.get_compositor_private().get_children(); + const windows = this._clone.get_children(); + for (let i = windows.length - 1; i >= 1; i--) { + const realWindow = windows[i].source; + const metaWindow = realWindow.meta_window; + + metaWindow.delete(global.get_current_time()); + } + + this._window.delete(global.get_current_time()); + } + + _onWindowAdded(workspace, win) { + const metaWindow = this._window; + + if (win.get_transient_for() === metaWindow) { + workspace.disconnect(this._windowAddedId); + this._windowAddedId = 0; + + // use an idle handler to avoid mapping problems - + // see comment in Workspace._windowAdded + const activationEvent = Clutter.get_current_event(); + this._windowAddedLater = Utils.laterAdd(Meta.LaterType.BEFORE_REDRAW, () => { + delete this._windowAddedLater; + this.emit('activate', activationEvent); + return GLib.SOURCE_REMOVE; + }); + } + } + + _hasAttachedDialogs() { + // count transient windows + let n = 0; + this._window.foreach_transient(() => { + n++; + }); + return n > 0; + } + + vfunc_key_focus_in() { + super.vfunc_key_focus_in(); + this._showCloseButton(); + } + + vfunc_key_focus_out() { + super.vfunc_key_focus_out(); + this._hideCloseButton(); + } + + vfunc_enter_event(crossingEvent) { + this._showCloseButton(); + return super.vfunc_enter_event(crossingEvent); + } + + vfunc_leave_event(crossingEvent) { + this._hideCloseButton(); + return super.vfunc_leave_event(crossingEvent); + } + + _idleToggleCloseButton() { + this._idleToggleCloseId = 0; + + this._hideCloseButton(); + + return GLib.SOURCE_REMOVE; + } + + _showCloseButton() { + if (this._windowCanClose()) { + this.closeButton.show(); + this.closeButton.remove_all_transitions(); + this.closeButton.ease({ + opacity: 255, + duration: Workspace.WINDOW_OVERLAY_FADE_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }); + } + } + + _hideCloseButton() { + if (this.closeButton.has_pointer || + this.get_children().some(a => a.has_pointer)) + return; + + this.closeButton.remove_all_transitions(); + this.closeButton.ease({ + opacity: 0, + duration: Workspace.WINDOW_OVERLAY_FADE_TIME, + mode: Clutter.AnimationMode.EASE_IN_QUAD, + }); + } + + show(animate) { + const fullWidth = this.get_width(); + + this.opacity = 0; + this.set_width(0); + + const time = animate ? PREVIEW_ANIMATION_DURATION : 0; + this.remove_all_transitions(); + this.ease({ + opacity: 255, + width: fullWidth, + duration: time, + mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD, + }); + } + + _animateOutAndDestroy() { + this.remove_all_transitions(); + this.ease({ + opacity: 0, + duration: PREVIEW_ANIMATION_DURATION, + }); + + this.ease({ + width: 0, + height: 0, + duration: PREVIEW_ANIMATION_DURATION, + delay: PREVIEW_ANIMATION_DURATION, + onComplete: () => this.destroy(), + }); + } + + activate() { + Main.activateWindow(this._window); + this._getTopMenu().close(); + } + + _onDestroy() { + if (this._cloneTextureLater) { + Utils.laterRemove(this._cloneTextureLater); + delete this._cloneTextureLater; + } + + if (this._windowAddedLater) { + Utils.laterRemove(this._windowAddedLater); + delete this._windowAddedLater; + } + + if (this._windowAddedId > 0) { + this._workspace.disconnect(this._windowAddedId); + this._windowAddedId = 0; + } + + if (this._destroyId > 0) { + this._mutterWindow.disconnect(this._destroyId); + this._destroyId = 0; + } + + if (this._windowTitleId > 0) { + this._window.disconnect(this._windowTitleId); + this._windowTitleId = 0; + } + } +}); -- cgit v1.3