Multi Monitors Add-On

This commit is contained in:
spin83
2014-12-19 01:46:57 +01:00
parent 95b8d8410d
commit cafb69b1d4
14 changed files with 2726 additions and 1 deletions

11
.project Normal file
View File

@@ -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>

View File

@@ -1,2 +1,22 @@
multi-monitors-add-on
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.
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 COPYING file for details.

View File

@@ -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 });
}

View File

@@ -0,0 +1,262 @@
/*
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 Main = imports.ui.main;
const Panel = imports.ui.panel;
const ExtensionUtils = imports.misc.extensionUtils;
const MultiMonitors = ExtensionUtils.getCurrentExtension();
const Convenience = MultiMonitors.imports.convenience;
const MMPanel = MultiMonitors.imports.mmpanel;
const MMOverview = MultiMonitors.imports.mmoverview;
const MMIndicator = MultiMonitors.imports.indicator;
const OVERRIDE_SCHEMA = 'org.gnome.shell.overrides';
const WORKSPACES_ONLY_ON_PRIMARY_ID = 'workspaces-only-on-primary';
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 THUMBNAILS_ON_LEFT_SIDE_ID = 'thumbnails-on-left-side';
const MultiMonitorsAddOn = new Lang.Class({
Name: 'MultiMonitorsAddOn',
_init: function() {
this._settings = Convenience.getSettings();
this._ov_settings = new Gio.Settings({ schema: OVERRIDE_SCHEMA });
this.mmIndicator = null;
Main.mmOverview = null;
Main.mmPanel = null;
this.panelBox = null;
this.mmappMenu = false;
this._mmMonitors = 0;
},
_changeMainPanelAppMenuButton: function(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);
},
_showIndicator: function() {
if(this._settings.get_boolean(SHOW_INDICATOR_ID)) {
if(!this.mmIndicator) {
this.mmIndicator = Main.panel.addToStatusArea('MultiMonitorsAddOn', new MMIndicator.MultiMonitorsIndicator());
}
}
else {
this._hideIndicator();
}
},
_hideIndicator: function() {
if(this.mmIndicator) {
this.mmIndicator.destroy();
this.mmIndicator = null;
}
},
_showPanel: function() {
if(this._settings.get_boolean(SHOW_PANEL_ID)){
Main.mmPanel = [];
this.panelBox = [];
for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
let monitor = Main.layoutManager.monitors[i];
if(i != Main.layoutManager.primaryIndex) {
let panel = new MMPanel.MultiMonitorsPanel(i);
this.panelBox[i] = new St.BoxLayout({ name: 'panelBox'+i, vertical: true });
Main.layoutManager.addChrome(this.panelBox[i], { affectsStruts: true, trackFullscreen: true });
this.panelBox[i].set_position(monitor.x, monitor.y);
// this.panelBox.connect('allocation-changed', Lang.bind(this, this._panelBoxChanged));
this.panelBox[i].add(panel.actor);
Main.mmPanel[i] = panel;
}
}
this.statusIndicatorsController = new MMPanel.StatusIndicatorsController();
this._showAppMenuId = this._settings.connect('changed::'+SHOW_APP_MENU_ID, Lang.bind(this, this._showAppMenu));
this._showAppMenu();
if(Main.mmOverview){
for (let i = 0; i < Main.mmOverview.length; i++) {
if(Main.mmOverview[i])
Main.mmOverview[i].addPanelGhost();
}
}
}
else{
this._hidePanel();
}
},
_showAppMenu: function(){
if(this._settings.get_boolean(SHOW_APP_MENU_ID)){
this._changeMainPanelAppMenuButton(MMPanel.MultiMonitorsAppMenuButton);
this.mmappMenu = true;
}
else{
if(this.mmappMenu){
this._changeMainPanelAppMenuButton(Panel.AppMenuButton);
this.mmappMenu = false;
}
}
},
_hidePanel: function() {
if(Main.mmPanel){
this._settings.disconnect(this._showAppMenuId);
this.statusIndicatorsController.destroy();
this.statusIndicatorsController = null;
for (let i = 0; i < Main.mmPanel.length; i++) {
if(Main.mmPanel[i])
this.panelBox[i].destroy();
}
Main.mmPanel = null;
this.panelBox = null;
if(this.mmappMenu){
this._changeMainPanelAppMenuButton(Panel.AppMenuButton);
this.mmappMenu = false;
}
if(Main.mmOverview){
for (let i = 0; i < Main.mmOverview.length; i++) {
if(Main.mmOverview[i])
Main.mmOverview[i].removePanelGhost();
}
}
}
},
_showThumbnailsSlider: function() {
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);
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);
}
}
}
else{
this._hideThumbnailsSlider();
}
},
_hideThumbnailsSlider: function() {
if(Main.mmOverview){
for (let i = 0; i < Main.mmOverview.length; i++) {
if(Main.mmOverview[i])
Main.mmOverview[i].destroy();
}
Main.mmOverview = null;
}
},
_relayout: function() {
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._hidePanel();
this._showPanel();
this._showThumbnailsSlider();
}
},
_switchOffThumbnails: function() {
if(this._ov_settings.get_boolean(WORKSPACES_ONLY_ON_PRIMARY_ID))
this._settings.set_boolean(SHOW_THUMBNAILS_SLIDER_ID, false);
},
enable: function() {
global.log("Enable Multi Monitors Add-On ...")
this._switchOffThumbnailsId = this._ov_settings.connect('changed::'+WORKSPACES_ONLY_ON_PRIMARY_ID,
Lang.bind(this, this._switchOffThumbnails));
this._showIndicator();
this._relayoutId = Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._relayout));
this._showIndicatorId = this._settings.connect('changed::'+SHOW_INDICATOR_ID, Lang.bind(this, this._showIndicator));
this._showPanelId = this._settings.connect('changed::'+SHOW_PANEL_ID, Lang.bind(this, this._showPanel));
this._showThumbnailsSliderId = this._settings.connect('changed::'+SHOW_THUMBNAILS_SLIDER_ID, Lang.bind(this, this._showThumbnailsSlider));
this._relayout();
},
disable: function() {
Main.layoutManager.disconnect(this._relayoutId);
this._settings.disconnect(this._showPanelId);
this._settings.disconnect(this._showThumbnailsSliderId);
this._hideThumbnailsSlider();
this._hidePanel();
this._mmMonitors = 0;
this._hideIndicator();
this._ov_settings.disconnect(this._switchOffThumbnailsId);
}
});
function init(extensionMeta) {
let theme = imports.gi.Gtk.IconTheme.get_default();
theme.append_search_path(extensionMeta.path + "/icons");
return new MultiMonitorsAddOn();
}

View File

@@ -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>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -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>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,132 @@
/*
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 PopupMenu = imports.ui.popupMenu;
const MultiMonitorsStatusIcon = new Lang.Class({
Name: 'MultiMonitorsStatusIcon',
Extends: St.BoxLayout,
_init: function() {
this.parent({ style_class: 'panel-status-menu-box' });
this._leftRightIcon = true;
this._viewMonitorsId = Main.layoutManager.connect('monitors-changed', Lang.bind(this, this._viewMonitors));
this.connect('destroy', Lang.bind(this, this._onDestroy));
this._viewMonitors();
},
_onDestroy: function(actor) {
Main.layoutManager.disconnect(this._viewMonitorsId);
},
_viewMonitors: function() {
let monitors = this.get_children();
let monitorChange = Main.layoutManager.monitors.length - monitors.length;
if(monitorChange>0){
for(let idx = 0; idx<monitorChange; idx++){
if(this._leftRightIcon){
this.add_child(new St.Icon({
icon_name: 'multi-monitors-l-symbolic',
style_class: 'system-status-icon'
}));
}
else{
this.add_child(new St.Icon({
icon_name: 'multi-monitors-r-symbolic',
style_class: 'system-status-icon'
}));
}
this._leftRightIcon = !this._leftRightIcon;
}
}
else if(monitorChange<0){
monitorChange = -monitorChange;
for(let idx = 0; idx<monitorChange; idx++){
this.remove_child(this.get_last_child());
this._leftRightIcon = !this._leftRightIcon;
}
}
}
});
const MultiMonitorsIndicator = new Lang.Class({
Name: 'MultiMonitorsIndicator',
Extends: PanelMenu.Button,
_init: function() {
this.parent(0.0, "MultiMonitorsAddOn");
this.text = null;
this._mmStatusIcon = new MultiMonitorsStatusIcon();
this.actor.add_child(this._mmStatusIcon);
this.menu.addAction('Preferences', Lang.bind(this, this._onPreferences));
this.menu.addAction('Test', Lang.bind(this, this._onTest));
},
_onPreferences: function()
{
Util.spawn(["gnome-shell-extension-prefs", "multi-monitors-add-on@spin83"]);
},
_onTest: function()
{
global.log('Multi Monitors Add-On');
this._showHello();
},
_hideHello: function() {
Main.uiGroup.remove_actor(this.text);
this.text = null;
},
_showHello: function() {
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: Lang.bind(this, this._hideHello) });
},
});

View File

@@ -0,0 +1,8 @@
{
"shell-version": ["3.10.4"],
"uuid": "multi-monitors-add-on@spin83",
"name": "Multi Monitors Add-On",
"settings-schema": "org.gnome.shell.extensions.multi-monitors-add-on",
"gettext-domain": "gnome-shell-extensions-multi-monitors-add-on",
"description": "Add multiple monitors overview and panel for gnome-shell."
}

View File

@@ -0,0 +1,662 @@
/*
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 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 : function(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', Lang.bind(this, this._onDestroy));
// this._createBackground();
this._bgManager = new Background.BackgroundManager({ monitorIndex: this.monitorIndex,
container: this._contents,
effects: Meta.BackgroundEffects.NONE });
let monitor = Main.layoutManager.monitors[this.monitorIndex];
this.setPorthole(monitor.x, monitor.y, monitor.width, monitor.height);
let windows = global.get_window_actors().filter(Lang.bind(this, function(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 = [];
this._allWindows = [];
this._minimizedChangedIds = [];
for (let i = 0; i < windows.length; i++) {
let minimizedChangedId =
windows[i].meta_window.connect('notify::minimized',
Lang.bind(this,
this._updateMinimized));
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',
Lang.bind(this, this._windowAdded));
this._windowRemovedId = this.metaWorkspace.connect('window-removed',
Lang.bind(this, this._windowRemoved));
this._windowEnteredMonitorId = global.screen.connect('window-entered-monitor',
Lang.bind(this, this._windowEnteredMonitor));
this._windowLeftMonitorId = global.screen.connect('window-left-monitor',
Lang.bind(this, this._windowLeftMonitor));
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: function(monitorIndex) {
this._monitorIndex = monitorIndex;
this.actor = new Shell.GenericContainer({ reactive: true,
style_class: 'workspace-thumbnails',
request_mode: Clutter.RequestMode.WIDTH_FOR_HEIGHT });
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this.actor.connect('allocate', Lang.bind(this, this._allocate));
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
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.actor.connect('button-press-event', function() { return true; });
this.actor.connect('button-release-event', Lang.bind(this, this._onButtonRelease));
this._showingId = Main.overview.connect('showing', Lang.bind(this, this._createThumbnails));
this._hiddenId = Main.overview.connect('hidden', Lang.bind(this, this._destroyThumbnails));
this._itemDragBeginId = Main.overview.connect('item-drag-begin', Lang.bind(this, this._onDragBegin));
this._itemDragEndId = Main.overview.connect('item-drag-end', Lang.bind(this, this._onDragEnd));
this._itemDragCancelledId = Main.overview.connect('item-drag-cancelled', Lang.bind(this, this._onDragCancelled));
this._windowDragBeginId = Main.overview.connect('window-drag-begin', Lang.bind(this, this._onDragBegin));
this._windowDragEndId = Main.overview.connect('window-drag-end', Lang.bind(this, this._onDragEnd));
this._windowDragCancelledId = Main.overview.connect('window-drag-cancelled', Lang.bind(this, this._onDragCancelled));
this._settings = new Gio.Settings({ schema: WorkspaceThumbnail.OVERRIDE_SCHEMA });
this._changedDynamicWorkspacesId = this._settings.connect('changed::dynamic-workspaces',
Lang.bind(this, this._updateSwitcherVisibility));
},
_onDestroy: function(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);
//TODO drag end ??
Tweener.removeTweens(actor);
this.actor._delegate = null;
},
_createThumbnails: function() {
this._switchWorkspaceNotifyId =
global.window_manager.connect('switch-workspace',
Lang.bind(this, this._activeWorkspaceChanged));
this._nWorkspacesNotifyId =
global.screen.connect('notify::n-workspaces',
Lang.bind(this, this._workspacesChanged));
this._syncStackingId =
Main.overview.connect('windows-restacked',
Lang.bind(this, this._syncStacking));
this._targetScale = 0;
this._scale = 0;
this._pendingScaleUpdate = false;
this._stateUpdateQueued = false;
this._stateCounts = {};
for (let key in WorkspaceThumbnail.ThumbnailState)
this._stateCounts[WorkspaceThumbnail.ThumbnailState[key]] = 0;
// The "porthole" is the portion of the screen that we show in the workspaces
this._porthole = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex);
this.addThumbnails(0, global.screen.n_workspaces);
this._updateSwitcherVisibility();
},
addThumbnails: function(start, count) {
for (let k = start; k < start + count; k++) {
let metaWorkspace = global.screen.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;
}
});
const MultiMonitorsSlidingControl = new Lang.Class({
Name: 'MultiMonitorsSlidingControl',
Extends: OverviewControls.SlidingControl,
_init: function(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.actor = new St.Widget({ layout_manager: this.layout,
style_class: 'overview-controls',
clip_to_allocation: true });
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this._showingId = Main.overview.connect('showing', Lang.bind(this, this._onOverviewShowing));
this._itemDragBeginId = Main.overview.connect('item-drag-begin', Lang.bind(this, this._onDragBegin));
this._itemDragEndId = Main.overview.connect('item-drag-end', Lang.bind(this, this._onDragEnd));
this._itemDragCancelledId = Main.overview.connect('item-drag-cancelled', Lang.bind(this, this._onDragEnd));
this._windowDragBeginId = Main.overview.connect('window-drag-begin', Lang.bind(this, this._onWindowDragBegin));
this._windowDragCancelledId = Main.overview.connect('window-drag-cancelled', Lang.bind(this, this._onWindowDragEnd));
this._windowDragEndId = Main.overview.connect('window-drag-end', Lang.bind(this, this._onWindowDragEnd));
},
_onDestroy: function(actor) {
Main.overview.disconnect(this._showingId);
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);
},
});
const MultiMonitorsThumbnailsSlider = new Lang.Class({
Name: 'MultiMonitorsThumbnailsSlider',
Extends: MultiMonitorsSlidingControl,
_init: function(thumbnailsBox) {
this.parent({ slideDirection: OverviewControls.SlideDirection.RIGHT });
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);
this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', Lang.bind(this, this.updateSlide));
this._hidingId = Main.overview.connect('hiding', Lang.bind(this, this.slideOut));
this.actor.connect('notify::hover', Lang.bind(this, this.updateSlide));
this._thumbnailsBox.actor.bind_property('visible', this.actor, 'visible', GObject.BindingFlags.SYNC_CREATE);
},
_onDestroy: function() {
Main.layoutManager.disconnect(this._monitorsChangedId);
Main.overview.disconnect(this._hidingId);
this.parent();
},
_getAlwaysZoomOut: function() {
// Always show the pager when hover, during a drag, or if workspaces are
// actually used, e.g. there are windows on more than one
let alwaysZoomOut = this.actor.hover || this.inDrag || !Meta.prefs_get_dynamic_workspaces() || global.screen.n_workspaces > 2;
if (!alwaysZoomOut) {
let monitors = Main.layoutManager.monitors;
let primary = Main.layoutManager.primaryMonitor;
/* Look for any monitor to the right of the primary, if there is
* one, we always keep zoom out, otherwise its hard to reach
* the thumbnail area without passing into the next monitor. */
for (let i = 0; i < monitors.length; i++) {
if (monitors[i].x >= primary.x + primary.width) {
alwaysZoomOut = true;
break;
}
}
}
return alwaysZoomOut;
},
_onOverviewShowing: function() {
this.visible = true;
this.layout.slideX = this.getSlide();
this.actor.translation_x = this._getTranslation();
this.slideIn();
},
getNonExpandedWidth: function() {
let child = this.actor.get_first_child();
return child.get_theme_node().get_length('visible-width');
},
getSlide: function() {
if (!this.visible)
return 0;
let alwaysZoomOut = this._getAlwaysZoomOut();
if (alwaysZoomOut)
return 1;
let child = this.actor.get_first_child();
let preferredHeight = child.get_preferred_height(-1)[1];
let expandedWidth = child.get_preferred_width(preferredHeight)[1];
return this.getNonExpandedWidth() / expandedWidth;
},
getVisibleWidth: function() {
let alwaysZoomOut = this._getAlwaysZoomOut();
if (alwaysZoomOut)
return this.parent();
else
return this.getNonExpandedWidth();
}
});
const MultiMonitorsControlsManager = new Lang.Class({
Name: 'MultiMonitorsControlsManager',
_init: function(index) {
this._monitorIndex = index;
this._workspacesViews = null;
this._thumbnailsBox = new MultiMonitorsThumbnailsBox(this._monitorIndex);
this._thumbnailsSlider = new MultiMonitorsThumbnailsSlider(this._thumbnailsBox);
let layout = new OverviewControls.ControlsLayout();
this.actor = new St.Widget({ layout_manager: layout,
reactive: true,
x_expand: true, y_expand: true,
clip_to_allocation: true });
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
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', Lang.bind(this, this._updateWorkspacesGeometry));
this._settings = Convenience.getSettings();
this._thumbnailsOnLeftSideId = this._settings.connect('changed::'+THUMBNAILS_ON_LEFT_SIDE_ID,
Lang.bind(this, this._thumbnailsOnLeftSide));
this._thumbnailsOnLeftSide();
this._pageChangedId = Main.overview.viewSelector.connect('page-changed', Lang.bind(this, this._setVisibility));
this._pageEmptyId = Main.overview.viewSelector.connect('page-empty', Lang.bind(this, this._onPageEmpty));
//
// Main.overview.connect('item-drag-begin', Lang.bind(this,
// function() {
// let activePage = this.viewSelector.getActivePage();
// if (activePage != ViewSelector.ViewPage.WINDOWS)
// this.viewSelector.fadeHalf();
// }));
// Main.overview.connect('item-drag-end', Lang.bind(this,
// function() {
// this.viewSelector.fadeIn();
// }));
// Main.overview.connect('item-drag-cancelled', Lang.bind(this,
// function() {
// this.viewSelector.fadeIn();
// }));
},
_onDestroy: function() {
Main.overview.viewSelector.disconnect(this._pageChangedId);
Main.overview.viewSelector.disconnect(this._pageEmptyId);
this._settings.disconnect(this._thumbnailsOnLeftSideId);
},
_thumbnailsOnLeftSide: function() {
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-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-left');
this._thumbnailsBox.actor.set_style_class_name('workspace-thumbnails');
this._group.set_child_above_sibling(this._thumbnailsSlider.actor, last);
}
}
},
_updateWorkspacesGeometry: function() {
let [x, y] = this.actor.get_transformed_position();
let [width, height] = this.actor.get_transformed_size();
let geometry = { x: x, y: y, width: width, height: 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;
}
// let [x, y] = this._viewActor.get_transformed_position();
// let width = this._viewActor.allocation.x2 - this._viewActor.allocation.x1;
// let height = this._viewActor.allocation.y2 - this._viewActor.allocation.y1;
// let geometry = { x: x, y: y, width: width, height: height };
this.setWorkspacesFullGeometry(geometry);
},
_setVisibility: function() {
// 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,
{ opacity: opacity,
time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
transition: 'easeOutQuad'
});
},
_onPageEmpty: function() {
this._thumbnailsSlider.pageEmpty();
},
show: function() {
this._workspacesViews = Main.overview.viewSelector._workspacesDisplay._workspacesViews[this._monitorIndex];
},
zoomFromOverview: function() {
// this._workspacesViews.hide();
// this._workspacesDisplay.zoomFromOverview();
//
// if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows())
// Main.overview.fadeInDesktop();
},
setWorkspacesFullGeometry: function(geom) {
if(!this._workspacesViews)
return;
this._workspacesViews.setActualGeometry(geom);
},
hide: function() {
if(!this._workspacesViews.actor.visible){
this._workspacesViews.actor.opacity = 255;
this._workspacesViews.actor.visible = true;
}
this._workspacesViews = null;
}
});
const MultiMonitorsOverview = new Lang.Class({
Name: 'MultiMonitorsOverview',
_init: function(index) {
this.monitorIndex = index;
this._settings = Convenience.getSettings();
let monitor = Main.layoutManager.monitors[this.monitorIndex];
let layout = new Clutter.BinLayout();
this._stack = new Clutter.Actor({ layout_manager: layout });
this._stack.add_constraint(new LayoutManager.MonitorConstraint({ index: this.monitorIndex }));
this._stack.connect('destroy', Lang.bind(this, this._onDestroy));
Main.layoutManager.overviewGroup.add_child(this._stack);
this._overview = new St.BoxLayout({ name: 'overview'+this.monitorIndex,
accessible_name: _("Overview"+this.monitorIndex),
reactive: true,
vertical: true,
x_expand: true,
y_expand: true });
this._overview._delegate = this;
this._stack.add_actor(this._overview);
if(Main.mmPanel && Main.mmPanel[this.monitorIndex]){
this._panelGhost = new St.Bin({ child: new Clutter.Clone({ source: Main.mmPanel[this.monitorIndex].actor }),
reactive: false, opacity: 0 });
this._overview.add_actor(this._panelGhost);
}
else
this._panelGhost = null;
this._overview.add_actor(new St.Widget({style_class: 'multimonitor-spacer'}));
this._controls = new MultiMonitorsControlsManager(this.monitorIndex);
this._overview.add(this._controls.actor, { y_fill: true, expand: true });
this._showingId = Main.overview.connect('showing', Lang.bind(this, this._show));
this._hidingId = Main.overview.connect('hiding', Lang.bind(this, this._hide));
},
_onDestroy: function(actor) {
Main.overview.disconnect(this._showingId);
Main.overview.disconnect(this._hidingId);
Tweener.removeTweens(actor);
Main.layoutManager.overviewGroup.remove_child(this._stack);
this._overview._delegate = null;
},
addPanelGhost: function() {
if(!this._panelGhost) {
this._panelGhost = new St.Bin({ child: new Clutter.Clone({ source: Main.mmPanel[this.monitorIndex].actor }),
reactive: false, opacity: 0 });
this._overview.add_actor(this._panelGhost);
this._overview.set_child_at_index(this._panelGhost, 0);
}
},
removePanelGhost: function() {
if(this._panelGhost) {
this._overview.remove_actor(this._panelGhost);
this._panelGhost.destroy();
this._panelGhost = null;
}
},
_show: function() {
this._controls.show();
this._stack.opacity = 0;
Tweener.addTween(this._stack,
{ opacity: 255,
transition: 'easeOutQuad',
time: Overview.ANIMATION_TIME,
onComplete: Lang.bind(this, this._showDone),
onCompleteScope: this
});
},
_showDone: function() {
},
_hide: function() {
this._controls.zoomFromOverview();
Tweener.addTween(this._stack,
{ opacity: 0,
transition: 'easeOutQuad',
time: Overview.ANIMATION_TIME,
onComplete: Lang.bind(this, this._hideDone),
onCompleteScope: this
});
},
_hideDone: function() {
this._controls.hide();
},
destroy: function() {
this._stack.destroy();
}
});

