summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2021-03-11 03:17:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2021-03-11 03:17:10 +0000
commiteb8579a9b1e6849b9df65df6909f89e0e748afb7 (patch)
tree4652461f032932c62a3a4e358f923a3e09fddde1
parentInitial commit. (diff)
downloadgnome-shell-extension-multi-monitors-eb8579a9b1e6849b9df65df6909f89e0e748afb7.tar.xz
gnome-shell-extension-multi-monitors-eb8579a9b1e6849b9df65df6909f89e0e748afb7.zip
Adding upstream version 16.upstream/16upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.project11
-rw-r--r--LICENSE340
-rw-r--r--README.md31
-rw-r--r--multi-monitors-add-on@spin83/convenience.js93
-rw-r--r--multi-monitors-add-on@spin83/extension.js308
-rw-r--r--multi-monitors-add-on@spin83/icons/multi-monitors-l-symbolic.svg392
-rw-r--r--multi-monitors-add-on@spin83/icons/multi-monitors-r-symbolic.svg393
-rw-r--r--multi-monitors-add-on@spin83/indicator.js133
-rw-r--r--multi-monitors-add-on@spin83/locale/de/LC_MESSAGES/multi-monitors-add-on.mobin0 -> 1896 bytes
-rw-r--r--multi-monitors-add-on@spin83/locale/de/LC_MESSAGES/multi-monitors-add-on.po81
-rw-r--r--multi-monitors-add-on@spin83/locale/es/LC_MESSAGES/multi-monitors-add-on.mobin0 -> 1774 bytes
-rw-r--r--multi-monitors-add-on@spin83/locale/es/LC_MESSAGES/multi-monitors-add-on.po77
-rw-r--r--multi-monitors-add-on@spin83/locale/fr/LC_MESSAGES/multi-monitors-add-on.mobin0 -> 1889 bytes
-rw-r--r--multi-monitors-add-on@spin83/locale/fr/LC_MESSAGES/multi-monitors-add-on.po81
-rw-r--r--multi-monitors-add-on@spin83/locale/it/LC_MESSAGES/multi-monitors-add-on.mobin0 -> 1804 bytes
-rw-r--r--multi-monitors-add-on@spin83/locale/it/LC_MESSAGES/multi-monitors-add-on.po81
-rw-r--r--multi-monitors-add-on@spin83/locale/pl/LC_MESSAGES/multi-monitors-add-on.mobin0 -> 1817 bytes
-rw-r--r--multi-monitors-add-on@spin83/locale/pl/LC_MESSAGES/multi-monitors-add-on.po79
-rw-r--r--multi-monitors-add-on@spin83/metadata.json10
-rw-r--r--multi-monitors-add-on@spin83/mmcalendar.js381
-rw-r--r--multi-monitors-add-on@spin83/mmlayout.js278
-rw-r--r--multi-monitors-add-on@spin83/mmoverview.js826
-rw-r--r--multi-monitors-add-on@spin83/mmpanel.js632
-rw-r--r--multi-monitors-add-on@spin83/multi-monitors-add-on.pot78
-rw-r--r--multi-monitors-add-on@spin83/prefs.js249
-rw-r--r--multi-monitors-add-on@spin83/schemas/gschemas.compiledbin0 -> 796 bytes
-rw-r--r--multi-monitors-add-on@spin83/schemas/org.gnome.shell.extensions.multi-monitors-add-on.gschema.xml59
-rw-r--r--multi-monitors-add-on@spin83/stylesheet.css29
28 files changed, 4642 insertions, 0 deletions
diff --git a/.project b/.project
new file mode 100644
index 0000000..58f94f4
--- /dev/null
+++ b/.project
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>multi-monitors-add-on</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ </buildSpec>
+ <natures>
+ </natures>
+</projectDescription>
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d6a9326
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,340 @@
+GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 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.
+
+ {description}
+ Copyright (C) {year} {fullname}
+
+ 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.
+
+ {signature of Ty Coon}, 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/README.md b/README.md
new file mode 100644
index 0000000..0b4c6bf
--- /dev/null
+++ b/README.md
@@ -0,0 +1,31 @@
+Multi Monitors Add-On
+=====================
+
+Extension inspired by https://github.com/darkxst/multiple-monitor-panels
+and rewritten from scratch for gnome-shell version 3.10.4. Adds panels
+and thumbnails for additional monitors. Settings changes are applied
+in dynamic fashion, no restart needed.
+
+Versions
+========
+
+* Branch [master](https://github.com/spin83/multi-monitors-add-on/tree/master) contains extension for GNOME 3.24, 3.26, 3.28 and 3.30
+* Branch [gnome-3-20_3-22](https://github.com/spin83/multi-monitors-add-on/tree/gnome-3-20_3-22) contains extension for GNOME 3.20 and 3.22
+* Branch [gnome-3-16_3-18](https://github.com/spin83/multi-monitors-add-on/tree/gnome-3-16_3-18) contains extension for GNOME 3.16 and 3.18
+* Branch [gnome-3-14](https://github.com/spin83/multi-monitors-add-on/tree/gnome-3-14) contains extension for GNOME 3.14
+* Branch [gnome-3-10](https://github.com/spin83/multi-monitors-add-on/tree/gnome-3-10) contains extension for GNOME 3.10
+
+Installation from git
+=====================
+
+ git clone git://github.com/spin83/multi-monitors-add-on.git
+ cd multi-monitors-add-on
+ cp -r multi-monitors-add-on@spin83 ~/.local/share/gnome-shell/extensions
+
+Restart the shell and then enable the extension.
+
+License
+=======
+
+Multi Monitors Add-On extension is distributed under the terms of the
+GNU General Public License, version 2 or later. See the LICENSE file for details.
diff --git a/multi-monitors-add-on@spin83/convenience.js b/multi-monitors-add-on@spin83/convenience.js
new file mode 100644
index 0000000..bbc8608
--- /dev/null
+++ b/multi-monitors-add-on@spin83/convenience.js
@@ -0,0 +1,93 @@
+/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (c) 2011-2012, Giovanni Campagna <scampa.giovanni@gmail.com>
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of the GNOME nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+ 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 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.
+*/
+
+const Gettext = imports.gettext;
+const Gio = imports.gi.Gio;
+
+const Config = imports.misc.config;
+const ExtensionUtils = imports.misc.extensionUtils;
+
+/**
+ * initTranslations:
+ * @domain: (optional): the gettext domain to use
+ *
+ * Initialize Gettext to load translations from extensionsdir/locale.
+ * If @domain is not provided, it will be taken from metadata['gettext-domain']
+ */
+function initTranslations(domain) {
+ let extension = ExtensionUtils.getCurrentExtension();
+
+ domain = domain || extension.metadata['gettext-domain'];
+
+ // check if this extension was built with "make zip-file", and thus
+ // has the locale files in a subfolder
+ // otherwise assume that extension has been installed in the
+ // same prefix as gnome-shell
+ let localeDir = extension.dir.get_child('locale');
+ if (localeDir.query_exists(null))
+ Gettext.bindtextdomain(domain, localeDir.get_path());
+ else
+ Gettext.bindtextdomain(domain, Config.LOCALEDIR);
+}
+
+/**
+ * getSettings:
+ * @schema: (optional): the GSettings schema id
+ *
+ * Builds and return a GSettings schema for @schema, using schema files
+ * in extensionsdir/schemas. If @schema is not provided, it is taken from
+ * metadata['settings-schema'].
+ */
+function getSettings(schema) {
+ let extension = ExtensionUtils.getCurrentExtension();
+
+ schema = schema || extension.metadata['settings-schema'];
+
+ const GioSSS = Gio.SettingsSchemaSource;
+
+ // check if this extension was built with "make zip-file", and thus
+ // has the schema files in a subfolder
+ // otherwise assume that extension has been installed in the
+ // same prefix as gnome-shell (and therefore schemas are available
+ // in the standard folders)
+ let schemaDir = extension.dir.get_child('schemas');
+ let schemaSource;
+ if (schemaDir.query_exists(null))
+ schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
+ GioSSS.get_default(),
+ false);
+ else
+ schemaSource = GioSSS.get_default();
+
+ let schemaObj = schemaSource.lookup(schema, true);
+ if (!schemaObj)
+ throw new Error('Schema ' + schema + ' could not be found for extension '
+ + extension.metadata.uuid + '. Please check your installation.');
+
+ return new Gio.Settings({ settings_schema: schemaObj });
+}
+
diff --git a/multi-monitors-add-on@spin83/extension.js b/multi-monitors-add-on@spin83/extension.js
new file mode 100644
index 0000000..b629e7c
--- /dev/null
+++ b/multi-monitors-add-on@spin83/extension.js
@@ -0,0 +1,308 @@
+/*
+Copyright (C) 2014 spin83
+
+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, visit https://www.gnu.org/licenses/.
+*/
+
+const Lang = imports.lang;
+
+const Gio = imports.gi.Gio;
+const Meta = imports.gi.Meta;
+
+const Main = imports.ui.main;
+
+const WorkspacesView = imports.ui.workspacesView;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const MultiMonitors = ExtensionUtils.getCurrentExtension();
+const Convenience = MultiMonitors.imports.convenience;
+
+const MMLayout = MultiMonitors.imports.mmlayout;
+const MMOverview = MultiMonitors.imports.mmoverview;
+const MMIndicator = MultiMonitors.imports.indicator;
+
+const Config = imports.misc.config;
+
+const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides';
+const MUTTER_SCHEMA = 'org.gnome.mutter';
+const WORKSPACES_ONLY_ON_PRIMARY_ID = 'workspaces-only-on-primary';
+
+const SHOW_INDICATOR_ID = 'show-indicator';
+const SHOW_THUMBNAILS_SLIDER_ID = 'show-thumbnails-slider';
+
+const MultiMonitorsAddOn = new Lang.Class({
+ Name: 'MultiMonitorsAddOn',
+
+ _init() {
+ this._settings = Convenience.getSettings();
+ this._ov_settings = new Gio.Settings({ schema: OVERRIDE_SCHEMA });
+ this._mu_settings = new Gio.Settings({ schema: MUTTER_SCHEMA });
+
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+
+ this.mmIndicator = null;
+ Main.mmOverview = null;
+ Main.mmLayoutManager = null;
+
+ this._mmMonitors = 0;
+ },
+
+ _showIndicator() {
+ if(this._settings.get_boolean(SHOW_INDICATOR_ID)) {
+ if(!this.mmIndicator) {
+ this.mmIndicator = Main.panel.addToStatusArea('MultiMonitorsAddOn', new MMIndicator.MultiMonitorsIndicator());
+ }
+ }
+ else {
+ this._hideIndicator();
+ }
+ },
+
+ _hideIndicator() {
+ if(this.mmIndicator) {
+ this.mmIndicator.destroy();
+ this.mmIndicator = null;
+ }
+ },
+
+ _showThumbnailsSlider() {
+ if(this._settings.get_boolean(SHOW_THUMBNAILS_SLIDER_ID)){
+
+ if(this._ov_settings.get_boolean(WORKSPACES_ONLY_ON_PRIMARY_ID))
+ this._ov_settings.set_boolean(WORKSPACES_ONLY_ON_PRIMARY_ID, false);
+ if(this._mu_settings.get_boolean(WORKSPACES_ONLY_ON_PRIMARY_ID))
+ this._mu_settings.set_boolean(WORKSPACES_ONLY_ON_PRIMARY_ID, false);
+
+ Main.mmOverview = [];
+ for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
+ let monitor = Main.layoutManager.monitors[i];
+ if(i != Main.layoutManager.primaryIndex) {
+ Main.mmOverview[i] = new MMOverview.MultiMonitorsOverview(i);
+ Main.mmOverview[i].init();
+ }
+ }
+
+ if (Main.overview.visible)
+ return;
+
+ let workspacesDisplay = Main.overview.viewSelector._workspacesDisplay;
+ if (workspacesDisplay._restackedNotifyId === undefined) {
+ workspacesDisplay._restackedNotifyId = 0;
+ }
+ workspacesDisplay.hide();
+ workspacesDisplay.actor._delegate = null;
+ workspacesDisplay.actor.destroy();
+ Main.overview.viewSelector._workspacesPage.hide();
+ Main.overview.viewSelector._workspacesPage.destroy();
+ workspacesDisplay.actor = null;
+
+ workspacesDisplay = new MMOverview.MultiMonitorsWorkspacesDisplay();
+ Main.overview.viewSelector._workspacesDisplay = workspacesDisplay;
+ Main.overview.viewSelector._workspacesPage = Main.overview.viewSelector._addPage(workspacesDisplay.actor,
+ _("Windows"), 'focus-windows-symbolic');
+ if (Main.overview.visible) {
+ Main.overview._controls._updateWorkspacesGeometry();
+ Main.overview.viewSelector._workspacesPage.show();
+ workspacesDisplay.show();
+ }
+ }
+ else{
+ this._hideThumbnailsSlider();
+ }
+ },
+
+ _hideThumbnailsSlider() {
+ if (Main.mmOverview) {
+
+ if (!Main.overview.visible) {
+ let workspacesDisplay = Main.overview.viewSelector._workspacesDisplay;
+ workspacesDisplay.hide();
+ workspacesDisplay.actor._delegate = null;
+ workspacesDisplay.actor.destroy();
+ Main.overview.viewSelector._workspacesPage.hide();
+ Main.overview.viewSelector._workspacesPage.destroy();
+ workspacesDisplay.actor = null;
+
+ workspacesDisplay = new WorkspacesView.WorkspacesDisplay();
+ Main.overview.viewSelector._workspacesDisplay = workspacesDisplay;
+ Main.overview.viewSelector._workspacesPage = Main.overview.viewSelector._addPage(workspacesDisplay.actor,
+ _("Windows"), 'focus-windows-symbolic');
+ }
+
+ for (let i = 0; i < Main.mmOverview.length; i++) {
+ if(Main.mmOverview[i])
+ Main.mmOverview[i].destroy();
+ }
+ Main.mmOverview = null;
+ }
+ },
+
+ _relayout() {
+// global.log(".....................................................................")
+ if(this._mmMonitors!=Main.layoutManager.monitors.length){
+ this._mmMonitors = Main.layoutManager.monitors.length;
+ global.log("pi:"+Main.layoutManager.primaryIndex);
+ for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
+ let monitor = Main.layoutManager.monitors[i];
+ global.log("i:"+i+" x:"+monitor.x+" y:"+monitor.y+" w:"+monitor.width+" h:"+monitor.height);
+ }
+ this._hideThumbnailsSlider();
+ this._showThumbnailsSlider();
+ }
+ },
+
+ _switchOffThumbnails() {
+ if(this._ov_settings.get_boolean(WORKSPACES_ONLY_ON_PRIMARY_ID))
+ this._settings.set_boolean(SHOW_THUMBNAILS_SLIDER_ID, false);
+ if(this._mu_settings.get_boolean(WORKSPACES_ONLY_ON_PRIMARY_ID))
+ this._settings.set_boolean(SHOW_THUMBNAILS_SLIDER_ID, false);
+ },
+
+ enable(version) {
+ global.log("Enable Multi Monitors Add-On ("+version+")...")
+
+ if(Main.panel.statusArea.MultiMonitorsAddOn)
+ disable();
+
+ this._mmMonitors = 0;
+
+ this._switchOffThumbnailsOvId = this._ov_settings.connect('changed::'+WORKSPACES_ONLY_ON_PRIMARY_ID,
+ this._switchOffThumbnails.bind(this));
+ this._switchOffThumbnailsMuId = this._mu_settings.connect('changed::'+WORKSPACES_ONLY_ON_PRIMARY_ID,
+ this._switchOffThumbnails.bind(this));
+
+ this._showIndicatorId = this._settings.connect('changed::'+SHOW_INDICATOR_ID, this._showIndicator.bind(this));
+ this._showIndicator();
+
+ Main.mmLayoutManager = new MMLayout.MultiMonitorsLayoutManager();
+ this._showPanelId = this._settings.connect('changed::'+MMLayout.SHOW_PANEL_ID, Main.mmLayoutManager.showPanel.bind(Main.mmLayoutManager));
+ Main.mmLayoutManager.showPanel();
+
+ this._showThumbnailsSliderId = this._settings.connect('changed::'+SHOW_THUMBNAILS_SLIDER_ID, this._showThumbnailsSlider.bind(this));
+ this._relayoutId = Main.layoutManager.connect('monitors-changed', this._relayout.bind(this));
+ this._relayout();
+ },
+
+ disable() {
+ Main.layoutManager.disconnect(this._relayoutId);
+ this._ov_settings.disconnect(this._switchOffThumbnailsOvId);
+ this._mu_settings.disconnect(this._switchOffThumbnailsMuId);
+
+ this._settings.disconnect(this._showPanelId);
+ this._settings.disconnect(this._showThumbnailsSliderId);
+ this._settings.disconnect(this._showIndicatorId);
+
+
+ this._hideIndicator();
+
+ Main.mmLayoutManager.hidePanel();
+ Main.mmLayoutManager = null;
+
+ this._hideThumbnailsSlider();
+ this._mmMonitors = 0;
+
+ global.log("Disable Multi Monitors Add-On ...")
+ }
+});
+
+let multiMonitorsAddOn = null;
+let version = null;
+
+function init(extensionMeta) {
+ Convenience.initTranslations();
+ let theme = imports.gi.Gtk.IconTheme.get_default();
+ theme.append_search_path(extensionMeta.path + "/icons");
+
+ // fix bug in panel: Destroy function many time added to this same indicator.
+ Main.panel._ensureIndicator = function(role) {
+ let indicator = this.statusArea[role];
+ if (indicator) {
+ indicator.container.show();
+ return null;
+ }
+ else {
+ let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
+ if (!constructor) {
+ // This icon is not implemented (this is a bug)
+ return null;
+ }
+ indicator = new constructor(this);
+ this.statusArea[role] = indicator;
+ }
+ return indicator;
+ };
+
+ // fix bug in workspacesView: Object, has been already deallocated — impossible to access it.
+ WorkspacesView.WorkspacesDisplay.prototype._parentSet = function(actor, oldParent) {
+ if (oldParent && this._notifyOpacityId)
+ oldParent.disconnect(this._notifyOpacityId);
+ this._notifyOpacityId = 0;
+
+ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
+ if (!this.actor)
+ return;
+ let newParent = this.actor.get_parent();
+ if (!newParent)
+ return;
+
+ // This is kinda hackish - we want the primary view to
+ // appear as parent of this.actor, though in reality it
+ // is added directly to Main.layoutManager.overviewGroup
+ this._notifyOpacityId = newParent.connect('notify::opacity', () => {
+ let opacity = this.actor.get_parent().opacity;
+ let primaryView = this._getPrimaryView();
+ if (!primaryView)
+ return;
+ primaryView.actor.opacity = opacity;
+ primaryView.actor.visible = opacity != 0;
+ });
+ });
+ };
+
+ let metaVersion = MultiMonitors.metadata['version'];
+ if (Number.isFinite(metaVersion)) {
+ version = 'v'+Math.trunc(metaVersion);
+ switch(Math.round((metaVersion%1)*10)) {
+ case 0:
+ break;
+ case 1:
+ version += '+bugfix';
+ break;
+ case 2:
+ version += '+develop';
+ break;
+ default:
+ version += '+modified';
+ break;
+ }
+ }
+ else
+ version = metaVersion;
+}
+
+function enable() {
+ if (multiMonitorsAddOn !== null)
+ return;
+
+ multiMonitorsAddOn = new MultiMonitorsAddOn();
+ multiMonitorsAddOn.enable(version);
+}
+
+function disable() {
+ if (multiMonitorsAddOn == null)
+ return;
+
+ multiMonitorsAddOn.disable();
+ multiMonitorsAddOn = null;
+}
diff --git a/multi-monitors-add-on@spin83/icons/multi-monitors-l-symbolic.svg b/multi-monitors-add-on@spin83/icons/multi-monitors-l-symbolic.svg
new file mode 100644
index 0000000..6341c21
--- /dev/null
+++ b/multi-monitors-add-on@spin83/icons/multi-monitors-l-symbolic.svg
@@ -0,0 +1,392 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="multi-monitor-symbolic.svg"
+ height="16"
+ id="svg7384"
+ inkscape:version="0.48.5 r10040"
+ version="1.1"
+ width="16">
+ <metadata
+ id="metadata90">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Gnome Symbolic Icon Theme</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:bbox-paths="false"
+ bordercolor="#666666"
+ borderopacity="1"
+ inkscape:current-layer="layer11"
+ inkscape:cx="3.128503"
+ inkscape:cy="7.3630629"
+ gridtolerance="10"
+ inkscape:guide-bbox="true"
+ guidetolerance="10"
+ id="namedview88"
+ inkscape:object-nodes="false"
+ inkscape:object-paths="false"
+ objecttolerance="10"
+ pagecolor="#555753"
+ inkscape:pageopacity="1"
+ inkscape:pageshadow="2"
+ showborder="false"
+ showgrid="true"
+ showguides="true"
+ inkscape:snap-bbox="true"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:snap-global="true"
+ inkscape:snap-grids="true"
+ inkscape:snap-nodes="false"
+ inkscape:snap-others="false"
+ inkscape:snap-to-guides="true"
+ inkscape:window-height="1014"
+ inkscape:window-maximized="1"
+ inkscape:window-width="1920"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:zoom="32">
+ <inkscape:grid
+ empspacing="2"
+ enabled="true"
+ id="grid4866"
+ originx="-42.000009px"
+ originy="412px"
+ snapvisiblegridlinesonly="true"
+ spacingx="1px"
+ spacingy="1px"
+ type="xygrid"
+ visible="true" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="3.4692426,9.4354561"
+ id="guide4029" />
+ </sodipodi:namedview>
+ <title
+ id="title9167">Gnome Symbolic Icon Theme</title>
+ <defs
+ id="defs7386">
+ <linearGradient
+ id="linearGradient5351"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#00a600;stop-opacity:1;"
+ offset="0"
+ id="stop5353" />
+ </linearGradient>
+ <marker
+ inkscape:stockid="EmptyTriangleOutS"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="EmptyTriangleOutS"
+ style="overflow:visible">
+ <path
+ id="path4013"
+ d="m 5.77,0 -8.65,5 0,-10 8.65,5 z"
+ style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.2,0,0,0.2,-0.6,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Legs"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Legs"
+ style="overflow:visible">
+ <g
+ id="g4046"
+ transform="scale(-0.7,-0.7)">
+ <g
+ id="g4048"
+ transform="matrix(0,-1,-1,0,20.70862,21.31391)">
+ <path
+ id="path4050"
+ d="m 21.22125,20.67536 c -6.910151,4.721157 -2.454525,6.606844 -5.841071,13.443235"
+ style="fill:none;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path4052"
+ d="m 21.39811,20.54812 c -1.360509,8.347524 3.536072,8.76994 4.505041,13.824958"
+ style="fill:none;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ id="path4054"
+ d="m -14.09007,-6.7318716 -0.922168,4.043383 3.962751,-1.22307 -3.040583,-2.820313 z"
+ style="fill:#030300;fill-rule:evenodd;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path4056"
+ d="m -15.215679,4.5567534 1.874127,3.699613 2.266874,-3.472855 -4.141001,-0.226758 z"
+ style="fill:#030300;fill-rule:evenodd;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ </g>
+ </marker>
+ <marker
+ inkscape:stockid="Torso"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Torso"
+ style="overflow:visible">
+ <g
+ id="g4059"
+ transform="scale(0.7,0.7)">
+ <path
+ id="path4061"
+ d="m -4.7792281,-3.239542 c 2.350374,0.3659393 5.30026732,1.9375477 5.03715532,3.62748546 C -0.00518779,2.0778819 -2.2126741,2.6176539 -4.5630471,2.2517169 -6.9134221,1.8857769 -8.521035,0.75201414 -8.257922,-0.93792336 -7.994809,-2.6278615 -7.1296041,-3.6054813 -4.7792281,-3.239542 z"
+ style="fill:none;stroke:#000000;stroke-width:1.25"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path4063"
+ d="M 4.4598789,0.08866574 C -2.5564571,-4.378332 5.2248769,-3.9061806 -0.84829578,-8.7197331"
+ style="fill:none;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path4065"
+ d="M 4.9298719,0.05752074 C -1.3872731,1.7494689 1.8027579,5.4782079 -4.9448731,7.5462725"
+ style="fill:none;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ <rect
+ id="rect4067"
+ transform="matrix(0.527536,-0.849533,0.887668,0.460484,0,0)"
+ y="-1.7408575"
+ x="-10.391706"
+ height="2.7608147"
+ width="2.6366582"
+ style="fill-rule:evenodd;stroke-width:1pt" />
+ <rect
+ id="rect4069"
+ transform="matrix(0.671205,-0.741272,0.790802,0.612072,0,0)"
+ y="-7.9629307"
+ x="4.9587269"
+ height="2.8614161"
+ width="2.7327356"
+ style="fill-rule:evenodd;stroke-width:1pt" />
+ <path
+ id="path4071"
+ transform="matrix(0,-1.109517,1.109517,0,25.96648,19.71619)"
+ d="m 16.779951,-28.685045 a 0.60731727,0.60731727 0 1 0 -1.214634,0 0.60731727,0.60731727 0 1 0 1.214634,0 z"
+ style="fill:#ff0000;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path4073"
+ transform="matrix(0,-1.109517,1.109517,0,26.8245,16.99126)"
+ d="m 16.779951,-28.685045 a 0.60731727,0.60731727 0 1 0 -1.214634,0 0.60731727,0.60731727 0 1 0 1.214634,0 z"
+ style="fill:#ff0000;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ </g>
+ </marker>
+ <marker
+ inkscape:stockid="EmptyDiamondLstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="EmptyDiamondLstart"
+ style="overflow:visible">
+ <path
+ id="path3962"
+ d="M 0,-7.0710768 -7.0710894,0 0,7.0710589 7.0710462,0 0,-7.0710768 z"
+ style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.8,0,0,0.8,5.6,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="DiamondSend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="DiamondSend"
+ style="overflow:visible">
+ <path
+ id="path3950"
+ d="M 0,-7.0710768 -7.0710894,0 0,7.0710589 7.0710462,0 0,-7.0710768 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.2,0,0,0.2,-1.2,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible">
+ <path
+ id="path3856"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Tail"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Tail"
+ style="overflow:visible">
+ <g
+ id="g3883"
+ transform="scale(-1.2,-1.2)">
+ <path
+ id="path3885"
+ d="M -3.8048674,-3.9585227 0.54352094,0"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path3887"
+ d="M -1.2866832,-3.9585227 3.0617053,0"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path3889"
+ d="M 1.3053582,-3.9585227 5.6537466,0"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path3891"
+ d="M -3.8048674,4.1775838 0.54352094,0.21974226"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path3893"
+ d="M -1.2866832,4.1775838 3.0617053,0.21974226"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path3895"
+ d="M 1.3053582,4.1775838 5.6537466,0.21974226"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ </g>
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Sstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Sstart"
+ style="overflow:visible">
+ <path
+ id="path3877"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(0.3,0,0,0.3,-0.69,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="DotL"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="DotL"
+ style="overflow:visible">
+ <path
+ id="path3908"
+ d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.8,0,0,0.8,5.92,0.8)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ </defs>
+ <g
+ inkscape:groupmode="layer"
+ id="layer9"
+ inkscape:label="status"
+ style="display:inline"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer10"
+ inkscape:label="devices"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer11"
+ inkscape:label="apps"
+ style="display:inline"
+ transform="translate(-283.00021,-629)">
+ <g
+ id="g3040">
+ <path
+ id="path3795-3"
+ d="m 283.60374,629.58458 0,12.86095 14.78834,-1.16918 0,-10.5226 -14.78834,-1.16917 z m 0.73172,0.65766 13.28639,1.05957 0,9.46303 -13.32491,1.02303 0.0385,-11.54563 z"
+ style="fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:#bebebe;stroke-width:1.20035386;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3839-5"
+ d="m 290.99791,641.86094 -2e-5,2.33836"
+ style="fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:#bebebe;stroke-width:3.60105371;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3841-2"
+ d="m 295.34001,643.66019 -8.13359,0.64305"
+ style="fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:#bebebe;stroke-width:1.32038927;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none;display:inline" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="on"
+ style="display:inline" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="off"
+ style="display:inline" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer13"
+ inkscape:label="places"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer14"
+ inkscape:label="mimetypes"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer15"
+ inkscape:label="emblems"
+ style="display:inline"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="g71291"
+ inkscape:label="emotes"
+ style="display:inline"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="g4953"
+ inkscape:label="categories"
+ style="display:inline"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer12"
+ inkscape:label="actions"
+ style="display:inline"
+ transform="translate(-283.00021,-629)" />
+</svg>
diff --git a/multi-monitors-add-on@spin83/icons/multi-monitors-r-symbolic.svg b/multi-monitors-add-on@spin83/icons/multi-monitors-r-symbolic.svg
new file mode 100644
index 0000000..6bf4651
--- /dev/null
+++ b/multi-monitors-add-on@spin83/icons/multi-monitors-r-symbolic.svg
@@ -0,0 +1,393 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ sodipodi:docname="multi-monitor-l-symbolic.svg"
+ height="16"
+ id="svg7384"
+ inkscape:version="0.48.5 r10040"
+ version="1.1"
+ width="16">
+ <metadata
+ id="metadata90">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title>Gnome Symbolic Icon Theme</dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:bbox-paths="false"
+ bordercolor="#666666"
+ borderopacity="1"
+ inkscape:current-layer="layer11"
+ inkscape:cx="3.191003"
+ inkscape:cy="7.3005629"
+ gridtolerance="10"
+ inkscape:guide-bbox="true"
+ guidetolerance="10"
+ id="namedview88"
+ inkscape:object-nodes="false"
+ inkscape:object-paths="false"
+ objecttolerance="10"
+ pagecolor="#555753"
+ inkscape:pageopacity="1"
+ inkscape:pageshadow="2"
+ showborder="false"
+ showgrid="true"
+ showguides="true"
+ inkscape:snap-bbox="true"
+ inkscape:snap-bbox-midpoints="false"
+ inkscape:snap-global="true"
+ inkscape:snap-grids="true"
+ inkscape:snap-nodes="false"
+ inkscape:snap-others="false"
+ inkscape:snap-to-guides="true"
+ inkscape:window-height="958"
+ inkscape:window-maximized="1"
+ inkscape:window-width="1280"
+ inkscape:window-x="1920"
+ inkscape:window-y="27"
+ inkscape:zoom="32">
+ <inkscape:grid
+ empspacing="2"
+ enabled="true"
+ id="grid4866"
+ originx="-42.000009px"
+ originy="412px"
+ snapvisiblegridlinesonly="true"
+ spacingx="1px"
+ spacingy="1px"
+ type="xygrid"
+ visible="true" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="3.4692426,9.4354561"
+ id="guide4029" />
+ </sodipodi:namedview>
+ <title
+ id="title9167">Gnome Symbolic Icon Theme</title>
+ <defs
+ id="defs7386">
+ <linearGradient
+ id="linearGradient5351"
+ osb:paint="solid">
+ <stop
+ style="stop-color:#00a600;stop-opacity:1;"
+ offset="0"
+ id="stop5353" />
+ </linearGradient>
+ <marker
+ inkscape:stockid="EmptyTriangleOutS"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="EmptyTriangleOutS"
+ style="overflow:visible">
+ <path
+ id="path4013"
+ d="m 5.77,0 -8.65,5 0,-10 8.65,5 z"
+ style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.2,0,0,0.2,-0.6,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Legs"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Legs"
+ style="overflow:visible">
+ <g
+ id="g4046"
+ transform="scale(-0.7,-0.7)">
+ <g
+ id="g4048"
+ transform="matrix(0,-1,-1,0,20.70862,21.31391)">
+ <path
+ id="path4050"
+ d="m 21.22125,20.67536 c -6.910151,4.721157 -2.454525,6.606844 -5.841071,13.443235"
+ style="fill:none;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path4052"
+ d="m 21.39811,20.54812 c -1.360509,8.347524 3.536072,8.76994 4.505041,13.824958"
+ style="fill:none;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ </g>
+ <path
+ id="path4054"
+ d="m -14.09007,-6.7318716 -0.922168,4.043383 3.962751,-1.22307 -3.040583,-2.820313 z"
+ style="fill:#030300;fill-rule:evenodd;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path4056"
+ d="m -15.215679,4.5567534 1.874127,3.699613 2.266874,-3.472855 -4.141001,-0.226758 z"
+ style="fill:#030300;fill-rule:evenodd;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ </g>
+ </marker>
+ <marker
+ inkscape:stockid="Torso"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Torso"
+ style="overflow:visible">
+ <g
+ id="g4059"
+ transform="scale(0.7,0.7)">
+ <path
+ id="path4061"
+ d="m -4.7792281,-3.239542 c 2.350374,0.3659393 5.30026732,1.9375477 5.03715532,3.62748546 C -0.00518779,2.0778819 -2.2126741,2.6176539 -4.5630471,2.2517169 -6.9134221,1.8857769 -8.521035,0.75201414 -8.257922,-0.93792336 -7.994809,-2.6278615 -7.1296041,-3.6054813 -4.7792281,-3.239542 z"
+ style="fill:none;stroke:#000000;stroke-width:1.25"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path4063"
+ d="M 4.4598789,0.08866574 C -2.5564571,-4.378332 5.2248769,-3.9061806 -0.84829578,-8.7197331"
+ style="fill:none;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path4065"
+ d="M 4.9298719,0.05752074 C -1.3872731,1.7494689 1.8027579,5.4782079 -4.9448731,7.5462725"
+ style="fill:none;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ <rect
+ id="rect4067"
+ transform="matrix(0.527536,-0.849533,0.887668,0.460484,0,0)"
+ y="-1.7408575"
+ x="-10.391706"
+ height="2.7608147"
+ width="2.6366582"
+ style="fill-rule:evenodd;stroke-width:1pt" />
+ <rect
+ id="rect4069"
+ transform="matrix(0.671205,-0.741272,0.790802,0.612072,0,0)"
+ y="-7.9629307"
+ x="4.9587269"
+ height="2.8614161"
+ width="2.7327356"
+ style="fill-rule:evenodd;stroke-width:1pt" />
+ <path
+ id="path4071"
+ transform="matrix(0,-1.109517,1.109517,0,25.96648,19.71619)"
+ d="m 16.779951,-28.685045 a 0.60731727,0.60731727 0 1 0 -1.214634,0 0.60731727,0.60731727 0 1 0 1.214634,0 z"
+ style="fill:#ff0000;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path4073"
+ transform="matrix(0,-1.109517,1.109517,0,26.8245,16.99126)"
+ d="m 16.779951,-28.685045 a 0.60731727,0.60731727 0 1 0 -1.214634,0 0.60731727,0.60731727 0 1 0 1.214634,0 z"
+ style="fill:#ff0000;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ inkscape:connector-curvature="0" />
+ </g>
+ </marker>
+ <marker
+ inkscape:stockid="EmptyDiamondLstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="EmptyDiamondLstart"
+ style="overflow:visible">
+ <path
+ id="path3962"
+ d="M 0,-7.0710768 -7.0710894,0 0,7.0710589 7.0710462,0 0,-7.0710768 z"
+ style="fill:#ffffff;fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.8,0,0,0.8,5.6,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="DiamondSend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="DiamondSend"
+ style="overflow:visible">
+ <path
+ id="path3950"
+ d="M 0,-7.0710768 -7.0710894,0 0,7.0710589 7.0710462,0 0,-7.0710768 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.2,0,0,0.2,-1.2,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Arrow1Mend"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow1Mend"
+ style="overflow:visible">
+ <path
+ id="path3856"
+ d="M 0,0 5,-5 -12.5,0 5,5 0,0 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(-0.4,0,0,-0.4,-4,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="Tail"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Tail"
+ style="overflow:visible">
+ <g
+ id="g3883"
+ transform="scale(-1.2,-1.2)">
+ <path
+ id="path3885"
+ d="M -3.8048674,-3.9585227 0.54352094,0"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path3887"
+ d="M -1.2866832,-3.9585227 3.0617053,0"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path3889"
+ d="M 1.3053582,-3.9585227 5.6537466,0"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path3891"
+ d="M -3.8048674,4.1775838 0.54352094,0.21974226"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path3893"
+ d="M -1.2866832,4.1775838 3.0617053,0.21974226"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path3895"
+ d="M 1.3053582,4.1775838 5.6537466,0.21974226"
+ style="fill:none;stroke:#000000;stroke-width:0.80000001;stroke-linecap:round"
+ inkscape:connector-curvature="0" />
+ </g>
+ </marker>
+ <marker
+ inkscape:stockid="Arrow2Sstart"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="Arrow2Sstart"
+ style="overflow:visible">
+ <path
+ id="path3877"
+ style="fill-rule:evenodd;stroke-width:0.625;stroke-linejoin:round"
+ d="M 8.7185878,4.0337352 -2.2072895,0.01601326 8.7185884,-4.0017078 c -1.7454984,2.3720609 -1.7354408,5.6174519 -6e-7,8.035443 z"
+ transform="matrix(0.3,0,0,0.3,-0.69,0)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ <marker
+ inkscape:stockid="DotL"
+ orient="auto"
+ refY="0"
+ refX="0"
+ id="DotL"
+ style="overflow:visible">
+ <path
+ id="path3908"
+ d="m -2.5,-1 c 0,2.76 -2.24,5 -5,5 -2.76,0 -5,-2.24 -5,-5 0,-2.76 2.24,-5 5,-5 2.76,0 5,2.24 5,5 z"
+ style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt"
+ transform="matrix(0.8,0,0,0.8,5.92,0.8)"
+ inkscape:connector-curvature="0" />
+ </marker>
+ </defs>
+ <g
+ inkscape:groupmode="layer"
+ id="layer9"
+ inkscape:label="status"
+ style="display:inline"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer10"
+ inkscape:label="devices"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer11"
+ inkscape:label="apps"
+ style="display:inline"
+ transform="translate(-283.00021,-629)">
+ <g
+ id="g3040"
+ transform="matrix(-1,0,0,1,581.99582,0)">
+ <path
+ id="path3795-3"
+ d="m 283.60374,629.58458 0,12.86095 14.78834,-1.16918 0,-10.5226 -14.78834,-1.16917 z m 0.73172,0.65766 13.28639,1.05957 0,9.46303 -13.32491,1.02303 0.0385,-11.54563 z"
+ style="fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:#bebebe;stroke-width:1.20035386;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+ inkscape:connector-curvature="0" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3839-5"
+ d="m 290.99791,641.86094 -2e-5,2.33836"
+ style="fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:#bebebe;stroke-width:3.60105371;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
+ <path
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0"
+ id="path3841-2"
+ d="m 295.34001,643.66019 -8.13359,0.64305"
+ style="fill:#bebebe;fill-opacity:1;fill-rule:nonzero;stroke:#bebebe;stroke-width:1.32038927;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker-start:none;display:inline" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer1"
+ inkscape:label="on"
+ style="display:inline" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="off"
+ style="display:inline" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer13"
+ inkscape:label="places"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer14"
+ inkscape:label="mimetypes"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer15"
+ inkscape:label="emblems"
+ style="display:inline"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="g71291"
+ inkscape:label="emotes"
+ style="display:inline"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="g4953"
+ inkscape:label="categories"
+ style="display:inline"
+ transform="translate(-283.00021,-629)" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer12"
+ inkscape:label="actions"
+ style="display:inline"
+ transform="translate(-283.00021,-629)" />
+</svg>
diff --git a/multi-monitors-add-on@spin83/indicator.js b/multi-monitors-add-on@spin83/indicator.js
new file mode 100644
index 0000000..7dd3a33
--- /dev/null
+++ b/multi-monitors-add-on@spin83/indicator.js
@@ -0,0 +1,133 @@
+/*
+Copyright (C) 2014 spin83
+
+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, visit https://www.gnu.org/licenses/.
+*/
+
+const Lang = imports.lang;
+
+const St = imports.gi.St;
+
+const Util = imports.misc.util;
+
+const Main = imports.ui.main;
+const Tweener = imports.ui.tweener;
+const PanelMenu = imports.ui.panelMenu;
+
+const Gettext = imports.gettext.domain('multi-monitors-add-on');
+const _ = Gettext.gettext;
+const Convenience = imports.misc.extensionUtils.getCurrentExtension().imports.convenience;
+
+var MultiMonitorsIndicator = new Lang.Class({
+ Name: 'MultiMonitorsIndicator',
+ Extends: PanelMenu.Button,
+
+ _init() {
+ this.parent(0.0, "MultiMonitorsAddOn", false);
+
+ Convenience.initTranslations();
+
+ this.text = null;
+
+ this._mmStatusIcon = new St.BoxLayout({ style_class: 'multimonitor-status-indicators-box' });
+ this._mmStatusIcon.hide();
+ this.actor.add_child(this._mmStatusIcon);
+ this._leftRightIcon = true;
+
+ this.menu.addAction(_("Preferences"), this._onPreferences.bind(this));
+
+ this._viewMonitorsId = Main.layoutManager.connect('monitors-changed', this._viewMonitors.bind(this));
+ this.connect('destroy', this._onDestroy.bind(this));
+ this._viewMonitors();
+ },
+
+ _onDestroy(actor) {
+ Main.layoutManager.disconnect(this._viewMonitorsId);
+ },
+
+ _syncIndicatorsVisible() {
+ this._mmStatusIcon.visible = this._mmStatusIcon.get_children().some((actor) => {
+ return actor.visible;
+ });
+ },
+
+ _viewMonitors() {
+ let monitors = this._mmStatusIcon.get_children();
+
+ let monitorChange = Main.layoutManager.monitors.length - monitors.length;
+ if(monitorChange>0){
+ global.log("Add Monitors ...");
+ for(let idx = 0; idx<monitorChange; idx++){
+ let icon;
+ icon = new St.Icon({style_class: 'system-status-icon multimonitor-status-icon'});
+ this._mmStatusIcon.add_child(icon);
+ icon.connect('notify::visible', this._syncIndicatorsVisible.bind(this));
+
+ if(this._leftRightIcon)
+ icon.icon_name = 'multi-monitors-l-symbolic';
+ else
+ icon.icon_name = 'multi-monitors-r-symbolic';
+ this._leftRightIcon = !this._leftRightIcon;
+ }
+ this._syncIndicatorsVisible();
+ }
+ else if(monitorChange<0){
+ global.log("Remove Monitors ...");
+ monitorChange = -monitorChange;
+
+ for(let idx = 0; idx<monitorChange; idx++){
+ let icon = this._mmStatusIcon.get_last_child();
+ this._mmStatusIcon.remove_child(icon);
+ icon.destroy();
+ this._leftRightIcon = !this._leftRightIcon;
+ }
+ }
+ },
+
+ _onPreferences()
+ {
+ Util.spawn(["gnome-shell-extension-prefs", "multi-monitors-add-on@spin83"]);
+ },
+
+ _onInit2ndMonitor()
+ {
+ Util.spawn(["intel-virtual-output"]);
+ },
+
+ _hideHello() {
+ Main.uiGroup.remove_actor(this.text);
+ this.text = null;
+ },
+
+ _showHello() {
+ if (!this.text) {
+ this.text = new St.Label({ style_class: 'helloworld-label', text: _("Multi Monitors Add-On") });
+ Main.uiGroup.add_actor(this.text);
+ }
+
+ this.text.opacity = 255;
+
+ let monitor = Main.layoutManager.primaryMonitor;
+
+ this.text.set_position(Math.floor(monitor.width / 2 - this.text.width / 2),
+ Math.floor(monitor.height / 2 - this.text.height / 2));
+
+ Tweener.addTween(this.text,
+ { opacity: 0,
+ time: 4,
+ transition: 'easeOutQuad',
+ onComplete: this._hideHello.bind(this) });
+
+ },
+});
diff --git a/multi-monitors-add-on@spin83/locale/de/LC_MESSAGES/multi-monitors-add-on.mo b/multi-monitors-add-on@spin83/locale/de/LC_MESSAGES/multi-monitors-add-on.mo
new file mode 100644
index 0000000..1cb7bbb
--- /dev/null
+++ b/multi-monitors-add-on@spin83/locale/de/LC_MESSAGES/multi-monitors-add-on.mo
Binary files differ
diff --git a/multi-monitors-add-on@spin83/locale/de/LC_MESSAGES/multi-monitors-add-on.po b/multi-monitors-add-on@spin83/locale/de/LC_MESSAGES/multi-monitors-add-on.po
new file mode 100644
index 0000000..83ef378
--- /dev/null
+++ b/multi-monitors-add-on@spin83/locale/de/LC_MESSAGES/multi-monitors-add-on.po
@@ -0,0 +1,81 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Multi Monitors Add On Gnome Shell Extension\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-23 22:29+0100\n"
+"PO-Revision-Date: 2015-01-23 22:30+0100\n"
+"Last-Translator: Jonatan Zeidler <jonatan_zeidler@gmx.de>\n"
+"Language-Team: German <jonatan_zeidler@gmx.de>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.4\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+
+#: prefs.js:61
+msgid "Show Multi Monitors indicator on Top Panel."
+msgstr "Multimonitor-Indikator in der oberen Leiste anzeigen"
+
+#: prefs.js:62
+msgid "Show Panel on additional monitors."
+msgstr "Leiste auf zusätzlichen Monitoren anzeigen"
+
+#: prefs.js:63
+msgid "Show Thumbnails-Slider on additional monitors."
+msgstr "Arbeitsflächenübersicht auf zusätzlichen Monitoren anzeigen"
+
+#: prefs.js:64
+msgid "Show Activities-Button on additional monitors."
+msgstr "Aktivitäten-Schaltfläche auf zusätzlichen Monitoren anzeigen"
+
+#: prefs.js:65
+msgid "Show AppMenu-Button on additional monitors."
+msgstr "Anwendungsmenü auf zusätzlichen Monitoren anzeigen"
+
+#: prefs.js:66
+msgid "Show Thumbnails-Slider on left side of additional monitors."
+msgstr "Arbeitsflächenübersicht bei zusätzlichen Monitoren links anzeigen"
+
+#: prefs.js:67
+msgid "Show DateTime-Button on additional monitors."
+msgstr "Datum-Zeit auf zusätzlichen Monitoren anzeigen."
+
+#: prefs.js:75
+msgid "A list of indicators for transfer to additional monitors."
+msgstr ""
+"Eine Liste von Indikatoren, die auf die zusätzlichen Monitore verschoben "
+"werden sollen"
+
+#: prefs.js:122
+msgid "Select indicator"
+msgstr "Indikator auswählen"
+
+#: prefs.js:125
+msgid "Add"
+msgstr "Hinzufügen"
+
+#: prefs.js:139
+msgid "Indicators on Top Panel"
+msgstr "Indikatoren in der oberen Leiste"
+
+#: prefs.js:168
+msgid "Monitor index:"
+msgstr "Monitorindex:"
+
+#: indicator.js:106
+msgid "Preferences"
+msgstr "Einstellungen"
+
+#: indicator.js:107
+msgid "Test"
+msgstr "Test"
+
+#: indicator.js:129
+msgid "Multi Monitors Add-On"
+msgstr "Multimonitor-Erweiterung"
diff --git a/multi-monitors-add-on@spin83/locale/es/LC_MESSAGES/multi-monitors-add-on.mo b/multi-monitors-add-on@spin83/locale/es/LC_MESSAGES/multi-monitors-add-on.mo
new file mode 100644
index 0000000..8757210
--- /dev/null
+++ b/multi-monitors-add-on@spin83/locale/es/LC_MESSAGES/multi-monitors-add-on.mo
Binary files differ
diff --git a/multi-monitors-add-on@spin83/locale/es/LC_MESSAGES/multi-monitors-add-on.po b/multi-monitors-add-on@spin83/locale/es/LC_MESSAGES/multi-monitors-add-on.po
new file mode 100644
index 0000000..0e26035
--- /dev/null
+++ b/multi-monitors-add-on@spin83/locale/es/LC_MESSAGES/multi-monitors-add-on.po
@@ -0,0 +1,77 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# Alonso Lara <alonso.lara.plana@gmail.com>, 2017.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-23 22:29+0100\n"
+"PO-Revision-Date: 2017-03-04 23:59+0100\n"
+"Last-Translator: Alonso Lara <alonso.lara.plana@gmail.com>\n"
+"Language-Team: Spanish <LL@li.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: prefs.js:61
+msgid "Show Multi Monitors indicator on Top Panel."
+msgstr "Mostrar indicador de monitores múltiples en el panel."
+
+#: prefs.js:62
+msgid "Show Panel on additional monitors."
+msgstr "Mostrar el panel en monitores adicionales."
+
+#: prefs.js:63
+msgid "Show Thumbnails-Slider on additional monitors."
+msgstr "Mostrar las miniaturas en los monitores adicionales."
+
+#: prefs.js:64
+msgid "Show Activities-Button on additional monitors."
+msgstr "Mostrar las actividades en los monitores adicionales."
+
+#: prefs.js:65
+msgid "Show AppMenu-Button on additional monitors."
+msgstr "Mostrar el menú de aplicaciones en los monitores adicionales."
+
+#: prefs.js:66
+msgid "Show Thumbnails-Slider on left side of additional monitors."
+msgstr "Mostrar las miniaturas en el lado izquierdo de los monitores adicionales."
+
+#: prefs.js:67
+msgid "Show DateTime-Button on additional monitors."
+msgstr "Mostrar la fecha en los monitores adicionales."
+
+#: prefs.js:75
+msgid "A list of indicators for transfer to additional monitors."
+msgstr "Un listado de indicadores para transferir a monitores adicionales."
+
+#: prefs.js:122
+msgid "Select indicator"
+msgstr "Seleccione indicador"
+
+#: prefs.js:125
+msgid "Add"
+msgstr "Añadir"
+
+#: prefs.js:139
+msgid "Indicators on Top Panel"
+msgstr "Indicadores en el panel"
+
+#: prefs.js:168
+msgid "Monitor index:"
+msgstr "Monitor número:"
+
+#: indicator.js:106
+msgid "Preferences"
+msgstr "Preferencias"
+
+#: indicator.js:107
+msgid "Test"
+msgstr "Prueba"
+
+#: indicator.js:129
+msgid "Multi Monitors Add-On"
+msgstr "Accesorio de monitores múltiples"
diff --git a/multi-monitors-add-on@spin83/locale/fr/LC_MESSAGES/multi-monitors-add-on.mo b/multi-monitors-add-on@spin83/locale/fr/LC_MESSAGES/multi-monitors-add-on.mo
new file mode 100644
index 0000000..4c26418
--- /dev/null
+++ b/multi-monitors-add-on@spin83/locale/fr/LC_MESSAGES/multi-monitors-add-on.mo
Binary files differ
diff --git a/multi-monitors-add-on@spin83/locale/fr/LC_MESSAGES/multi-monitors-add-on.po b/multi-monitors-add-on@spin83/locale/fr/LC_MESSAGES/multi-monitors-add-on.po
new file mode 100644
index 0000000..9db73b4
--- /dev/null
+++ b/multi-monitors-add-on@spin83/locale/fr/LC_MESSAGES/multi-monitors-add-on.po
@@ -0,0 +1,81 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Multi Monitors Add On Gnome Shell Extension\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-12-26 22:29+0100\n"
+"PO-Revision-Date: 2015-12-26 22:30+0100\n"
+"Last-Translator: Quentin Daem\n"
+"Language-Team: "
+"Language: fr_FR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.4\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+
+#: prefs.js:61
+msgid "Show Multi Monitors indicator on Top Panel."
+msgstr "Afficher l'icone Multi Moniteurs sur la barre du haut"
+
+#: prefs.js:62
+msgid "Show Panel on additional monitors."
+msgstr "Afficher Menu sur les moniteurs secondaires"
+
+#: prefs.js:63
+msgid "Show Thumbnails-Slider on additional monitors."
+msgstr "Afficher le dock listant les espaces de travail sur les moniteurs secondaires"
+
+#: prefs.js:64
+msgid "Show Activities-Button on additional monitors."
+msgstr "Afficher le bouton Activités sur les moniteurs secondaires"
+
+#: prefs.js:65
+msgid "Show AppMenu-Button on additional monitors."
+msgstr "Afficher le bouton du menu Applications sur les moniteurs secondaires"
+
+#: prefs.js:66
+msgid "Show Thumbnails-Slider on left side of additional monitors."
+msgstr "Afficher le dock listant les espaces de travail, sur la gauche pour les moniteurs secondaires"
+
+#: prefs.js:67
+msgid "Show DateTime-Button on additional monitors."
+msgstr "Afficher le bouton Date-Heure sur les moniteurs secondaires."
+
+#: prefs.js:75
+msgid "A list of indicators for transfer to additional monitors."
+msgstr ""
+"Une liste d'indicateurs pour transfert vers les moniteurs secondaires "
+""
+
+#: prefs.js:122
+msgid "Select indicator"
+msgstr "Selectionner indicateur"
+
+#: prefs.js:125
+msgid "Add"
+msgstr "Ajouter"
+
+#: prefs.js:139
+msgid "Indicators on Top Panel"
+msgstr "Indicateur dans le panneau du haut"
+
+#: prefs.js:168
+msgid "Monitor index:"
+msgstr "Index moniteur:"
+
+#: indicator.js:106
+msgid "Preferences"
+msgstr "Préférences"
+
+#: indicator.js:107
+msgid "Test"
+msgstr "Test"
+
+#: indicator.js:129
+msgid "Multi Monitors Add-On"
+msgstr "Multi Moniteurs Add-On"
diff --git a/multi-monitors-add-on@spin83/locale/it/LC_MESSAGES/multi-monitors-add-on.mo b/multi-monitors-add-on@spin83/locale/it/LC_MESSAGES/multi-monitors-add-on.mo
new file mode 100644
index 0000000..f16d173
--- /dev/null
+++ b/multi-monitors-add-on@spin83/locale/it/LC_MESSAGES/multi-monitors-add-on.mo
Binary files differ
diff --git a/multi-monitors-add-on@spin83/locale/it/LC_MESSAGES/multi-monitors-add-on.po b/multi-monitors-add-on@spin83/locale/it/LC_MESSAGES/multi-monitors-add-on.po
new file mode 100644
index 0000000..a01f43a
--- /dev/null
+++ b/multi-monitors-add-on@spin83/locale/it/LC_MESSAGES/multi-monitors-add-on.po
@@ -0,0 +1,81 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Multi Monitors Add On Gnome Shell Extension\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2018-5-5 15:18+0100\n"
+"PO-Revision-Date: 2018-5-5 15:19+0100\n"
+"Last-Translator: Albano Battistella\n"
+"Language-Team: Italian"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.4\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+
+#: prefs.js:61
+msgid "Show Multi Monitors indicator on Top Panel."
+msgstr "Mostra l’icona Multi Monitor sul pannello in alto"
+
+#: prefs.js:62
+msgid "Show Panel on additional monitors."
+msgstr "Mostra Menu su monitor secondari"
+
+#: prefs.js:63
+msgid "Show Thumbnails-Slider on additional monitors."
+msgstr "Visualizza le aree di lavoro sui monitor secondari"
+
+#: prefs.js:64
+msgid "Show Activities-Button on additional monitors."
+msgstr "Mostra pulsante-attività su altri monitor."
+
+#: prefs.js:65
+msgid "Show AppMenu-Button on additional monitors."
+msgstr "Visualizza il pulsante del menu Applicazioni sui monitor secondari"
+
+#: prefs.js:66
+msgid "Show Thumbnails-Slider on left side of additional monitors."
+msgstr "Visualizza gli spazi di lavoro a sinistra per i monitor secondari"
+
+#: prefs.js:67
+msgid "Show DateTime-Button on additional monitors."
+msgstr "Mostra il pulsante Data-Ora sui monitor secondari."
+
+#: prefs.js:75
+msgid "A list of indicators for transfer to additional monitors."
+msgstr ""
+"Un elenco di indicatori per il trasferimento a monitor secondari "
+""
+
+#: prefs.js:122
+msgid "Select indicator"
+msgstr "Seleziona indicatore"
+
+#: prefs.js:125
+msgid "Add"
+msgstr "Aggiungere"
+
+#: prefs.js:139
+msgid "Indicators on Top Panel"
+msgstr "Indicatore nel pannello superiore"
+
+#: prefs.js:168
+msgid "Monitor index:"
+msgstr "Index monitor:"
+
+#: indicator.js:106
+msgid "Preferences"
+msgstr "Preferenze"
+
+#: indicator.js:107
+msgid "Test"
+msgstr "Test"
+
+#: indicator.js:129
+msgid "Multi Monitors Add-On"
+msgstr "Componente aggiuntivo per più monitor"
diff --git a/multi-monitors-add-on@spin83/locale/pl/LC_MESSAGES/multi-monitors-add-on.mo b/multi-monitors-add-on@spin83/locale/pl/LC_MESSAGES/multi-monitors-add-on.mo
new file mode 100644
index 0000000..41e61b3
--- /dev/null
+++ b/multi-monitors-add-on@spin83/locale/pl/LC_MESSAGES/multi-monitors-add-on.mo
Binary files differ
diff --git a/multi-monitors-add-on@spin83/locale/pl/LC_MESSAGES/multi-monitors-add-on.po b/multi-monitors-add-on@spin83/locale/pl/LC_MESSAGES/multi-monitors-add-on.po
new file mode 100644
index 0000000..70b6c9d
--- /dev/null
+++ b/multi-monitors-add-on@spin83/locale/pl/LC_MESSAGES/multi-monitors-add-on.po
@@ -0,0 +1,79 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2016-12-29 14:25+0100\n"
+"PO-Revision-Date: 2016-12-29 14:25+0100\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.8.11\n"
+"Last-Translator: \n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"Language: pl_PL\n"
+
+#: prefs.js:61
+msgid "Show Multi Monitors indicator on Top Panel."
+msgstr "Wyświetl wskaźnik rozszerzenia na głównym pasku."
+
+#: prefs.js:62
+msgid "Show Panel on additional monitors."
+msgstr "Wyświetl główny pasek na dodatkowych monitorach."
+
+#: prefs.js:63
+msgid "Show Thumbnails-Slider on additional monitors."
+msgstr "Wyświetl pasek miniatur na dodatkowych monitorach."
+
+#: prefs.js:64
+msgid "Show Activities-Button on additional monitors."
+msgstr "Wyświetl przycisk podglądu na dodatkowych monitorach."
+
+#: prefs.js:65
+msgid "Show AppMenu-Button on additional monitors."
+msgstr "Wyświetl przycisk aplikacji na dodatkowych monitorach."
+
+#: prefs.js:66
+msgid "Show Thumbnails-Slider on left side of additional monitors."
+msgstr "Wyświetl pasek miniatur po lewej stronie dla dodatkowych monitorów."
+
+#: prefs.js:67
+msgid "Show DateTime-Button on additional monitors."
+msgstr "Wyświetl przycisk daty i czasu na dodatkowych monitorach."
+
+#: prefs.js:75
+msgid "A list of indicators for transfer to additional monitors."
+msgstr "Lista wskaźników do przesunięcia na dodatkowe monitory."
+
+#: prefs.js:122
+msgid "Select indicator"
+msgstr "Wybierz wskaźnik"
+
+#: prefs.js:125
+msgid "Add"
+msgstr "Dodaj"
+
+#: prefs.js:139
+msgid "Indicators on Top Panel"
+msgstr "Wskaźniki na głównym panelu"
+
+#: prefs.js:168
+msgid "Monitor index:"
+msgstr "Indeks monitora:"
+
+#: indicator.js:106
+msgid "Preferences"
+msgstr "Ustawienia"
+
+#: indicator.js:107
+msgid "Test"
+msgstr "Test"
+
+#: indicator.js:129
+msgid "Multi Monitors Add-On"
+msgstr "Multi Monitors Add-On"
diff --git a/multi-monitors-add-on@spin83/metadata.json b/multi-monitors-add-on@spin83/metadata.json
new file mode 100644
index 0000000..39b2da4
--- /dev/null
+++ b/multi-monitors-add-on@spin83/metadata.json
@@ -0,0 +1,10 @@
+{
+ "shell-version": ["3.24", "3.26", "3.28", "3.30"],
+ "uuid": "multi-monitors-add-on@spin83",
+ "name": "Multi Monitors Add-On",
+ "settings-schema": "org.gnome.shell.extensions.multi-monitors-add-on",
+ "gettext-domain": "multi-monitors-add-on",
+ "description": "Add multiple monitors overview and panel for gnome-shell.",
+ "url": "https://github.com/spin83/multi-monitors-add-on.git",
+ "version": 16
+}
diff --git a/multi-monitors-add-on@spin83/mmcalendar.js b/multi-monitors-add-on@spin83/mmcalendar.js
new file mode 100644
index 0000000..0b96887
--- /dev/null
+++ b/multi-monitors-add-on@spin83/mmcalendar.js
@@ -0,0 +1,381 @@
+/*
+Copyright (C) 2014 spin83
+
+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, visit https://www.gnu.org/licenses/.
+*/
+
+const Lang = imports.lang;
+
+const St = imports.gi.St;
+const Gio = imports.gi.Gio;
+const Gtk = imports.gi.Gtk;
+const Shell = imports.gi.Shell;
+const Clutter = imports.gi.Clutter;
+const GnomeDesktop = imports.gi.GnomeDesktop;
+const GObject = imports.gi.GObject;
+
+const Config = imports.misc.config;
+
+const Main = imports.ui.main;
+const PanelMenu = imports.ui.panelMenu;
+const MessageList = imports.ui.messageList;
+const DateMenu = imports.ui.dateMenu;
+const Calendar = imports.ui.calendar;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const MultiMonitors = ExtensionUtils.getCurrentExtension();
+const Convenience = MultiMonitors.imports.convenience;
+
+const Gettext_gtk30 = imports.gettext.domain('gtk30');
+const gtk30_ = Gettext_gtk30.gettext;
+
+const MultiMonitorsCalendar = new Lang.Class({
+ Name: 'MultiMonitorsCalendar',
+ Extends: Calendar.Calendar,
+
+ _init() {
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+ this._weekStart = Shell.util_get_week_start();
+ this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.calendar' });
+
+ this._showWeekdateKeyId = this._settings.connect('changed::' + Calendar.SHOW_WEEKDATE_KEY, this._onSettingsChange.bind(this));
+ this._useWeekdate = this._settings.get_boolean(Calendar.SHOW_WEEKDATE_KEY);
+
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>26) {
+ this._headerFormatWithoutYear = _('%OB');
+ this._headerFormat = _('%OB %Y');
+ }
+ else {
+ // Find the ordering for month/year in the calendar heading
+ this._headerFormatWithoutYear = '%B';
+ switch (gtk30_('calendar:MY')) {
+ case 'calendar:MY':
+ this._headerFormat = '%B %Y';
+ break;
+ case 'calendar:YM':
+ this._headerFormat = '%Y %B';
+ break;
+ default:
+ log('Translation of "calendar:MY" in GTK+ is not correct');
+ this._headerFormat = '%B %Y';
+ break;
+ }
+ }
+
+ // Start off with the current date
+ this._selectedDate = new Date();
+
+ this._shouldDateGrabFocus = false;
+
+ this.actor = new St.Widget({ style_class: 'calendar',
+ layout_manager: new Clutter.TableLayout(),
+ reactive: true });
+
+ this.actor.connect('scroll-event', this._onScroll.bind(this));
+
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+
+ this._buildHeader ();
+ },
+
+ _onDestroy(actor) {
+ this._settings.disconnect(this._showWeekdateKeyId);
+ }
+});
+
+const MultiMonitorsEventsSection = new Lang.Class({
+ Name: 'MultiMonitorsEventsSection',
+ Extends: MessageList.MessageListSection,
+
+ _init() {
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+ this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
+ this._reloadEventsId = this._desktopSettings.connect('changed', this._reloadEvents.bind(this));
+ this._eventSource = new Calendar.EmptyEventSource();
+
+ this._messageById = new Map();
+
+ this.parent();
+
+ this._title = new St.Button({ style_class: 'events-section-title',
+ label: '',
+ x_align: St.Align.START,
+ can_focus: true });
+ this.actor.insert_child_below(this._title, null);
+
+ this._title.connect('clicked', this._onTitleClicked.bind(this));
+ this._title.connect('key-focus-in', this._onKeyFocusIn.bind(this));
+
+ this._defaultAppSystem = Shell.AppSystem.get_default();
+ this._appInstalledChangedId = this._defaultAppSystem.connect('installed-changed',
+ this._appInstalledChanged.bind(this));
+
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+ this._appInstalledChanged();
+ },
+
+ _onDestroy(actor) {
+ this._desktopSettings.disconnect(this._reloadEventsId);
+ this._defaultAppSystem.disconnect(this._appInstalledChangedId);
+ },
+
+ _ignoreEvent: Calendar.EventsSection.prototype._ignoreEvent,
+ setEventSource: Calendar.EventsSection.prototype.setEventSource,
+
+ get allowed() {
+ return Main.sessionMode.showCalendarEvents;
+ },
+
+ _updateTitle: Calendar.EventsSection.prototype._updateTitle,
+ _reloadEvents: Calendar.EventsSection.prototype._reloadEvents,
+ _appInstalledChanged: Calendar.EventsSection.prototype._appInstalledChanged,
+ _getCalendarApp: Calendar.EventsSection.prototype._getCalendarApp,
+ _onTitleClicked: Calendar.EventsSection.prototype._onTitleClicked,
+ setDate: Calendar.EventsSection.prototype.setDate,
+ _shouldShow: Calendar.EventsSection.prototype._shouldShow,
+ _sync: Calendar.EventsSection.prototype._sync
+});
+
+const MultiMonitorsNotificationSection = new Lang.Class({
+ Name: 'MultiMonitorsNotificationSection',
+ Extends: MessageList.MessageListSection,
+
+ _init() {
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+
+ this.parent();
+ this._sources = new Map();
+ this._nUrgent = 0;
+
+ this._sourceAddedId = Main.messageTray.connect('source-added', this._sourceAdded.bind(this));
+ Main.messageTray.getSources().forEach((source) => {
+ this._sourceAdded(Main.messageTray, source);
+ });
+
+ this.actor.connect('notify::mapped', this._onMapped.bind(this));
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+ },
+
+ _onDestroy(actor) {
+ Main.messageTray.disconnect(this._sourceAddedId);
+ let source, obj;
+ for ([source, obj] of this._sources.entries()) {
+ this._onSourceDestroy(source, obj);
+ }
+ },
+
+ get allowed() {
+ return Main.sessionMode.hasNotifications &&
+ !Main.sessionMode.isGreeter;
+ },
+
+ _createTimeLabel: Calendar.NotificationSection.prototype._createTimeLabel,
+ _sourceAdded: Calendar.NotificationSection.prototype._sourceAdded,
+ _onNotificationAdded: Calendar.NotificationSection.prototype._onNotificationAdded,
+ _onSourceDestroy: Calendar.NotificationSection.prototype._onSourceDestroy,
+ _onMapped: Calendar.NotificationSection.prototype._onMapped
+});
+
+const MultiMonitorsCalendarMessageList = new Lang.Class({
+ Name: 'MultiMonitorsCalendarMessageList',
+ Extends: Calendar.CalendarMessageList,
+
+ _init() {
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+
+ this.actor = new St.Widget({ style_class: 'message-list',
+ layout_manager: new Clutter.BinLayout(),
+ x_expand: true, y_expand: true });
+
+ this._placeholder = new Calendar.Placeholder();
+ this.actor.add_actor(this._placeholder.actor);
+
+ this._scrollView = new St.ScrollView({ style_class: 'vfade',
+ overlay_scrollbars: true,
+ x_expand: true, y_expand: true,
+ x_fill: true, y_fill: true });
+ this._scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
+
+ let box = new St.BoxLayout({ vertical: true,
+ x_expand: true, y_expand: true });
+ this.actor.add_actor(box);
+
+ box.add_actor(this._scrollView);
+
+ this._clearButton = new St.Button({ style_class: 'message-list-clear-button button',
+ label: _("Clear All"),
+ can_focus: true });
+ this._clearButton.set_x_align(Clutter.ActorAlign.END);
+ this._clearButton.connect('clicked', () => {
+ let sections = [...this._sections.keys()];
+ sections.forEach((s) => { s.clear(); });
+ });
+ box.add_actor(this._clearButton);
+ this._sectionList = new St.BoxLayout({ style_class: 'message-list-sections',
+ vertical: true,
+ y_expand: true,
+ y_align: Clutter.ActorAlign.START });
+ this._scrollView.add_actor(this._sectionList);
+ this._sections = new Map();
+
+// this._mediaSection = new Mpris.MediaSection();
+// this._addSection(this._mediaSection);
+
+ this._notificationSection = new MultiMonitorsNotificationSection();
+ this._addSection(this._notificationSection);
+ this._eventsSection = new MultiMonitorsEventsSection();
+ this._addSection(this._eventsSection);
+
+ this._sessionModeUpdatedId = Main.sessionMode.connect('updated', Lang.bind(this, this._sync));
+
+ this._destroy = false;
+
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+ },
+
+ _onDestroy(actor) {
+ this._destroy = true;
+ Main.sessionMode.disconnect(this._sessionModeUpdatedId);
+ },
+
+ _sync() {
+ if (this._destroy) return;
+ this.parent();
+ }
+});
+
+var MultiMonitorsDateMenuButton = new Lang.Class({
+ Name: 'MultiMonitorsDateMenuButton',
+ Extends: PanelMenu.Button,
+
+ _init() {
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+ let hbox;
+ let vbox;
+
+ let menuAlignment = 0.5;
+ if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
+ menuAlignment = 1.0 - menuAlignment;
+ this.parent(menuAlignment);
+
+ this._clockDisplay = new St.Label({ y_align: Clutter.ActorAlign.CENTER });
+// this._indicator = new DateMenu.MessagesIndicator()
+
+ let box = new St.BoxLayout();
+// box.add_actor(new DateMenu.IndicatorPad(this._indicator.actor));
+ box.add_actor(this._clockDisplay);
+// box.add_actor(this._indicator.actor);
+
+ this.actor.label_actor = this._clockDisplay;
+ this.actor.add_actor(box);
+ this.actor.add_style_class_name ('clock-display');
+
+ let layout = new DateMenu.FreezableBinLayout();
+ let bin = new St.Widget({ layout_manager: layout });
+
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ // For some minimal compatibility with PopupMenuItem
+ bin._delegate = this;
+ }
+
+ this.menu.box.add_child(bin);
+
+ hbox = new St.BoxLayout({ name: 'calendarArea' });
+ bin.add_actor(hbox);
+ this._calendar = new MultiMonitorsCalendar();
+ this._calendar.connect('selected-date-changed',
+ (calendar, date) => {
+ layout.frozen = !DateMenu._isToday(date);
+ this._messageList.setDate(date);
+ });
+
+ this.menu.connect('open-state-changed', (menu, isOpen) => {
+ // Whenever the menu is opened, select today
+ if (isOpen) {
+ let now = new Date();
+ this._calendar.setDate(now);
+ this._date.setDate(now);
+ this._messageList.setDate(now);
+ }
+ });
+
+ // Fill up the first column
+ this._messageList = new MultiMonitorsCalendarMessageList();
+ hbox.add(this._messageList.actor, { expand: true, y_fill: false, y_align: St.Align.START });
+
+ // Fill up the second column
+ let boxLayout = new DateMenu.CalendarColumnLayout(this._calendar.actor);
+ vbox = new St.Widget({ style_class: 'datemenu-calendar-column',
+ layout_manager: boxLayout });
+ boxLayout.hookup_style(vbox);
+ hbox.add(vbox);
+
+ this._date = new DateMenu.TodayButton(this._calendar);
+ vbox.add_actor(this._date.actor);
+
+ vbox.add_actor(this._calendar.actor);
+
+ this._clock = new GnomeDesktop.WallClock();
+ this._clock.bind_property('clock', this._clockDisplay, 'text', GObject.BindingFlags.SYNC_CREATE);
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ this._clockNotifyTimezoneId = this._clock.connect('notify::timezone', this._updateTimeZone.bind(this));
+ }
+
+ this._sessionModeUpdatedId = Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
+
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+
+ this._sessionUpdated();
+ },
+
+ _onDestroy(actor) {
+ Main.sessionMode.disconnect(this._sessionModeUpdatedId);
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ this._clock.disconnect(this._clockNotifyTimezoneId);
+ }
+ },
+
+ _getEventSource() {
+ return new Calendar.DBusEventSource();
+ },
+
+ _setEventSource(eventSource) {
+ if (this._eventSource)
+ this._eventSource.destroy();
+
+ this._calendar.setEventSource(eventSource);
+ this._messageList.setEventSource(eventSource);
+
+ this._eventSource = eventSource;
+ },
+
+ _updateTimeZone: DateMenu.DateMenuButton.prototype._updateTimeZone,
+
+ _sessionUpdated() {
+ let eventSource;
+ let showEvents = Main.sessionMode.showCalendarEvents;
+ if (showEvents) {
+ eventSource = this._getEventSource();
+ } else {
+ eventSource = new Calendar.EmptyEventSource();
+ }
+ this._setEventSource(eventSource);
+
+ // Displays are not actually expected to launch Settings when activated
+ // but the corresponding app (clocks, weather); however we can consider
+ // that display-specific settings, so re-use "allowSettings" here ...
+// this._displaysSection.visible = Main.sessionMode.allowSettings;
+ }
+
+});
diff --git a/multi-monitors-add-on@spin83/mmlayout.js b/multi-monitors-add-on@spin83/mmlayout.js
new file mode 100644
index 0000000..858a65f
--- /dev/null
+++ b/multi-monitors-add-on@spin83/mmlayout.js
@@ -0,0 +1,278 @@
+/**
+ * New node file
+ */
+
+const Lang = imports.lang;
+
+const St = imports.gi.St;
+const Meta = imports.gi.Meta;
+
+const Main = imports.ui.main;
+const Panel = imports.ui.panel;
+const Layout = imports.ui.layout;
+
+const Config = imports.misc.config;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const MultiMonitors = ExtensionUtils.getCurrentExtension();
+const Convenience = MultiMonitors.imports.convenience;
+
+const MMPanel = MultiMonitors.imports.mmpanel;
+
+var SHOW_PANEL_ID = 'show-panel';
+
+const MultiMonitorsPanelBox = new Lang.Class({
+ Name: 'MultiMonitorsPanelBox',
+ _init (monitor) {
+ this._rightPanelBarrier = null;
+
+ this.panelBox = new St.BoxLayout({ name: 'panelBox', vertical: true });
+ Main.layoutManager.addChrome(this.panelBox, { affectsStruts: true, trackFullscreen: true });
+ this.panelBox.set_position(monitor.x, monitor.y);
+ this.panelBox.set_size(monitor.width, -1);
+ Main.uiGroup.set_child_below_sibling(this.panelBox, Main.layoutManager.panelBox);
+
+ this._panelBoxChangedId = this.panelBox.connect('allocation-changed', this._panelBoxChanged.bind(this));
+ },
+
+ destroy () {
+ if (this._rightPanelBarrier) {
+ this._rightPanelBarrier.destroy();
+ this._rightPanelBarrier = null;
+ }
+
+ this.panelBox.disconnect(this._panelBoxChangedId);
+ this.panelBox.destroy();
+ },
+
+ updatePanel(monitor) {
+ this.panelBox.set_position(monitor.x, monitor.y);
+ this.panelBox.set_size(monitor.width, -1);
+ },
+
+ _panelBoxChanged(self, box, flags) {
+// global.log(box.get_x()+" "+box.get_y()+" "+box.get_height()+" "+box.get_width())
+
+ if (this._rightPanelBarrier) {
+ this._rightPanelBarrier.destroy();
+ this._rightPanelBarrier = null;
+ }
+
+ if (this.panelBox.height) {
+ this._rightPanelBarrier = new Meta.Barrier({ display: global.display,
+ x1: box.get_x() + box.get_width(), y1: box.get_y(),
+ x2: box.get_x() + box.get_width(), y2: box.get_y() + this.panelBox.height,
+ directions: Meta.BarrierDirection.NEGATIVE_X });
+ }
+ },
+});
+
+var MultiMonitorsLayoutManager = new Lang.Class({
+ Name: 'MultiMonitorsLayoutManager',
+ _init () {
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+
+ this._settings = Convenience.getSettings();
+
+ Main.mmPanel = [];
+
+ this._monitorIds = [];
+ this.mmPanelBox = [];
+ this.mmappMenu = false;
+
+ this._showAppMenuId = null;
+ this._monitorsChangedId = null;
+
+ this.statusIndicatorsController = null;
+ this._layoutManager_updateHotCorners = null;
+ this._changedEnableHotCornersId = null;
+ },
+
+ showPanel() {
+ if (this._settings.get_boolean(SHOW_PANEL_ID)) {
+ if (!this._monitorsChangedId) {
+ this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', this._monitorsChanged.bind(this));
+ this._monitorsChanged();
+ }
+ if (!this._showAppMenuId) {
+ this._showAppMenuId = this._settings.connect('changed::'+MMPanel.SHOW_APP_MENU_ID, this._showAppMenu.bind(this));
+ }
+
+ if (!this.statusIndicatorsController) {
+ this.statusIndicatorsController = new MMPanel.StatusIndicatorsController();
+ }
+
+ if (!this._layoutManager_updateHotCorners) {
+ this._layoutManager_updateHotCorners = Main.layoutManager._updateHotCorners;
+
+ let enable_hot_corners = (Main.sessionMode.currentMode == 'ubuntu' && this._currentVersion[0]==3 && this._currentVersion[1]==28);
+ Main.layoutManager._updateHotCorners = function() {
+ this.hotCorners.forEach((corner) => {
+ if (corner)
+ corner.destroy();
+ });
+ this.hotCorners = [];
+
+ if (enable_hot_corners) {
+ if (!global.settings.get_boolean('enable-hot-corners')) {
+ this.emit('hot-corners-changed');
+ return;
+ }
+ }
+
+ let size = this.panelBox.height;
+
+ for (let i = 0; i < this.monitors.length; i++) {
+ let monitor = this.monitors[i];
+ let cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
+ let cornerY = monitor.y;
+
+ let corner = new Layout.HotCorner(this, monitor, cornerX, cornerY);
+ corner.setBarrierSize(size);
+ this.hotCorners.push(corner);
+ }
+
+ this.emit('hot-corners-changed');
+ };
+
+ if (!this._changedEnableHotCornersId) {
+ if (enable_hot_corners) {
+ this._changedEnableHotCornersId = global.settings.connect('changed::enable-hot-corners',
+ Main.layoutManager._updateHotCorners.bind(Main.layoutManager));
+ }
+ }
+
+ Main.layoutManager._updateHotCorners();
+ }
+ }
+ else {
+ this.hidePanel();
+ }
+ },
+
+ hidePanel() {
+ if (this._changedEnableHotCornersId) {
+ global.settings.disconnect(this._changedEnableHotCornersId);
+ this._changedEnableHotCornersId = null;
+ }
+
+ if (this._layoutManager_updateHotCorners) {
+ Main.layoutManager['_updateHotCorners'] = this._layoutManager_updateHotCorners;
+ this._layoutManager_updateHotCorners = null;
+ Main.layoutManager._updateHotCorners();
+ }
+
+ if (this.statusIndicatorsController) {
+ this.statusIndicatorsController.destroy();
+ this.statusIndicatorsController = null;
+ }
+
+ if (this._showAppMenuId) {
+ this._settings.disconnect(this._showAppMenuId);
+ this._showAppMenuId = null;
+ }
+ this._hideAppMenu();
+
+ if (this._monitorsChangedId) {
+ Main.layoutManager.disconnect(this._monitorsChangedId);
+ this._monitorsChangedId = null;
+ }
+
+ let panels2remove = this._monitorIds.length;
+ for (let i = 0; i < panels2remove; i++) {
+ let monitorId = this._monitorIds.pop();
+ this._popPanel();
+ global.log("remove: "+monitorId);
+ }
+ },
+
+ _monitorsChanged () {
+ let monitorChange = Main.layoutManager.monitors.length - this._monitorIds.length -1;
+ if (monitorChange<0) {
+ for (let idx = 0; idx<-monitorChange; idx++) {
+ let monitorId = this._monitorIds.pop();
+ this._popPanel();
+ global.log("remove: "+monitorId);
+ }
+ }
+
+ let j = 0;
+ let tIndicators = false;
+ for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
+ if (i!=Main.layoutManager.primaryIndex) {
+ let monitor = Main.layoutManager.monitors[i];
+ let monitorId = "i"+i+"x"+monitor.x+"y"+monitor.y+"w"+monitor.width+"h"+monitor.height;
+ if (monitorChange>0 && j==this._monitorIds.length) {
+ this._monitorIds.push(monitorId);
+ this._pushPanel(i, monitor);
+ global.log("new: "+monitorId);
+ tIndicators = true;
+ }
+ else if (this._monitorIds[j]>monitorId || this._monitorIds[j]<monitorId) {
+ let oldMonitorId = this._monitorIds[j];
+ this._monitorIds[j]=monitorId;
+ this.mmPanelBox[j].updatePanel(monitor);
+ global.log("update: "+oldMonitorId+">"+monitorId);
+ }
+ j++;
+ }
+ }
+ this._showAppMenu();
+ if (tIndicators && this.statusIndicatorsController) {
+ this.statusIndicatorsController.transferIndicators();
+ }
+ },
+
+ _pushPanel(i, monitor) {
+ let mmPanelBox = new MultiMonitorsPanelBox(monitor);
+ let panel = new MMPanel.MultiMonitorsPanel(i, mmPanelBox);
+
+ Main.mmPanel.push(panel);
+ this.mmPanelBox.push(mmPanelBox);
+ },
+
+ _popPanel() {
+ let panel = Main.mmPanel.pop();
+ if (this.statusIndicatorsController) {
+ this.statusIndicatorsController.transferBack(panel);
+ }
+ let mmPanelBox = this.mmPanelBox.pop();
+ mmPanelBox.destroy();
+ },
+
+ _changeMainPanelAppMenuButton(appMenuButton) {
+ let role = "appMenu";
+ let panel = Main.panel;
+ let indicator = panel.statusArea[role];
+ panel.menuManager.removeMenu(indicator.menu);
+ indicator.destroy();
+ if (indicator._actionGroupNotifyId) {
+ indicator._targetApp.disconnect(indicator._actionGroupNotifyId);
+ indicator._actionGroupNotifyId = 0;
+ }
+ indicator = new appMenuButton(panel);
+ panel.statusArea[role] = indicator;
+ let box = panel._leftBox;
+ panel._addToPanelBox(role, indicator, box.get_n_children()+1, box);
+ },
+
+ _showAppMenu() {
+ if (this._settings.get_boolean(MMPanel.SHOW_APP_MENU_ID) && Main.mmPanel.length>0) {
+ if (!this.mmappMenu) {
+ this._changeMainPanelAppMenuButton(MMPanel.MultiMonitorsAppMenuButton);
+ this.mmappMenu = true;
+ }
+ }
+ else {
+ this._hideAppMenu();
+ }
+ },
+
+ _hideAppMenu() {
+ if (this.mmappMenu) {
+ this._changeMainPanelAppMenuButton(Panel.AppMenuButton);
+ this.mmappMenu = false;
+ }
+ },
+});
+
diff --git a/multi-monitors-add-on@spin83/mmoverview.js b/multi-monitors-add-on@spin83/mmoverview.js
new file mode 100644
index 0000000..9b57bf2
--- /dev/null
+++ b/multi-monitors-add-on@spin83/mmoverview.js
@@ -0,0 +1,826 @@
+/*
+Copyright (C) 2014 spin83
+
+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, visit https://www.gnu.org/licenses/.
+*/
+
+const Lang = imports.lang;
+
+const Clutter = imports.gi.Clutter;
+const GObject = imports.gi.GObject;
+const St = imports.gi.St;
+const Shell = imports.gi.Shell;
+const Gio = imports.gi.Gio;
+const Meta = imports.gi.Meta;
+
+const Main = imports.ui.main;
+const Tweener = imports.ui.tweener;
+const Params = imports.misc.params;
+const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
+const OverviewControls = imports.ui.overviewControls;
+const Overview = imports.ui.overview;
+const ViewSelector = imports.ui.viewSelector;
+const LayoutManager = imports.ui.layout;
+const Background = imports.ui.background;
+const WorkspacesView = imports.ui.workspacesView;
+
+const Config = imports.misc.config;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const MultiMonitors = ExtensionUtils.getCurrentExtension();
+const Convenience = MultiMonitors.imports.convenience;
+
+const THUMBNAILS_ON_LEFT_SIDE_ID = 'thumbnails-on-left-side';
+
+const MultiMonitorsWorkspaceThumbnail = new Lang.Class({
+ Name: 'MultiMonitorsWorkspaceThumbnail',
+ Extends: WorkspaceThumbnail.WorkspaceThumbnail,
+
+ _init (metaWorkspace, monitorIndex) {
+ this.metaWorkspace = metaWorkspace;
+ this.monitorIndex = monitorIndex;
+
+ this._removed = false;
+
+ this.actor = new St.Widget({ clip_to_allocation: true,
+ style_class: 'workspace-thumbnail' });
+ this.actor._delegate = this;
+
+ this._contents = new Clutter.Actor();
+ this.actor.add_child(this._contents);
+
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+
+// this._createBackground();
+ this._bgManager = new Background.BackgroundManager({ monitorIndex: this.monitorIndex,
+ container: this._contents,
+ vignette: false });
+
+ let workArea = Main.layoutManager.getWorkAreaForMonitor(this.monitorIndex);
+ this.setPorthole(workArea.x, workArea.y, workArea.width, workArea.height);
+
+ let windows = global.get_window_actors().filter((actor) => {
+ let win = actor.meta_window;
+ return win.located_on_workspace(metaWorkspace);
+ });
+
+ // Create clones for windows that should be visible in the Overview
+ this._windows = [];
+ //--- fix Ubuntu changes from js-fix-invalid-access-errors.patch
+ this._windowsDestroyedIds = [];
+ //---
+ this._allWindows = [];
+ this._minimizedChangedIds = [];
+ for (let i = 0; i < windows.length; i++) {
+ let minimizedChangedId =
+ windows[i].meta_window.connect('notify::minimized', this._updateMinimized.bind(this));
+ this._allWindows.push(windows[i].meta_window);
+ this._minimizedChangedIds.push(minimizedChangedId);
+
+ if (this._isMyWindow(windows[i]) && this._isOverviewWindow(windows[i])) {
+ this._addWindowClone(windows[i]);
+ }
+ }
+
+ // Track window changes
+ this._windowAddedId = this.metaWorkspace.connect('window-added', this._windowAdded.bind(this));
+ this._windowRemovedId = this.metaWorkspace.connect('window-removed', this._windowRemoved.bind(this));
+ let display;
+ display = global.screen || global.display;
+ this._windowEnteredMonitorId = display.connect('window-entered-monitor', this._windowEnteredMonitor.bind(this));
+ this._windowLeftMonitorId = display.connect('window-left-monitor', this._windowLeftMonitor.bind(this));
+
+ this.state = WorkspaceThumbnail.ThumbnailState.NORMAL;
+ this._slidePosition = 0; // Fully slid in
+ this._collapseFraction = 0; // Not collapsed
+ }
+});
+
+const MultiMonitorsThumbnailsBox = new Lang.Class({
+ Name: 'MultiMonitorsThumbnailsBox',
+ Extends: WorkspaceThumbnail.ThumbnailsBox,
+
+ _init(monitorIndex) {
+ this._monitorIndex = monitorIndex;
+
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+
+ this.actor = new Shell.GenericContainer({ reactive: true,
+ style_class: 'workspace-thumbnails',
+ request_mode: Clutter.RequestMode.WIDTH_FOR_HEIGHT });
+ this.actor.connect('get-preferred-width', this._getPreferredWidth.bind(this));
+ this.actor.connect('get-preferred-height', this._getPreferredHeight.bind(this));
+ this.actor.connect('allocate', this._allocate.bind(this));
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+ this.actor._delegate = this;
+
+ let indicator = new St.Bin({ style_class: 'workspace-thumbnail-indicator' });
+
+ // We don't want the indicator to affect drag-and-drop
+ Shell.util_set_hidden_from_pick(indicator, true);
+
+ this._indicator = indicator;
+ this.actor.add_actor(indicator);
+
+ this._dropWorkspace = -1;
+ this._dropPlaceholderPos = -1;
+ this._dropPlaceholder = new St.Bin({ style_class: 'placeholder' });
+ this.actor.add_actor(this._dropPlaceholder);
+ this._spliceIndex = -1;
+
+ this._targetScale = 0;
+ this._scale = 0;
+ this._pendingScaleUpdate = false;
+ this._stateUpdateQueued = false;
+ this._animatingIndicator = false;
+ this._indicatorY = 0; // only used when _animatingIndicator is true
+
+ this._stateCounts = {};
+ for (let key in WorkspaceThumbnail.ThumbnailState)
+ this._stateCounts[WorkspaceThumbnail.ThumbnailState[key]] = 0;
+
+ this._thumbnails = [];
+ this._switchWorkspaceNotifyId = 0;
+ this._nWorkspacesNotifyId = 0;
+ this._syncStackingId = 0;
+ this._porthole = null;
+
+ this.actor.connect('button-press-event', () => { return Clutter.EVENT_STOP; });
+ this.actor.connect('button-release-event', this._onButtonRelease.bind(this));
+
+ this.actor.connect('touch-event', this._onTouchEvent.bind(this));
+
+ this._showingId = Main.overview.connect('showing', this._createThumbnails.bind(this));
+ this._hiddenId = Main.overview.connect('hidden', this._destroyThumbnails.bind(this));
+
+ this._itemDragBeginId = Main.overview.connect('item-drag-begin', this._onDragBegin.bind(this));
+ this._itemDragEndId = Main.overview.connect('item-drag-end', this._onDragEnd.bind(this));
+ this._itemDragCancelledId = Main.overview.connect('item-drag-cancelled', this._onDragCancelled.bind(this));
+ this._windowDragBeginId = Main.overview.connect('window-drag-begin', this._onDragBegin.bind(this));
+ this._windowDragEndId = Main.overview.connect('window-drag-end', this._onDragEnd.bind(this));
+ this._windowDragCancelledId = Main.overview.connect('window-drag-cancelled', this._onDragCancelled.bind(this));
+
+ this._settings = new Gio.Settings({ schema_id: WorkspaceThumbnail.OVERRIDE_SCHEMA });
+ this._changedDynamicWorkspacesId = this._settings.connect('changed::dynamic-workspaces',
+ this._updateSwitcherVisibility.bind(this));
+
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', () => {
+ this._destroyThumbnails();
+ if (Main.overview.visible)
+ this._createThumbnails();
+ });
+ }
+
+ this._switchWorkspaceNotifyId = 0;
+ this._nWorkspacesNotifyId = 0;
+ this._syncStackingId = 0;
+ this._workareasChangedId = 0;
+ },
+
+ _onDestroy(actor) {
+ this._destroyThumbnails();
+
+ Main.overview.disconnect(this._showingId);
+ Main.overview.disconnect(this._hiddenId);
+
+ Main.overview.disconnect(this._itemDragBeginId);
+ Main.overview.disconnect(this._itemDragEndId);
+ Main.overview.disconnect(this._itemDragCancelledId);
+ Main.overview.disconnect(this._windowDragBeginId);
+ Main.overview.disconnect(this._windowDragEndId);
+ Main.overview.disconnect(this._windowDragCancelledId);
+
+ this._settings.disconnect(this._changedDynamicWorkspacesId);
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ Main.layoutManager.disconnect(this._monitorsChangedId);
+ }
+ //TODO drag end ??
+
+ Tweener.removeTweens(actor);
+
+ this.actor._delegate = null;
+ },
+
+ addThumbnails(start, count) {
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ if (!this._ensurePorthole())
+ return;
+ }
+ else {
+ this._ensurePorthole24();
+ }
+
+ let display;
+ display = global.screen || global.workspace_manager;
+
+ for (let k = start; k < start + count; k++) {
+
+ let metaWorkspace = display.get_workspace_by_index(k);
+
+ let thumbnail = new MultiMonitorsWorkspaceThumbnail(metaWorkspace, this._monitorIndex);
+
+ thumbnail.setPorthole(this._porthole.x, this._porthole.y,
+ this._porthole.width, this._porthole.height);
+ this._thumbnails.push(thumbnail);
+ this.actor.add_actor(thumbnail.actor);
+
+ if (start > 0 && this._spliceIndex == -1) {
+ // not the initial fill, and not splicing via DND
+ thumbnail.state = WorkspaceThumbnail.ThumbnailState.NEW;
+ thumbnail.slidePosition = 1; // start slid out
+ this._haveNewThumbnails = true;
+ } else {
+ thumbnail.state = WorkspaceThumbnail.ThumbnailState.NORMAL;
+ }
+
+ this._stateCounts[thumbnail.state]++;
+ }
+
+ this._queueUpdateStates();
+
+ // The thumbnails indicator actually needs to be on top of the thumbnails
+ this._indicator.raise_top();
+
+ // Clear the splice index, we got the message
+ this._spliceIndex = -1;
+ },
+ // The "porthole" is the portion of the screen that we show in the
+ // workspaces
+ _ensurePorthole() {
+ if (!(Main.layoutManager.monitors.length>this._monitorIndex))
+ return false;
+
+ if (!this._porthole)
+ this._porthole = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex);
+
+ return true;
+ },
+ _ensurePorthole24() {
+ if (!this._porthole)
+ this._porthole = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex);
+ },
+});
+
+const MultiMonitorsSlidingControl = new Lang.Class({
+ Name: 'MultiMonitorsSlidingControl',
+ Extends: OverviewControls.SlidingControl,
+
+ _init(params) {
+ params = Params.parse(params, { slideDirection: OverviewControls.SlideDirection.LEFT });
+
+ this._visible = true;
+ this._inDrag = false;
+
+ this.layout = new OverviewControls.SlideLayout();
+ this.layout.slideDirection = params.slideDirection;
+ this.layout.translationX = 0;
+ this.actor = new St.Widget({ layout_manager: this.layout,
+ style_class: 'overview-controls',
+ clip_to_allocation: true });
+
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+
+ this._hidingId = Main.overview.connect('hiding', this._onOverviewHiding.bind(this));
+
+ this._itemDragBeginId = Main.overview.connect('item-drag-begin', this._onDragBegin.bind(this));
+ this._itemDragEndId = Main.overview.connect('item-drag-end', this._onDragEnd.bind(this));
+ this._itemDragCancelledId = Main.overview.connect('item-drag-cancelled', this._onDragEnd.bind(this));
+
+ this._windowDragBeginId = Main.overview.connect('window-drag-begin', this._onWindowDragBegin.bind(this));
+ this._windowDragCancelledId = Main.overview.connect('window-drag-cancelled', this._onWindowDragEnd.bind(this));
+ this._windowDragEndId = Main.overview.connect('window-drag-end', this._onWindowDragEnd.bind(this));
+
+ this.onAnimationBegin = null;
+ this.onAnimationEnd = null;
+ },
+
+ _onDestroy(actor) {
+ Main.overview.disconnect(this._hidingId);
+
+ Main.overview.disconnect(this._itemDragBeginId);
+ Main.overview.disconnect(this._itemDragEndId);
+ Main.overview.disconnect(this._itemDragCancelledId);
+
+ Main.overview.disconnect(this._windowDragBeginId);
+ Main.overview.disconnect(this._windowDragCancelledId);
+ Main.overview.disconnect(this._windowDragEndId);
+
+ Tweener.removeTweens(actor);
+ },
+
+ _updateTranslation() {
+ let translationStart = 0;
+ let translationEnd = 0;
+ let translation = this._getTranslation();
+
+ let shouldShow = (this._getSlide() > 0);
+ if (shouldShow) {
+ translationStart = translation;
+ } else {
+ translationEnd = translation;
+ }
+
+ if (this.layout.translationX == translationEnd)
+ return;
+
+ this.layout.translationX = translationStart;
+ if (this.onAnimationBegin) this.onAnimationBegin();
+ Tweener.addTween(this.layout, { translationX: translationEnd,
+ time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
+ transition: 'easeOutQuad',
+ onComplete() {
+ if (this.onAnimationEnd) this.onAnimationEnd();
+ },
+ onCompleteScope: this});
+ },
+});
+
+const MultiMonitorsThumbnailsSlider = new Lang.Class({
+ Name: 'MultiMonitorsThumbnailsSlider',
+ Extends: MultiMonitorsSlidingControl,
+
+ _init(thumbnailsBox) {
+ this.parent({ slideDirection: OverviewControls.SlideDirection.RIGHT });
+
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+
+ this._thumbnailsBox = thumbnailsBox;
+
+ this.actor.request_mode = Clutter.RequestMode.WIDTH_FOR_HEIGHT;
+ this.actor.reactive = true;
+ this.actor.track_hover = true;
+ this.actor.add_actor(this._thumbnailsBox.actor);
+
+ if(this._currentVersion[0]==3 && this._currentVersion[1]>28) {
+ this._activeWorkspaceChangedId = global.workspace_manager.connect('active-workspace-changed',
+ this._updateSlide.bind(this));
+ this._notifyNWorkspacesId = global.workspace_manager.connect('notify::n-workspaces',
+ this._updateSlide.bind(this));
+ }
+
+ this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', this._updateSlide.bind(this));
+ this.actor.connect('notify::hover', this._updateSlide.bind(this));
+
+ if(this._currentVersion[0]==3 && this._currentVersion[1]<26) {
+ this._switchWorkspaceId = global.window_manager.connect('switch-workspace', this._updateSlide.bind(this));
+ }
+
+ this._thumbnailsBox.actor.bind_property('visible', this.actor, 'visible', GObject.BindingFlags.SYNC_CREATE);
+ },
+
+ _onDestroy() {
+ Main.layoutManager.disconnect(this._monitorsChangedId);
+ if(this._currentVersion[0]==3 && this._currentVersion[1]<26) {
+ global.window_manager.disconnect(this._switchWorkspaceId);
+ }
+ if(this._currentVersion[0]==3 && this._currentVersion[1]>28) {
+ global.workspace_manager.disconnect(this._activeWorkspaceChangedId);
+ global.workspace_manager.disconnect(this._notifyNWorkspacesId);
+ }
+ this.parent();
+ },
+
+ _getAlwaysZoomOut: OverviewControls.ThumbnailsSlider.prototype._getAlwaysZoomOut,
+ getNonExpandedWidth: OverviewControls.ThumbnailsSlider.prototype.getNonExpandedWidth,
+ _getSlide: OverviewControls.ThumbnailsSlider.prototype._getSlide,
+ getVisibleWidth: OverviewControls.ThumbnailsSlider.prototype.getVisibleWidth,
+});
+
+const MultiMonitorsControlsManager = new Lang.Class({
+ Name: 'MultiMonitorsControlsManager',
+
+ _init(index) {
+ this._monitorIndex = index;
+ this._workspacesViews = null;
+
+ this._fullGeometry = null;
+ this._animationInProgress = false;
+
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+
+ this._thumbnailsBox = new MultiMonitorsThumbnailsBox(this._monitorIndex);
+ this._thumbnailsSlider = new MultiMonitorsThumbnailsSlider(this._thumbnailsBox);
+
+ this._thumbnailsSlider.onAnimationBegin = () => {
+ this._animationInProgress = true;
+ };
+ this._thumbnailsSlider.onAnimationEnd = () => {
+ this._animationInProgress = false;
+ if(!this._workspacesViews)
+ return;
+ let geometry = this.getWorkspacesActualGeometry();
+// global.log("actualG+ i: "+this._monitorIndex+" x: "+geometry.x+" y: "+geometry.y+" width: "+geometry.width+" height: "+geometry.height);
+ this._workspacesViews.setActualGeometry(geometry);
+ };
+
+ let reactiveFlag = false;
+
+ let layout = new OverviewControls.ControlsLayout();
+ this.actor = new St.Widget({ layout_manager: layout,
+ reactive: reactiveFlag,
+ x_expand: true, y_expand: true,
+ clip_to_allocation: true });
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+
+
+ this._group = new St.BoxLayout({ name: 'mm-overview-group',
+ x_expand: true, y_expand: true });
+ this.actor.add_actor(this._group);
+
+ this._viewActor = new St.Widget({ clip_to_allocation: true });
+
+ this._group.add(this._viewActor, { x_fill: true,
+ expand: true });
+
+ this._group.add_actor(this._thumbnailsSlider.actor);
+
+ layout.connect('allocation-changed', this._updateWorkspacesGeometry.bind(this));
+ this.actor.connect('scroll-event', this._onScrollEvent.bind(this));
+
+ this._settings = Convenience.getSettings();
+ this._thumbnailsOnLeftSideId = this._settings.connect('changed::'+THUMBNAILS_ON_LEFT_SIDE_ID,
+ this._thumbnailsOnLeftSide.bind(this));
+ this._thumbnailsOnLeftSide();
+
+ this._pageChangedId = Main.overview.viewSelector.connect('page-changed', this._setVisibility.bind(this));
+ this._pageEmptyId = Main.overview.viewSelector.connect('page-empty', this._onPageEmpty.bind(this));
+
+ this._clickAction = new Clutter.ClickAction()
+ this._clickedId = this._clickAction.connect('clicked', (action) => {
+ if (action.get_button() == 1 && this._workspacesViews &&
+ this._workspacesViews.getActiveWorkspace().isEmpty())
+ Main.overview.hide();
+ });
+
+ Main.mmOverview[this._monitorIndex].addAction(this._clickAction);
+ },
+
+ inOverviewInit() {
+ if (Main.overview.visible) {
+ this._thumbnailsBox._createThumbnails();
+ let activePage = Main.overview.viewSelector.getActivePage();
+ if (activePage != ViewSelector.ViewPage.WINDOWS) {
+ this._thumbnailsSlider.slideOut();
+ this._thumbnailsSlider.pageEmpty();
+ }
+ this.show();
+ }
+ },
+
+ _onScrollEvent(actor, event) {
+ if (!this.actor.mapped)
+ return Clutter.EVENT_PROPAGATE;
+ let display;
+ display = global.screen || global.workspace_manager;
+
+ let activeWs = display.get_active_workspace();
+ let ws;
+ switch (event.get_scroll_direction()) {
+ case Clutter.ScrollDirection.UP:
+ ws = activeWs.get_neighbor(Meta.MotionDirection.UP);
+ break;
+ case Clutter.ScrollDirection.DOWN:
+ ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN);
+ break;
+ default:
+ return Clutter.EVENT_PROPAGATE;
+ }
+ Main.wm.actionMoveWorkspace(ws);
+ return Clutter.EVENT_STOP;
+ },
+
+ _onDestroy() {
+ Main.overview.viewSelector.disconnect(this._pageChangedId);
+ Main.overview.viewSelector.disconnect(this._pageEmptyId);
+ this._settings.disconnect(this._thumbnailsOnLeftSideId);
+
+ this._clickAction.disconnect(this._clickedId);
+ Main.mmOverview[this._monitorIndex].removeAction(this._clickAction);
+ },
+
+ _thumbnailsOnLeftSide() {
+ if(this._settings.get_boolean(THUMBNAILS_ON_LEFT_SIDE_ID)){
+ let first = this._group.get_first_child();
+ if(first != this._thumbnailsSlider.actor){
+ this._thumbnailsSlider.layout.slideDirection = OverviewControls.SlideDirection.LEFT;
+ this._thumbnailsBox.actor.remove_style_class_name('workspace-thumbnails');
+ this._thumbnailsBox.actor.set_style_class_name('workspace-thumbnails workspace-thumbnails-left');
+ this._group.set_child_below_sibling(this._thumbnailsSlider.actor, first)
+ }
+ }
+ else{
+ let last = this._group.get_last_child();
+ if(last != this._thumbnailsSlider.actor){
+ this._thumbnailsSlider.layout.slideDirection = OverviewControls.SlideDirection.RIGHT;
+ this._thumbnailsBox.actor.remove_style_class_name('workspace-thumbnails workspace-thumbnails-left');
+ this._thumbnailsBox.actor.set_style_class_name('workspace-thumbnails');
+ this._group.set_child_above_sibling(this._thumbnailsSlider.actor, last);
+ }
+ }
+ },
+
+ getWorkspacesGeometry() {
+ if (!(Main.layoutManager.monitors.length>this._monitorIndex)) {
+ return { x: -1, y: -1, width: -1, height: -1 };
+ }
+ let top_spacer_height = Main.layoutManager.primaryMonitor.height;
+
+ let panelGhost_height = 0;
+ if(Main.mmOverview[this._monitorIndex]._panelGhost)
+ panelGhost_height = Main.mmOverview[this._monitorIndex]._panelGhost.get_height();
+
+ let allocation = Main.overview._controls.actor.allocation;
+ let primaryControl_height = allocation.y2 - allocation.y1;
+ let bottom_spacer_height = Main.layoutManager.primaryMonitor.height - allocation.y2;
+
+ top_spacer_height -= primaryControl_height + panelGhost_height + bottom_spacer_height;
+ top_spacer_height = Math.round(top_spacer_height);
+
+ let spacer = Main.mmOverview[this._monitorIndex]._spacer;
+ if (spacer.get_height()!=top_spacer_height)
+ spacer.set_height(top_spacer_height);
+
+ let [x, y] = this.actor.get_transformed_position();
+ let [width, height] = this.actor.get_transformed_size();
+
+ if (width < Main.layoutManager.monitors[this._monitorIndex].width*0.05) {
+ width = Main.layoutManager.monitors[this._monitorIndex].width;
+ height = Main.layoutManager.monitors[this._monitorIndex].height;
+ height -= top_spacer_height + panelGhost_height + bottom_spacer_height;
+ let _y = Main.layoutManager.monitors[this._monitorIndex].y;
+ if ((y-_y)<(top_spacer_height+panelGhost_height)) {
+ y += top_spacer_height;
+ x = Main.layoutManager.monitors[this._monitorIndex].x
+ }
+ }
+
+ let geometry = { x: x, y: y, width: width, height: height };
+// global.log("getWorkspacesGeometry x: "+geometry.x+" y: "+geometry.y+" width: "+geometry.width+" height: "+geometry.height);
+ let spacing = this.actor.get_theme_node().get_length('spacing');
+
+ let thumbnailsWidth = this._thumbnailsSlider.getVisibleWidth() + spacing;
+
+ geometry.width -= thumbnailsWidth;
+
+ if(this._settings.get_boolean(THUMBNAILS_ON_LEFT_SIDE_ID)){
+ geometry.x += thumbnailsWidth;
+ }
+ return geometry;
+ },
+
+ isAnimationInProgress() {
+ return this._animationInProgress;
+ },
+
+ getWorkspacesFullGeometry() {
+ if (this._fullGeometry)
+ return this._fullGeometry;
+ else
+ return Main.layoutManager.monitors[this._monitorIndex];
+ },
+
+ getWorkspacesActualGeometry() {
+ let [x, y] = this._viewActor.get_transformed_position();
+ let allocation = this._viewActor.allocation;
+ let width = allocation.x2 - allocation.x1;
+ let height = allocation.y2 - allocation.y1;
+ return { x: x, y: y, width: width, height: height };
+ },
+
+ _updateWorkspacesGeometry() {
+ this._fullGeometry = this.getWorkspacesGeometry();
+ if(!this._workspacesViews)
+ return;
+ this._workspacesViews.setFullGeometry(this._fullGeometry);
+ },
+
+ _setVisibility() {
+ // Ignore the case when we're leaving the overview, since
+ // actors will be made visible again when entering the overview
+ // next time, and animating them while doing so is just
+ // unnecessary noise
+ if (!Main.overview.visible ||
+ (Main.overview.animationInProgress && !Main.overview.visibleTarget))
+ return;
+
+ let activePage = Main.overview.viewSelector.getActivePage();
+
+ let thumbnailsVisible = (activePage == ViewSelector.ViewPage.WINDOWS);
+
+ let opacity = null;
+ if (thumbnailsVisible){
+ this._thumbnailsSlider.slideIn();
+
+ opacity = 255;
+ }
+ else{
+ this._thumbnailsSlider.slideOut();
+
+ opacity = 0;
+ }
+
+ if(!this._workspacesViews)
+ return;
+
+ this._workspacesViews.actor.visible = opacity != 0;
+ Tweener.addTween((this._workspacesViews.actor, this._viewActor),
+ { opacity: opacity,
+ time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
+ transition: 'easeOutQuad'
+ });
+ },
+
+ _onPageEmpty() {
+ this._thumbnailsSlider.pageEmpty();
+ },
+
+ show() {
+ this._workspacesViews = Main.overview.viewSelector._workspacesDisplay._workspacesViews[this._monitorIndex];
+ },
+
+ hide() {
+ if (this._workspacesViews && (!this._workspacesViews.actor.visible)) {
+ this._workspacesViews.actor.opacity = 255;
+ this._workspacesViews.actor.visible = true;
+ }
+ this._workspacesViews = null;
+ }
+});
+
+var MultiMonitorsOverview = new Lang.Class({
+ Name: 'MultiMonitorsOverview',
+
+ _init(index) {
+ this.monitorIndex = index;
+ this._settings = Convenience.getSettings();
+
+ this._overview = new St.BoxLayout({ name: 'overview'+this.monitorIndex,
+ accessible_name: _("Overview"+this.monitorIndex),
+ vertical: true});
+ this._overview.add_constraint(new LayoutManager.MonitorConstraint({ index: this.monitorIndex }));
+ this._overview.connect('destroy', this._onDestroy.bind(this));
+ this._overview._delegate = this;
+
+ Main.layoutManager.overviewGroup.add_child(this._overview);
+
+ this._showingId = null;
+ this._hidingId = null;
+ },
+
+ init() {
+ this._panelGhost = null;
+
+ if (Main.mmPanel) {
+ for (let idx in Main.mmPanel) {
+ if (Main.mmPanel[idx].monitorIndex === this.monitorIndex) {
+ this._panelGhost = new St.Bin({ child: new Clutter.Clone({source: Main.mmPanel[idx].actor}), reactive: false, opacity: 0 });
+ this._overview.add_actor(this._panelGhost);
+ break;
+ }
+ }
+ }
+
+ this._spacer = new St.Widget();
+ this._overview.add_actor(this._spacer);
+
+ this._controls = new MultiMonitorsControlsManager(this.monitorIndex);
+ this._overview.add(this._controls.actor, { y_fill: true, expand: true });
+ this._controls.inOverviewInit();
+
+ this._showingId = Main.overview.connect('showing', this._show.bind(this));
+ this._hidingId = Main.overview.connect('hiding', this._hide.bind(this));
+ },
+
+ getWorkspacesFullGeometry() {
+ return this._controls.getWorkspacesFullGeometry();
+ },
+
+ getWorkspacesActualGeometry() {
+ if (this._controls.isAnimationInProgress())
+ return null;
+ return this._controls.getWorkspacesActualGeometry();
+ },
+
+ _onDestroy(actor) {
+ if(this._showingId)
+ Main.overview.disconnect(this._showingId);
+ if(this._hidingId)
+ Main.overview.disconnect(this._hidingId);
+
+ Main.layoutManager.overviewGroup.remove_child(this._overview);
+
+ this._overview._delegate = null;
+ },
+
+ _show() {
+ this._controls.show();
+ },
+
+ _hide() {
+ this._controls.hide();
+ },
+
+ destroy() {
+ this._overview.destroy();
+ },
+
+ addAction(action) {
+// if (this.isDummy)
+// return;
+
+ this._overview.add_action(action);
+// _overview >> _backgroundGroup
+ },
+
+ removeAction(action) {
+ if(action.get_actor())
+ this._overview.remove_action(action);
+ }
+
+});
+
+
+var MultiMonitorsWorkspacesDisplay = new Lang.Class({
+ Name: 'MultiMonitorsWorkspacesDisplay',
+ Extends: WorkspacesView.WorkspacesDisplay,
+
+ _init() {
+ this.parent();
+ this._restackedNotifyId = 0;
+ },
+
+ _workspacesOnlyOnPrimaryChanged() {
+ this._workspacesOnlyOnPrimary = this._settings.get_boolean('workspaces-only-on-primary');
+
+ if (!Main.overview.visible)
+ return;
+
+ if (!this._fullGeometry)
+ return;
+
+ this._updateWorkspacesViews();
+ },
+
+ _updateWorkspacesFullGeometry() {
+ if (this._workspacesViews.length!=Main.layoutManager.monitors.length)
+ return;
+
+ let monitors = Main.layoutManager.monitors;
+ for (let i = 0; i < monitors.length; i++) {
+ let geometry;
+ if (i == this._primaryIndex) {
+ geometry = this._fullGeometry;
+ }
+ else if (Main.mmOverview && Main.mmOverview[i]) {
+ geometry = Main.mmOverview[i].getWorkspacesFullGeometry();
+ }
+ else {
+ geometry = monitors[i];
+ }
+// global.log("fulllG i: "+i+" x: "+geometry.x+" y: "+geometry.y+" width: "+geometry.width+" height: "+geometry.height);
+ this._workspacesViews[i].setFullGeometry(geometry);
+ }
+ },
+
+ _updateWorkspacesActualGeometry() {
+ if (this._workspacesViews.length!=Main.layoutManager.monitors.length)
+ return;
+
+ let [x, y] = this.actor.get_transformed_position();
+ let allocation = this.actor.allocation;
+ let width = allocation.x2 - allocation.x1;
+ let height = allocation.y2 - allocation.y1;
+ let primaryGeometry = { x: x, y: y, width: width, height: height };
+
+ let monitors = Main.layoutManager.monitors;
+ for (let i = 0; i < monitors.length; i++) {
+ let geometry;
+ if (i == this._primaryIndex) {
+ geometry = primaryGeometry;
+ }
+ else if (Main.mmOverview && Main.mmOverview[i]) {
+ geometry = Main.mmOverview[i].getWorkspacesActualGeometry();
+ }
+ else {
+ geometry = monitors[i];
+ }
+ if (geometry) {
+// global.log("actualG i: "+i+" x: "+geometry.x+" y: "+geometry.y+" width: "+geometry.width+" height: "+geometry.height);
+ this._workspacesViews[i].setActualGeometry(geometry);
+ }
+ }
+ }
+
+});
diff --git a/multi-monitors-add-on@spin83/mmpanel.js b/multi-monitors-add-on@spin83/mmpanel.js
new file mode 100644
index 0000000..82a7fb9
--- /dev/null
+++ b/multi-monitors-add-on@spin83/mmpanel.js
@@ -0,0 +1,632 @@
+/*
+Copyright (C) 2014 spin83
+
+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, visit https://www.gnu.org/licenses/.
+*/
+
+const Lang = imports.lang;
+
+const St = imports.gi.St;
+const Shell = imports.gi.Shell;
+const Meta = imports.gi.Meta;
+const Atk = imports.gi.Atk;
+const Clutter = imports.gi.Clutter;
+const GnomeDesktop = imports.gi.GnomeDesktop;
+const GObject = imports.gi.GObject;
+
+const Main = imports.ui.main;
+const Tweener = imports.ui.tweener;
+const Panel = imports.ui.panel;
+const PopupMenu = imports.ui.popupMenu;
+const PanelMenu = imports.ui.panelMenu;
+const CtrlAltTab = imports.ui.ctrlAltTab;
+const ExtensionSystem = imports.ui.extensionSystem;
+
+const Config = imports.misc.config;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const MultiMonitors = ExtensionUtils.getCurrentExtension();
+const Convenience = MultiMonitors.imports.convenience;
+const MMCalendar = MultiMonitors.imports.mmcalendar;
+
+const SHOW_ACTIVITIES_ID = 'show-activities';
+var SHOW_APP_MENU_ID = 'show-app-menu';
+const SHOW_DATE_TIME_ID = 'show-date-time';
+const AVAILABLE_INDICATORS_ID = 'available-indicators';
+const TRANSFER_INDICATORS_ID = 'transfer-indicators';
+
+var StatusIndicatorsController = new Lang.Class({
+ Name: 'StatusIndicatorController',
+
+ _init() {
+ this._transfered_indicators = []; //{iname:, box:, monitor:}
+ this._settings = Convenience.getSettings();
+
+ this._updatedSessionId = Main.sessionMode.connect('updated', this._updateSessionIndicators.bind(this));
+ this._updateSessionIndicators();
+ this._extensionStateChangedId = ExtensionSystem.connect('extension-state-changed',
+ this._extensionStateChanged.bind(this));
+
+ this._transferIndicatorsId = this._settings.connect('changed::'+TRANSFER_INDICATORS_ID,
+ this.transferIndicators.bind(this));
+ },
+
+ destroy() {
+ this._settings.disconnect(this._transferIndicatorsId);
+ ExtensionSystem.disconnect(this._extensionStateChangedId);
+ Main.sessionMode.disconnect(this._updatedSessionId);
+ this._settings.set_strv(AVAILABLE_INDICATORS_ID, []);
+ this._transferBack(this._transfered_indicators);
+ },
+
+ transferBack(panel) {
+ let transfer_back = this._transfered_indicators.filter((element) => {
+ return element.monitor==panel.monitorIndex;
+ });
+
+ this._transferBack(transfer_back, panel);
+ },
+
+ transferIndicators() {
+ let boxs = ['_leftBox', '_centerBox', '_rightBox'];
+ let transfers = this._settings.get_value(TRANSFER_INDICATORS_ID).deep_unpack();
+ let show_app_menu = this._settings.get_value(SHOW_APP_MENU_ID);
+
+ let transfer_back = this._transfered_indicators.filter((element) => {
+ return !transfers.hasOwnProperty(element.iname);
+ });
+
+ this._transferBack(transfer_back);
+
+ for(let iname in transfers) {
+ if(transfers.hasOwnProperty(iname) && Main.panel.statusArea[iname]) {
+ let monitor = transfers[iname];
+
+ let indicator = Main.panel.statusArea[iname];
+ let panel = this._findPanel(monitor);
+ boxs.forEach((box) => {
+ if(Main.panel[box].contains(indicator.container) && panel) {
+ global.log('a '+box+ " > " + iname + " : "+ monitor);
+ this._transfered_indicators.push({iname:iname, box:box, monitor:monitor});
+ Main.panel[box].remove_child(indicator.container);
+ if (show_app_menu && box === '_leftBox')
+ panel[box].insert_child_at_index(indicator.container, 1);
+ else
+ panel[box].insert_child_at_index(indicator.container, 0);
+ }
+ });
+ }
+ }
+ },
+
+ _findPanel(monitor) {
+ for (let i = 0; i < Main.mmPanel.length; i++) {
+ if (Main.mmPanel[i].monitorIndex == monitor) {
+ return Main.mmPanel[i];
+ }
+ }
+ return null;
+ },
+
+ _transferBack(transfer_back, panel) {
+ transfer_back.forEach((element) => {
+ this._transfered_indicators.splice(this._transfered_indicators.indexOf(element));
+ if(Main.panel.statusArea[element.iname]) {
+ let indicator = Main.panel.statusArea[element.iname];
+ if(!panel) {
+ panel = this._findPanel(element.monitor);
+ }
+ if(panel[element.box].contains(indicator.container)) {
+ global.log("r "+element.box+ " > " + element.iname + " : "+ element.monitor);
+ panel[element.box].remove_child(indicator.container);
+ if (element.box === '_leftBox')
+ Main.panel[element.box].insert_child_at_index(indicator.container, 1);
+ else
+ Main.panel[element.box].insert_child_at_index(indicator.container, 0);
+ }
+ }
+ });
+ },
+
+ _extensionStateChanged() {
+ this._findAvailableIndicators();
+ this.transferIndicators();
+ },
+
+ _updateSessionIndicators() {
+ let session_indicators = [];
+ session_indicators.push('MultiMonitorsAddOn');
+ let sessionPanel = Main.sessionMode.panel;
+ for (let sessionBox in sessionPanel){
+ sessionPanel[sessionBox].forEach((sesionIndicator) => {
+ session_indicators.push(sesionIndicator);
+ });
+ }
+ this._session_indicators = session_indicators;
+ this._available_indicators = [];
+
+ this._findAvailableIndicators();
+ this.transferIndicators();
+ },
+
+ _findAvailableIndicators() {
+ let available_indicators = [];
+ let statusArea = Main.panel.statusArea;
+ for(let indicator in statusArea) {
+ if(statusArea.hasOwnProperty(indicator) && this._session_indicators.indexOf(indicator)<0){
+ available_indicators.push(indicator);
+ }
+ }
+ if(available_indicators.length!=this._available_indicators.length) {
+ this._available_indicators = available_indicators;
+// global.log(this._available_indicators);
+ this._settings.set_strv(AVAILABLE_INDICATORS_ID, this._available_indicators);
+ }
+ }
+});
+
+var MultiMonitorsAppMenuButton = new Lang.Class({
+ Name: 'MultiMonitorsAppMenuButton',
+ Extends: Panel.AppMenuButton,
+
+ _init(panel) {
+ if (panel.monitorIndex==undefined)
+ this._monitorIndex = Main.layoutManager.primaryIndex;
+ else
+ this._monitorIndex = panel.monitorIndex;
+ this._actionOnWorkspaceGroupNotifyId = 0;
+ this._targetAppGroup = null;
+ this._lastFocusedWindow = null;
+ this.parent(panel);
+
+ let display;
+ display = global.screen || global.display;
+
+ this._windowEnteredMonitorId = display.connect('window-entered-monitor',
+ this._windowEnteredMonitor.bind(this));
+ this._windowLeftMonitorId = display.connect('window-left-monitor',
+ this._windowLeftMonitor.bind(this));
+ },
+
+ _windowEnteredMonitor (metaScreen, monitorIndex, metaWin) {
+ if (monitorIndex == this._monitorIndex) {
+ switch(metaWin.get_window_type()){
+ case Meta.WindowType.NORMAL:
+ case Meta.WindowType.DIALOG:
+ case Meta.WindowType.MODAL_DIALOG:
+ case Meta.WindowType.SPLASHSCREEN:
+ this._sync();
+ break;
+ }
+ }
+ },
+
+ _windowLeftMonitor (metaScreen, monitorIndex, metaWin) {
+ if (monitorIndex == this._monitorIndex) {
+ switch(metaWin.get_window_type()){
+ case Meta.WindowType.NORMAL:
+ case Meta.WindowType.DIALOG:
+ case Meta.WindowType.MODAL_DIALOG:
+ case Meta.WindowType.SPLASHSCREEN:
+ this._sync();
+ break;
+ }
+ }
+ },
+
+ _findTargetApp() {
+
+ if (this._actionOnWorkspaceGroupNotifyId) {
+ this._targetAppGroup.disconnect(this._actionOnWorkspaceGroupNotifyId);
+ this._actionOnWorkspaceGroupNotifyId = 0;
+ this._targetAppGroup = null;
+ }
+ let groupWindow = false;
+ let groupFocus = false;
+
+ let display;
+ display = global.screen || global.workspace_manager;
+
+ let workspace = display.get_active_workspace();
+ let tracker = Shell.WindowTracker.get_default();
+ let focusedApp = tracker.focus_app;
+ if (focusedApp && focusedApp.is_on_workspace(workspace)){
+ let windows = focusedApp.get_windows();
+ for (let i = 0; i < windows.length; i++) {
+ let win = windows[i];
+ if(win.located_on_workspace(workspace)){
+ if(win.get_monitor() == this._monitorIndex){
+ if(win.has_focus()){
+ this._lastFocusedWindow = win;
+// global.log(this._monitorIndex+": focus :"+win.get_title()+" : "+win.has_focus());
+ return focusedApp;
+ }
+ else
+ groupWindow = true;
+ }
+ else{
+ if(win.has_focus())
+ groupFocus = true;
+ }
+ if(groupFocus && groupWindow){
+ if(focusedApp != this._targetApp){
+ this._targetAppGroup = focusedApp;
+ this._actionOnWorkspaceGroupNotifyId = this._targetAppGroup.connect('notify::action-group',
+ this._sync.bind(this));
+// global.log(this._monitorIndex+": gConnect :"+win.get_title()+" : "+win.has_focus());
+ }
+ break;
+ }
+ }
+ }
+ }
+
+ for (let i = 0; i < this._startingApps.length; i++)
+ if (this._startingApps[i].is_on_workspace(workspace)){
+// global.log(this._monitorIndex+": newAppFocus");
+ return this._startingApps[i];
+ }
+
+ if (this._lastFocusedWindow && this._lastFocusedWindow.located_on_workspace(workspace) &&
+ this._lastFocusedWindow.get_monitor() == this._monitorIndex){
+// global.log(this._monitorIndex+": lastFocus :"+this._lastFocusedWindow.get_title());
+ return tracker.get_window_app(this._lastFocusedWindow);
+ }
+
+ if (global.screen)
+ display = global.screen.get_display();
+ else
+ display = global.display;
+
+ let windows = display.get_tab_list(Meta.TabList.NORMAL_ALL, workspace);
+
+ for (let i = 0; i < windows.length; i++) {
+ if(windows[i].get_monitor() == this._monitorIndex){
+ this._lastFocusedWindow = windows[i];
+// global.log(this._monitorIndex+": appFind :"+windows[i].get_title());
+ return tracker.get_window_app(windows[i]);
+ }
+ }
+
+ return null;
+ },
+ destroy() {
+ if (this._actionGroupNotifyId) {
+ this._targetApp.disconnect(this._actionGroupNotifyId);
+ this._actionGroupNotifyId = 0;
+ }
+
+ let display;
+ display = global.screen || global.display;
+
+ display.disconnect(this._windowEnteredMonitorId);
+ display.disconnect(this._windowLeftMonitorId);
+
+ this.parent();
+ }
+});
+
+const MultiMonitorsActivitiesButton = new Lang.Class({
+ Name: 'MultiMonitorsActivitiesButton',
+ Extends: PanelMenu.Button,
+
+ handleDragOver: Panel.ActivitiesButton.prototype["handleDragOver"],
+ _onCapturedEvent: Panel.ActivitiesButton.prototype["_onCapturedEvent"],
+ _onEvent: Panel.ActivitiesButton.prototype["_onEvent"],
+ _onKeyRelease: Panel.ActivitiesButton.prototype["_onKeyRelease"],
+ _xdndToggleOverview: Panel.ActivitiesButton.prototype["_xdndToggleOverview"],
+
+ _init() {
+ this.parent(0.0, null, true);
+ this.actor.accessible_role = Atk.Role.TOGGLE_BUTTON;
+
+ this.actor.name = 'mmPanelActivities';
+
+ /* Translators: If there is no suitable word for "Activities"
+ in your language, you can use the word for "Overview". */
+ this._label = new St.Label({ text: _("Activities"),
+ y_align: Clutter.ActorAlign.CENTER });
+ this.actor.add_actor(this._label);
+
+ this.actor.label_actor = this._label;
+
+ this.actor.connect('captured-event', this._onCapturedEvent.bind(this));
+ this.actor.connect_after('key-release-event', this._onKeyRelease.bind(this));
+
+ this._showingId = Main.overview.connect('showing', () => {
+ this.actor.add_style_pseudo_class('overview');
+ this.actor.add_accessible_state (Atk.StateType.CHECKED);
+ });
+ this._hidingId = Main.overview.connect('hiding', () => {
+ this.actor.remove_style_pseudo_class('overview');
+ this.actor.remove_accessible_state (Atk.StateType.CHECKED);
+ });
+
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+
+ this._xdndTimeOut = 0;
+ },
+
+ _onDestroy(actor) {
+ Main.overview.disconnect(this._showingId);
+ Main.overview.disconnect(this._hidingId);
+ }
+
+});
+
+const MULTI_MONITOR_PANEL_ITEM_IMPLEMENTATIONS = {
+ 'activities': MultiMonitorsActivitiesButton,
+// 'aggregateMenu': Panel.AggregateMenu,
+ 'appMenu': MultiMonitorsAppMenuButton,
+ 'dateMenu': MMCalendar.MultiMonitorsDateMenuButton,
+// 'a11y': imports.ui.status.accessibility.ATIndicator,
+// 'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
+ };
+
+var MultiMonitorsPanel = new Lang.Class({
+ Name: 'MultiMonitorsPanel',
+ Extends: Panel.Panel,
+
+ _init (monitorIndex, mmPanelBox) {
+ this.monitorIndex = monitorIndex;
+
+ this._currentVersion = Config.PACKAGE_VERSION.split('.');
+
+ this.actor = new Shell.GenericContainer({ name: 'panel', reactive: true });
+ this.actor._delegate = this;
+
+ this.actor.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
+
+ this._sessionStyle = null;
+
+ this.statusArea = {};
+
+ this.menuManager = new PopupMenu.PopupMenuManager(this);
+
+ this._leftBox = new St.BoxLayout({ name: 'panelLeft' });
+ this.actor.add_actor(this._leftBox);
+ this._centerBox = new St.BoxLayout({ name: 'panelCenter' });
+ this.actor.add_actor(this._centerBox);
+ this._rightBox = new St.BoxLayout({ name: 'panelRight' });
+ this.actor.add_actor(this._rightBox);
+
+ this._leftCorner = new Panel.PanelCorner(St.Side.LEFT);
+ this.actor.add_actor(this._leftCorner.actor);
+
+ this._rightCorner = new Panel.PanelCorner(St.Side.RIGHT);
+ this.actor.add_actor(this._rightCorner.actor);
+
+ this.actor.connect('get-preferred-width', this._getPreferredWidth.bind(this));
+ this.actor.connect('get-preferred-height', this._getPreferredHeight.bind(this));
+ this.actor.connect('allocate', this._allocate.bind(this));
+ this.actor.connect('button-press-event', this._onButtonPress.bind(this));
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>28) {
+ this.actor.connect('touch-event', this._onButtonPress.bind(this));
+ }
+ this.actor.connect('destroy', this._onDestroy.bind(this));
+
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>26) {
+ this.actor.connect('key-press-event', this._onKeyPress.bind(this));
+ }
+
+ this._showingId = Main.overview.connect('showing', () => {
+ this.actor.add_style_pseudo_class('overview');
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ this._updateSolidStyle();
+ }
+ });
+ this._hidingId = Main.overview.connect('hiding', () => {
+ this.actor.remove_style_pseudo_class('overview');
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ this._updateSolidStyle();
+ }
+ });
+
+ mmPanelBox.panelBox.add(this.actor);
+
+ Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar")+" "+this.monitorIndex, 'focus-top-bar-symbolic',
+ { sortGroup: CtrlAltTab.SortGroup.TOP });
+
+ this._updatedId = Main.sessionMode.connect('updated', this._updatePanel.bind(this));
+
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ this._trackedWindows = new Map();
+ this._actorAddedId = global.window_group.connect('actor-added', this._onWindowActorAdded.bind(this));
+ this._actorRemovedId = global.window_group.connect('actor-removed', this._onWindowActorRemoved.bind(this));
+ this._switchWorkspaceId = global.window_manager.connect('switch-workspace', this._updateSolidStyle.bind(this));
+
+ global.window_group.get_children().forEach(metaWindowActor => {
+ if (metaWindowActor['get_meta_window'] && metaWindowActor.get_meta_window().get_window_type() != Meta.WindowType.DESKTOP)
+ this._onWindowActorAdded(null, metaWindowActor);
+ });
+ }
+
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>26) {
+ let display;
+ //global.screen < 3.30
+ display = global.screen || global.display;
+
+ this._workareasChangedId = display.connect('workareas-changed', () => { this.actor.queue_relayout(); });
+ }
+
+ this._updatePanel();
+
+ this._settings = Convenience.getSettings();
+ this._showActivitiesId = this._settings.connect('changed::'+SHOW_ACTIVITIES_ID,
+ this._showActivities.bind(this));
+ this._showActivities();
+
+ this._showAppMenuId = this._settings.connect('changed::'+SHOW_APP_MENU_ID,
+ this._showAppMenu.bind(this));
+ this._showAppMenu();
+
+ this._showDateTimeId = this._settings.connect('changed::'+SHOW_DATE_TIME_ID,
+ this._showDateTime.bind(this));
+ this._showDateTime();
+
+ },
+
+ _onDestroy(actor) {
+
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>26) {
+ let display;
+ display = global.screen || global.display;
+
+ display.disconnect(this._workareasChangedId);
+ }
+
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ global.window_group.disconnect(this._actorAddedId);
+ global.window_group.disconnect(this._actorRemovedId);
+ global.window_manager.disconnect(this._switchWorkspaceId);
+
+ this._trackedWindows.forEach((value, key, map) => {
+ value.forEach(id => {
+ key.disconnect(id);
+ });
+ });
+ }
+
+ Main.overview.disconnect(this._showingId);
+ Main.overview.disconnect(this._hidingId);
+ this._settings.disconnect(this._showActivitiesId);
+ this._settings.disconnect(this._showAppMenuId);
+// Tweener.removeTweens(actor);
+
+ Main.ctrlAltTabManager.removeGroup(this.actor);
+
+ Main.sessionMode.disconnect(this._updatedId);
+
+ for (let name in this.statusArea) {
+ if(this.statusArea.hasOwnProperty(name))
+ this.statusArea[name].destroy();
+ delete this.statusArea[name];
+ }
+
+ this.actor._delegate = null;
+ },
+
+ _showActivities() {
+ let name = 'activities';
+ if(this._settings.get_boolean(SHOW_ACTIVITIES_ID)){
+ if(this.statusArea[name])
+ this.statusArea[name].actor.visible = true;
+ }
+ else{
+ if(this.statusArea[name])
+ this.statusArea[name].actor.visible = false;
+ }
+ },
+
+ _showDateTime() {
+ let name = 'dateMenu';
+ if(this._settings.get_boolean(SHOW_DATE_TIME_ID)){
+ if(this.statusArea[name])
+ this.statusArea[name].actor.visible = true;
+ }
+ else{
+ if(this.statusArea[name])
+ this.statusArea[name].actor.visible = false;
+ }
+ },
+
+ _showAppMenu() {
+ let name = 'appMenu';
+ if(this._settings.get_boolean(SHOW_APP_MENU_ID)){
+ if(!this.statusArea[name]){
+ let indicator = new MultiMonitorsAppMenuButton(this);
+ this.statusArea[name] = indicator;
+ let box = this._leftBox;
+ this._addToPanelBox(name, indicator, box.get_n_children()+1, box);
+ }
+ }
+ else{
+ if(this.statusArea[name]){
+ let indicator = this.statusArea[name];
+ this.menuManager.removeMenu(indicator.menu);
+ indicator.destroy();
+ }
+ }
+ },
+
+ _getPreferredWidth(actor, forHeight, alloc) {
+ alloc.min_size = -1;
+ if(Main.layoutManager.monitors.length>this.monitorIndex)
+ alloc.natural_size = Main.layoutManager.monitors[this.monitorIndex].width;
+ else
+ alloc.natural_size = -1;
+ },
+
+ _updateSolidStyle() {
+ if (this.actor.has_style_pseudo_class('overview') || !Main.sessionMode.hasWindows) {
+ this._removeStyleClassName('solid');
+ return;
+ }
+
+ if (!(Main.layoutManager.monitors.length>this.monitorIndex))
+ return;
+
+ /* Get all the windows in the active workspace that are in the primary monitor and visible */
+ let display;
+ display = global.screen || global.workspace_manager;
+ let activeWorkspace = display.get_active_workspace();
+ let monitorIndex = this.monitorIndex;
+ let windows = activeWorkspace.list_windows().filter((metaWindow) => {
+ return metaWindow.get_monitor() == monitorIndex &&
+ metaWindow.showing_on_its_workspace() &&
+ metaWindow.get_window_type() != Meta.WindowType.DESKTOP;
+ });
+
+ /* Check if at least one window is near enough to the panel */
+ let [, panelTop] = this.actor.get_transformed_position();
+ let panelBottom = panelTop + this.actor.get_height();
+ let scale = St.ThemeContext.get_for_stage(global.stage).scale_factor;
+ let isNearEnough = windows.some((metaWindow) => {
+ let verticalPosition = metaWindow.get_frame_rect().y;
+ return verticalPosition < panelBottom + 5 * scale;
+ });
+
+ if (isNearEnough)
+ this._addStyleClassName('solid');
+ else
+ this._removeStyleClassName('solid');
+ },
+
+
+ _hideIndicators() {
+ for (let role in MULTI_MONITOR_PANEL_ITEM_IMPLEMENTATIONS) {
+ let indicator = this.statusArea[role];
+ if (!indicator)
+ continue;
+ if (this._currentVersion[0]==3 && this._currentVersion[1]>24) {
+ if (indicator.menu)
+ indicator.menu.close();
+ }
+ indicator.container.hide();
+ }
+ },
+
+ _ensureIndicator(role) {
+ let indicator = this.statusArea[role];
+ if (!indicator) {
+ let constructor = MULTI_MONITOR_PANEL_ITEM_IMPLEMENTATIONS[role];
+ if (!constructor) {
+ return null;
+ }
+ indicator = new constructor(this);
+ this.statusArea[role] = indicator;
+ }
+ return indicator;
+ }
+});
diff --git a/multi-monitors-add-on@spin83/multi-monitors-add-on.pot b/multi-monitors-add-on@spin83/multi-monitors-add-on.pot
new file mode 100644
index 0000000..94c5ba9
--- /dev/null
+++ b/multi-monitors-add-on@spin83/multi-monitors-add-on.pot
@@ -0,0 +1,78 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2015-01-23 22:29+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: prefs.js:61
+msgid "Show Multi Monitors indicator on Top Panel."
+msgstr ""
+
+#: prefs.js:62
+msgid "Show Panel on additional monitors."
+msgstr ""
+
+#: prefs.js:63
+msgid "Show Thumbnails-Slider on additional monitors."
+msgstr ""
+
+#: prefs.js:64
+msgid "Show Activities-Button on additional monitors."
+msgstr ""
+
+#: prefs.js:65
+msgid "Show AppMenu-Button on additional monitors."
+msgstr ""
+
+#: prefs.js:66
+msgid "Show Thumbnails-Slider on left side of additional monitors."
+msgstr ""
+
+#: prefs.js:67
+msgid "Show DateTime-Button on additional monitors."
+msgstr ""
+
+#: prefs.js:75
+msgid "A list of indicators for transfer to additional monitors."
+msgstr ""
+
+#: prefs.js:122
+msgid "Select indicator"
+msgstr ""
+
+#: prefs.js:125
+msgid "Add"
+msgstr ""
+
+#: prefs.js:139
+msgid "Indicators on Top Panel"
+msgstr ""
+
+#: prefs.js:168
+msgid "Monitor index:"
+msgstr ""
+
+#: indicator.js:106
+msgid "Preferences"
+msgstr ""
+
+#: indicator.js:107
+msgid "Test"
+msgstr ""
+
+#: indicator.js:129
+msgid "Multi Monitors Add-On"
+msgstr ""
diff --git a/multi-monitors-add-on@spin83/prefs.js b/multi-monitors-add-on@spin83/prefs.js
new file mode 100644
index 0000000..3689cf0
--- /dev/null
+++ b/multi-monitors-add-on@spin83/prefs.js
@@ -0,0 +1,249 @@
+/*
+Copyright (C) 2014 spin83
+
+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, visit https://www.gnu.org/licenses/.
+*/
+
+const Lang = imports.lang;
+
+const GObject = imports.gi.GObject;
+const Gdk = imports.gi.Gdk;
+const Gtk = imports.gi.Gtk;
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+
+const Gettext = imports.gettext.domain('multi-monitors-add-on');
+const _ = Gettext.gettext;
+
+const ExtensionUtils = imports.misc.extensionUtils;
+const MultiMonitors = ExtensionUtils.getCurrentExtension();
+const Convenience = MultiMonitors.imports.convenience;
+
+const SHOW_INDICATOR_ID = 'show-indicator';
+const SHOW_PANEL_ID = 'show-panel';
+const SHOW_THUMBNAILS_SLIDER_ID = 'show-thumbnails-slider';
+const SHOW_ACTIVITIES_ID = 'show-activities';
+const SHOW_APP_MENU_ID = 'show-app-menu';
+const SHOW_DATE_TIME_ID = 'show-date-time';
+const THUMBNAILS_ON_LEFT_SIDE_ID = 'thumbnails-on-left-side';
+const AVAILABLE_INDICATORS_ID = 'available-indicators';
+const TRANSFER_INDICATORS_ID = 'transfer-indicators';
+
+const Columns = {
+ INDICATOR_NAME: 0,
+ MONITOR_NUMBER: 1
+};
+
+
+const MultiMonitorsPrefsWidget = new GObject.Class({
+ Name: 'MultiMonitorsPrefsWidget',
+ Extends: Gtk.Grid,
+
+ _init(params) {
+ this.parent(params);
+
+ this.set_orientation(Gtk.Orientation.VERTICAL);
+
+ this._settings = Convenience.getSettings();
+
+ this._screen = Gdk.Screen.get_default();
+
+ this._addBooleanSwitch(_('Show Multi Monitors indicator on Top Panel.'), SHOW_INDICATOR_ID);
+ this._addBooleanSwitch(_('Show Panel on additional monitors.'), SHOW_PANEL_ID);
+ this._addBooleanSwitch(_('Show Thumbnails-Slider on additional monitors.'), SHOW_THUMBNAILS_SLIDER_ID);
+ this._addBooleanSwitch(_('Show Activities-Button on additional monitors.'), SHOW_ACTIVITIES_ID);
+ this._addBooleanSwitch(_('Show AppMenu-Button on additional monitors.'), SHOW_APP_MENU_ID);
+ this._addBooleanSwitch(_('Show DateTime-Button on additional monitors.'), SHOW_DATE_TIME_ID);
+ this._addBooleanSwitch(_('Show Thumbnails-Slider on left side of additional monitors.'), THUMBNAILS_ON_LEFT_SIDE_ID);
+
+ this._store = new Gtk.ListStore();
+ this._store.set_column_types([GObject.TYPE_STRING, GObject.TYPE_INT]);
+
+ this._treeView = new Gtk.TreeView({ model: this._store, hexpand: true, vexpand: true });
+ this._treeView.get_selection().set_mode(Gtk.SelectionMode.SINGLE);
+
+ let appColumn = new Gtk.TreeViewColumn({ expand: true, sort_column_id: Columns.INDICATOR_NAME,
+ title: _("A list of indicators for transfer to additional monitors.") });
+
+ let nameRenderer = new Gtk.CellRendererText;
+ appColumn.pack_start(nameRenderer, true);
+ appColumn.add_attribute(nameRenderer, "text", Columns.INDICATOR_NAME);
+
+ nameRenderer = new Gtk.CellRendererText;
+ appColumn.pack_start(nameRenderer, true);
+ appColumn.add_attribute(nameRenderer, "text", Columns.MONITOR_NUMBER);
+
+ this._treeView.append_column(appColumn);
+ this.add(this._treeView);
+
+ let toolbar = new Gtk.Toolbar();
+ toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR);
+
+ this._settings.connect('changed::'+TRANSFER_INDICATORS_ID, Lang.bind(this, this._updateIndicators));
+ this._updateIndicators();
+
+ let addTButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_ADD });
+ addTButton.connect('clicked', Lang.bind(this, this._addIndicator));
+ toolbar.add(addTButton);
+
+ let removeTButton = new Gtk.ToolButton({ stock_id: Gtk.STOCK_REMOVE });
+ removeTButton.connect('clicked', Lang.bind(this, this._removeIndicator));
+ toolbar.add(removeTButton);
+
+ this.add(toolbar);
+
+ },
+
+ _updateIndicators() {
+ this._store.clear();
+
+ let transfers = this._settings.get_value(TRANSFER_INDICATORS_ID).deep_unpack();
+
+ for(let indicator in transfers) {
+ if(transfers.hasOwnProperty(indicator)){
+ let monitor = transfers[indicator];
+ let iter = this._store.append();
+ this._store.set(iter, [Columns.INDICATOR_NAME, Columns.MONITOR_NUMBER], [indicator, monitor]);
+ }
+ }
+ },
+
+ _addIndicator() {
+
+ let dialog = new Gtk.Dialog({ title: _("Select indicator"),
+ transient_for: this.get_toplevel(), modal: true });
+ dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL);
+ dialog.add_button(_("Add"), Gtk.ResponseType.OK);
+ dialog.set_default_response(Gtk.ResponseType.OK);
+
+ let grid = new Gtk.Grid({ column_spacing: 10, row_spacing: 15, margin: 10 });
+
+ grid.set_orientation(Gtk.Orientation.VERTICAL);
+
+ dialog._store = new Gtk.ListStore();
+ dialog._store.set_column_types([GObject.TYPE_STRING]);
+
+ dialog._treeView = new Gtk.TreeView({ model: dialog._store, hexpand: true, vexpand: true });
+ dialog._treeView.get_selection().set_mode(Gtk.SelectionMode.SINGLE);
+
+ let appColumn = new Gtk.TreeViewColumn({ expand: true, sort_column_id: Columns.INDICATOR_NAME,
+ title: _("Indicators on Top Panel") });
+
+ let nameRenderer = new Gtk.CellRendererText;
+ appColumn.pack_start(nameRenderer, true);
+ appColumn.add_attribute(nameRenderer, "text", Columns.INDICATOR_NAME);
+
+ dialog._treeView.append_column(appColumn);
+
+ let availableIndicators = () => {
+ let transfers = this._settings.get_value(TRANSFER_INDICATORS_ID).unpack();
+ dialog._store.clear();
+ this._settings.get_strv(AVAILABLE_INDICATORS_ID).forEach((indicator) => {
+ if(!transfers.hasOwnProperty(indicator)){
+ let iter = dialog._store.append();
+ dialog._store.set(iter, [Columns.INDICATOR_NAME], [indicator]);
+ }
+ });
+ };
+
+ let availableIndicatorsId = this._settings.connect('changed::'+AVAILABLE_INDICATORS_ID,
+ availableIndicators);
+ let transferIndicatorsId = this._settings.connect('changed::'+TRANSFER_INDICATORS_ID,
+ availableIndicators);
+
+ availableIndicators.apply(this);
+// grid.attach(dialog._treeView, 0, 0, 2, 1);
+ grid.add(dialog._treeView);
+
+ let gHBox = new Gtk.HBox({margin: 10, spacing: 20, hexpand: true});
+ let gLabel = new Gtk.Label({label: _('Monitor index:'), halign: Gtk.Align.START});
+ gHBox.add(gLabel);
+ dialog._adjustment = new Gtk.Adjustment({lower: 0.0, upper: 0.0, step_increment:1.0});
+ let spinButton = new Gtk.SpinButton({halign: Gtk.Align.END, adjustment: dialog._adjustment, numeric: 1});
+ gHBox.add(spinButton);
+
+ let monitorsChanged = () => {
+ let n_monitors = this._screen.get_n_monitors() -1;
+ dialog._adjustment.set_upper(n_monitors)
+ dialog._adjustment.set_value(n_monitors);
+ };
+
+ let monitorsChangedId = this._screen.connect('monitors-changed', monitorsChanged);
+
+ monitorsChanged.apply(this);
+ grid.add(gHBox);
+
+ dialog.get_content_area().add(grid);
+
+ dialog.connect('response', (dialog, id) => {
+ this._screen.disconnect(monitorsChangedId);
+ this._settings.disconnect(availableIndicatorsId);
+ this._settings.disconnect(transferIndicatorsId);
+ if (id != Gtk.ResponseType.OK) {
+ dialog.destroy();
+ return;
+ }
+
+ let [any, model, iter] = dialog._treeView.get_selection().get_selected();
+ if (any) {
+ let indicator = model.get_value(iter, Columns.INDICATOR_NAME);
+
+ let transfers = this._settings.get_value(TRANSFER_INDICATORS_ID).deep_unpack();
+ if(!transfers.hasOwnProperty(indicator)){
+ transfers[indicator] = dialog._adjustment.get_value();
+ this._settings.set_value(TRANSFER_INDICATORS_ID, new GLib.Variant('a{si}', transfers));
+ }
+ }
+
+ dialog.destroy();
+ });
+
+ dialog.show_all();
+ },
+
+ _removeIndicator() {
+ let [any, model, iter] = this._treeView.get_selection().get_selected();
+ if (any) {
+ let indicator = model.get_value(iter, Columns.INDICATOR_NAME);
+
+ let transfers = this._settings.get_value(TRANSFER_INDICATORS_ID).deep_unpack();
+ if(transfers.hasOwnProperty(indicator)){
+ delete transfers[indicator];
+ this._settings.set_value(TRANSFER_INDICATORS_ID, new GLib.Variant('a{si}', transfers));
+ }
+ }
+ },
+
+ _addBooleanSwitch(label, schema_id) {
+ let gHBox = new Gtk.HBox({margin: 10, spacing: 20, hexpand: true});
+ let gLabel = new Gtk.Label({label: _(label), halign: Gtk.Align.START});
+ gHBox.add(gLabel);
+ let gSwitch = new Gtk.Switch({halign: Gtk.Align.END});
+ gHBox.add(gSwitch);
+ this.add(gHBox);
+
+ this._settings.bind(schema_id, gSwitch, 'active', Gio.SettingsBindFlags.DEFAULT);
+ }
+});
+
+function init() {
+ Convenience.initTranslations();
+}
+
+function buildPrefsWidget() {
+ let widget = new MultiMonitorsPrefsWidget();
+ widget.show_all();
+
+ return widget;
+}
diff --git a/multi-monitors-add-on@spin83/schemas/gschemas.compiled b/multi-monitors-add-on@spin83/schemas/gschemas.compiled
new file mode 100644
index 0000000..2a49abb
--- /dev/null
+++ b/multi-monitors-add-on@spin83/schemas/gschemas.compiled
Binary files differ
diff --git a/multi-monitors-add-on@spin83/schemas/org.gnome.shell.extensions.multi-monitors-add-on.gschema.xml b/multi-monitors-add-on@spin83/schemas/org.gnome.shell.extensions.multi-monitors-add-on.gschema.xml
new file mode 100644
index 0000000..99726b3
--- /dev/null
+++ b/multi-monitors-add-on@spin83/schemas/org.gnome.shell.extensions.multi-monitors-add-on.gschema.xml
@@ -0,0 +1,59 @@
+<schemalist gettext-domain="gnome-shell-extensions">
+ <schema id="org.gnome.shell.extensions.multi-monitors-add-on" path="/org/gnome/shell/extensions/multi-monitors-add-on/">
+
+ <key name="show-indicator" type="b">
+ <default>true</default>
+ <summary>Show Multi Monitors indicator on Top Panel.</summary>
+ <description>Add or remove Multi Monitors indicator from Top Panel.</description>
+ </key>
+
+ <key name="show-panel" type="b">
+ <default>true</default>
+ <summary>Show Panel on additional monitors.</summary>
+ <description>Add or remove Panel from additional monitors.</description>
+ </key>
+
+ <key name="show-thumbnails-slider" type="b">
+ <default>true</default>
+ <summary>Show Thumbnails-Slider on additional monitors.</summary>
+ <description>Add or remove Thumbnails-Slider from additional monitors.</description>
+ </key>
+
+ <key name="show-activities" type="b">
+ <default>true</default>
+ <summary>Show Activities-Button on additional monitors.</summary>
+ <description>Change visibility of Activities-Button on additional monitors.</description>
+ </key>
+
+ <key name="show-app-menu" type="b">
+ <default>true</default>
+ <summary>Show AppMenu-Button on additional monitors.</summary>
+ <description>Change visibility of AppMenu-Button on additional monitors.</description>
+ </key>
+
+ <key name="show-date-time" type="b">
+ <default>true</default>
+ <summary>Show DateTime-Button on additional monitors.</summary>
+ <description>Change visibility of DateTime-Button on additional monitors.</description>
+ </key>
+
+ <key name="thumbnails-on-left-side" type="b">
+ <default>true</default>
+ <summary>Show Thumbnails-Slider on left side of additional monitors.</summary>
+ <description>Toggle position of Thumbnails-Slider from right to left on additional monitors.</description>
+ </key>
+
+ <key name="available-indicators" type="as">
+ <default>[]</default>
+ <summary>A list of available indicators.</summary>
+ <description>A list of indicators that are available for transfer. For internal use only.</description>
+ </key>
+
+ <key name="transfer-indicators" type="a{si}">
+ <default>{}</default>
+ <summary>A list of indicators for transfer.</summary>
+ <description>A list of indicators selected for transfer to additional Panel.</description>
+ </key>
+
+ </schema>
+</schemalist> \ No newline at end of file
diff --git a/multi-monitors-add-on@spin83/stylesheet.css b/multi-monitors-add-on@spin83/stylesheet.css
new file mode 100644
index 0000000..b1d20c5
--- /dev/null
+++ b/multi-monitors-add-on@spin83/stylesheet.css
@@ -0,0 +1,29 @@
+
+.helloworld-label {
+ font-size: 72px;
+ font-weight: bold;
+ color: #ffffff;
+ background-color: rgba(0,0,0,0.5);
+ border-radius: 5px;
+ padding: .5em;
+}
+
+.multimonitor-spacer {
+ height: 4em;
+}
+
+.multimonitor-status-indicators-box {
+ spacing: 0px;
+}
+
+.multimonitor-status-icon {
+ padding: 0 2px;
+}
+
+.workspace-thumbnails-left {
+ border-radius: 0 9px 9px 0;
+}
+
+.workspace-thumbnails-left:rtl {
+ border-radius: 9px 0 0 9px;
+}