From cafb69b1d47b2164b174a6ab7dde45611af9e711 Mon Sep 17 00:00:00 2001 From: spin83 Date: Fri, 19 Dec 2014 01:46:57 +0100 Subject: [PATCH] Multi Monitors Add-On --- .project | 11 + README.md | 22 +- multi-monitors-add-on@spin83/convenience.js | 93 +++ multi-monitors-add-on@spin83/extension.js | 262 +++++++ .../icons/multi-monitors-l-symbolic.svg | 392 +++++++++++ .../icons/multi-monitors-r-symbolic.svg | 393 +++++++++++ multi-monitors-add-on@spin83/indicator.js | 132 ++++ multi-monitors-add-on@spin83/metadata.json | 8 + multi-monitors-add-on@spin83/mmoverview.js | 662 ++++++++++++++++++ multi-monitors-add-on@spin83/mmpanel.js | 413 +++++++++++ multi-monitors-add-on@spin83/prefs.js | 247 +++++++ .../schemas/gschemas.compiled | Bin 0 -> 732 bytes ...tensions.multi-monitors-add-on.gschema.xml | 53 ++ multi-monitors-add-on@spin83/stylesheet.css | 39 ++ 14 files changed, 2726 insertions(+), 1 deletion(-) create mode 100644 .project create mode 100644 multi-monitors-add-on@spin83/convenience.js create mode 100644 multi-monitors-add-on@spin83/extension.js create mode 100644 multi-monitors-add-on@spin83/icons/multi-monitors-l-symbolic.svg create mode 100644 multi-monitors-add-on@spin83/icons/multi-monitors-r-symbolic.svg create mode 100644 multi-monitors-add-on@spin83/indicator.js create mode 100644 multi-monitors-add-on@spin83/metadata.json create mode 100644 multi-monitors-add-on@spin83/mmoverview.js create mode 100644 multi-monitors-add-on@spin83/mmpanel.js create mode 100644 multi-monitors-add-on@spin83/prefs.js create mode 100644 multi-monitors-add-on@spin83/schemas/gschemas.compiled create mode 100644 multi-monitors-add-on@spin83/schemas/org.gnome.shell.extensions.multi-monitors-add-on.gschema.xml create mode 100644 multi-monitors-add-on@spin83/stylesheet.css diff --git a/.project b/.project new file mode 100644 index 0000000..58f94f4 --- /dev/null +++ b/.project @@ -0,0 +1,11 @@ + + + multi-monitors-add-on + + + + + + + + diff --git a/README.md b/README.md index f465918..e0c6fba 100644 --- a/README.md +++ b/README.md @@ -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. \ No newline at end of file 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 + + 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..bed7de8 --- /dev/null +++ b/multi-monitors-add-on@spin83/extension.js @@ -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(); +} 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 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/multi-monitors-add-on@spin83/indicator.js b/multi-monitors-add-on@spin83/indicator.js new file mode 100644 index 0000000..77f5fe2 --- /dev/null +++ b/multi-monitors-add-on@spin83/indicator.js @@ -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 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(); + } + +}); \ No newline at end of file diff --git a/multi-monitors-add-on@spin83/mmpanel.js b/multi-monitors-add-on@spin83/mmpanel.js new file mode 100644 index 0000000..3c04e4b --- /dev/null +++ b/multi-monitors-add-on@spin83/mmpanel.js @@ -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; + } +}); \ No newline at end of file diff --git a/multi-monitors-add-on@spin83/prefs.js b/multi-monitors-add-on@spin83/prefs.js new file mode 100644 index 0000000..b22e143 --- /dev/null +++ b/multi-monitors-add-on@spin83/prefs.js @@ -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; +} \ No newline at end of file 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 0000000000000000000000000000000000000000..701835a4db3436b675c9e72a18bd7391bb28b579 GIT binary patch literal 732 zcmZuuJ4gdT5M9OiiKvC&+Uf-FF127|V~W(lMsSm?$->=UxZT8{#5#pqS_f<_Ep0?g zu?mQi!oo(+ z7WfiIYLT&ToF^NcV_onI5XEJ$tfEkOx*~$W6AdGjmXXq4SPP7F!$?UJ#oFbik{c4y79)1x*+SCIN~Y)TJI6%h_J;p^Kf{GC8CdSe?Cs-vH1Fsk;9X!NGrdke=_U9La1*%DANVFZ4ZnrIQ=k_&@5;sW55K!>s$uB5C~VQ zk}Pr>Eo?lT>or}84qRR|vM!Aj)*oR`Va=;@Q?XIZ$HIKOFyAiB|67><@50c%HkDdf zP$;aiK{pWl#?`VUD5K49>kb9!0JpI%_;+9BN(4!~iMi7IA|8H&!;iFVCQtZ%+Hihe Pt-7I5wd5T(Zz9 + + + + true + Show Multi Monitors indicator on Top Panel. + Add or remove Multi Monitors indicator from Top Panel. + + + + true + Show Panel on additional monitors. + Add or remove Panel from additional monitors. + + + + true + Show Thumbnails-Slider on additional monitors. + Add or remove Thumbnails-Slider from additional monitors. + + + + true + Show Activities-Button on additional monitors. + Change visibility of Activities-Button on additional monitors. + + + + true + Show AppMenu-Button on additional monitors. + Change visibility of AppMenu-Button on additional monitors. + + + + true + Show Thumbnails-Slider on left side of additional monitors. + Toggle position of Thumbnails-Slider from right to left on additional monitors. + + + + [] + A list of available indicators. + A list of indicators that are available for transfer. For internal use only. + + + + {} + A list of indicators for transfer. + A list of indicators selected for transfer to additional Panel. + + + + \ 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..731e8d0 --- /dev/null +++ b/multi-monitors-add-on@spin83/stylesheet.css @@ -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; +}