View File

@@ -0,0 +1,413 @@
/*
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 Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const Panel = imports.ui.panel;
const PopupMenu = imports.ui.popupMenu;
const CtrlAltTab = imports.ui.ctrlAltTab;
const ExtensionSystem = imports.ui.extensionSystem;
const ExtensionUtils = imports.misc.extensionUtils;
const MultiMonitors = ExtensionUtils.getCurrentExtension();
const Convenience = MultiMonitors.imports.convenience;
const SHOW_ACTIVITIES_ID = 'show-activities';
const SHOW_APP_MENU_ID = 'show-app-menu';
const AVAILABLE_INDICATORS_ID = 'available-indicators';
const TRANSFER_INDICATORS_ID = 'transfer-indicators';
const StatusIndicatorsController = new Lang.Class({
Name: 'StatusIndicatorController',
_init: function() {
this._transfered_indicators = []; //{iname:, box:, monitor:}
this._settings = Convenience.getSettings();
this._updatedSessionId = Main.sessionMode.connect('updated', Lang.bind(this, this._updateSessionIndicators));
this._updateSessionIndicators();
this._extensionStateChangedId = ExtensionSystem.connect('extension-state-changed',
Lang.bind(this, this._extensionStateChanged));
this._transferIndicatorsId = this._settings.connect('changed::'+TRANSFER_INDICATORS_ID,
Lang.bind(this, this._transferIndicators));
},
destroy: function() {
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);
},
_transferIndicators: function() {
let boxs = ['_leftBox', '_centerBox', '_rightBox'];
let transfers = this._settings.get_value(TRANSFER_INDICATORS_ID).deep_unpack();
let transfer_back = this._transfered_indicators.filter(function(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];
boxs.forEach(Lang.bind(this, function(box) {
if(Main.panel[box].contains(indicator.container) && Main.mmPanel[monitor]) {
global.log('a '+box+ " > " + iname + " : "+ monitor);
this._transfered_indicators.push({iname:iname, box:box, monitor:monitor});
Main.panel[box].remove_child(indicator.container);
Main.mmPanel[monitor][box].insert_child_at_index(indicator.container, 0);
}
}));
}
}
},
_transferBack: function(transfer_back) {
transfer_back.forEach(Lang.bind(this, function(element) {
this._transfered_indicators.slice(this._transfered_indicators.indexOf(element));
if(Main.panel.statusArea[element.iname]) {
let indicator = Main.panel.statusArea[element.iname];
if(Main.mmPanel[element.monitor][element.box].contains(indicator.container)) {
global.log("r "+element.box+ " > " + element.iname + " : "+ element.monitor);
Main.mmPanel[element.monitor][element.box].remove_child(indicator.container);
Main.panel[element.box].insert_child_at_index(indicator.container, 0);
}
}
}));
},
_extensionStateChanged: function() {
this._findAvailableIndicators();
this._transferIndicators();
},
_updateSessionIndicators: function() {
let session_indicators = [];
session_indicators.push('MultiMonitorsAddOn');
let sessionPanel = Main.sessionMode.panel;
for (let sessionBox in sessionPanel){
sessionPanel[sessionBox].forEach(function(sesionIndicator){
session_indicators.push(sesionIndicator);
});
}
this._session_indicators = session_indicators;
this._available_indicators = [];
this._findAvailableIndicators();
this._transferIndicators();
},
_findAvailableIndicators: function() {
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);
}
}
});
const MultiMonitorsAppMenuButton = new Lang.Class({
Name: 'MultiMonitorsAppMenuButton',
Extends: Panel.AppMenuButton,
_init: function(panel){
if(!panel.monitorIndex)
this._monitorIndex = Main.layoutManager.primaryIndex;
else
this._monitorIndex = panel.monitorIndex;
this._actionOnWorkspaceGroupNotifyId = 0;
this._targetAppGroup = null;
this._lastFocusedWindow = null;
this.parent(panel);
this._windowEnteredMonitorId = global.screen.connect('window-entered-monitor',
Lang.bind(this, this._windowEnteredMonitor));
this._windowLeftMonitorId = global.screen.connect('window-left-monitor',
Lang.bind(this, this._windowLeftMonitor));
},
_windowEnteredMonitor : function(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 : function(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: function() {
if (this._actionOnWorkspaceGroupNotifyId) {
this._targetAppGroup.disconnect(this._actionOnWorkspaceGroupNotifyId);
this._actionOnWorkspaceGroupNotifyId = 0;
this._targetAppGroup = null;
}
let groupWindow = false;
let groupFocus = false;
let workspace = global.screen.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',
Lang.bind(this, this._sync));
// 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);
}
let screen = global.screen;
let windows = screen.get_display().get_tab_list(Meta.TabList.NORMAL_ALL, screen, 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: function() {
this.parent();
if (this._actionGroupNotifyId) {
this._targetApp.disconnect(this._actionGroupNotifyId);
this._actionGroupNotifyId = 0;
}
global.screen.disconnect(this._windowEnteredMonitorId);
global.screen.disconnect(this._windowLeftMonitorId);
}
});
const MULTI_MONITOR_PANEL_ITEM_IMPLEMENTATIONS = {
'activities': Panel.ActivitiesButton,
// 'aggregateMenu': Panel.AggregateMenu,
'appMenu': MultiMonitorsAppMenuButton,
// 'dateMenu': imports.ui.dateMenu.DateMenuButton,
// 'a11y': imports.ui.status.accessibility.ATIndicator,
// 'a11yGreeter': imports.ui.status.accessibility.ATGreeterIndicator,
// 'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
};
const MultiMonitorsPanel = new Lang.Class({
Name: 'MultiMonitorsPanel',
Extends: Panel.Panel,
_init : function(monitorIndex) {
this.monitorIndex = monitorIndex;
this.actor = new Shell.GenericContainer({ name: 'panel', reactive: true });
this.actor._delegate = this;
this._sessionStyle = null;
this.statusArea = {};
this.menuManager = new PopupMenu.PopupMenuManager(this, { keybindingMode: Shell.KeyBindingMode.TOPBAR_POPUP });
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', Lang.bind(this, this._getPreferredWidth));
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
this.actor.connect('allocate', Lang.bind(this, this._allocate));
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
this._showingId = Main.overview.connect('showing', Lang.bind(this, function () {
this.actor.add_style_pseudo_class('overview');
}));
this._hidingId = Main.overview.connect('hiding', Lang.bind(this, function () {
this.actor.remove_style_pseudo_class('overview');
}));
Main.ctrlAltTabManager.addGroup(this.actor, _("Top Bar "+this.monitorIndex), 'emblem-system-symbolic',
{ sortGroup: CtrlAltTab.SortGroup.TOP });
Main.sessionMode.connect('updated', Lang.bind(this, this._updatePanel));
this._updatePanel();
this._settings = Convenience.getSettings();
this._showActivitiesId = this._settings.connect('changed::'+SHOW_ACTIVITIES_ID,
Lang.bind(this, this._showActivities));
this._showActivities();
this._showAppMenuId = this._settings.connect('changed::'+SHOW_APP_MENU_ID,
Lang.bind(this, this._showAppMenu));
this._showAppMenu();
},
_onDestroy: function(actor) {
Main.overview.disconnect(this._showingId);
Main.overview.disconnect(this._hidingId);
this._settings.disconnect(this._showActivitiesId);
// Tweener.removeTweens(actor);
Main.ctrlAltTabManager.removeGroup(this.actor);
for(let name in this.statusArea){
if(this.statusArea.hasOwnProperty(name))
this.statusArea[name].destroy();
}
this.actor._delegate = null;
},
_showActivities: function() {
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;
}
},
_showAppMenu: function() {
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: function(actor, forHeight, alloc) {
alloc.min_size = -1;
alloc.natural_size = Main.layoutManager.monitors[this.monitorIndex].width;
},
_hideIndicators: function() {
for (let role in MULTI_MONITOR_PANEL_ITEM_IMPLEMENTATIONS) {
let indicator = this.statusArea[role];
if (!indicator)
continue;
if (indicator.menu)
indicator.menu.close();
indicator.container.hide();
}
},
_ensureIndicator: function(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;
}
});

View File

@@ -0,0 +1,247 @@
/*
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('gnome-shell-extensions-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 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: function(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 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);
let 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: function() {
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: function() {
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 = function() {
let transfers = this._settings.get_value(TRANSFER_INDICATORS_ID).unpack();
dialog._store.clear();
this._settings.get_strv(AVAILABLE_INDICATORS_ID).forEach(function(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,
Lang.bind(this, availableIndicators));
let transferIndicatorsId = this._settings.connect('changed::'+TRANSFER_INDICATORS_ID,
Lang.bind(this, 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 = function() {
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', Lang.bind(this, monitorsChanged));
monitorsChanged.apply(this);
grid.add(gHBox);
dialog.get_content_area().add(grid);
dialog.connect('response', Lang.bind(this, function(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: function() {
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: function(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;
}

Binary file not shown.

View File

@@ -0,0 +1,53 @@
<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="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>

View File

@@ -0,0 +1,39 @@
.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;
}
.panel-status-menu-box {
spacing: 0px;
}
.system-status-icon {
padding: 0 2px;
}
.workspace-thumbnails-left {
spacing: 11px;
visible-width: 32px; /* Amount visible before hovering */
border: 1px solid rgba(128, 128, 128, 0.4);
border-left: 0px;
border-radius: 0px 9px 9px 0px;
background-color: rgba(0, 0, 0, 0.5);
padding: 11px 11px 11px 7px;
}
.workspace-thumbnails-left:rtl {
border-left: 1px;
border-right: 0px;
border-radius: 9px 0px 0px 9px;
padding: 11px 7px 11px 11px;
}