From a3f352d0c3cc4a84f9d3e89e5e012568b7ae18f3 Mon Sep 17 00:00:00 2001 From: Sylvain Pasche Date: Sun, 16 Nov 2014 16:03:39 +0100 Subject: [PATCH] window-list: Option to show the window list on all monitors A new setting "show-on-all-monitors" (false by default) is available to show window lists on all connected monitors. The Extension object monitors conditions that require the list of windows to be rebuilt. The WindowList and Button classes have a new "perMonitor" property that indicates they should handle windows on their own monitor only. https://bugzilla.gnome.org/show_bug.cgi?id=737486 --- extensions/window-list/extension.js | 176 ++++++++++++++---- ...hell.extensions.window-list.gschema.xml.in | 8 + extensions/window-list/prefs.js | 20 +- 3 files changed, 157 insertions(+), 47 deletions(-) diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js index 23ea3cb4..06777775 100644 --- a/extensions/window-list/extension.js +++ b/extensions/window-list/extension.js @@ -53,7 +53,7 @@ function _onMenuStateChanged(menu, isOpen) { let [x, y,] = global.get_pointer(); let actor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y); - if (Me.stateObj.windowListContains(actor)) + if (Me.stateObj.someWindowListContains(actor)) actor.sync_hover(); } @@ -190,7 +190,10 @@ const BaseButton = new Lang.Class({ Name: 'BaseButton', Abstract: true, - _init: function() { + _init: function(perMonitor, monitorIndex) { + this._perMonitor = perMonitor; + this._monitorIndex = monitorIndex; + this.actor = new St.Button({ style_class: 'window-button', x_fill: true, can_focus: true, @@ -209,6 +212,15 @@ const BaseButton = new Lang.Class({ this._switchWorkspaceId = global.window_manager.connect('switch-workspace', Lang.bind(this, this._updateVisibility)); + + if (this._perMonitor) { + this._windowEnteredMonitorId = + global.screen.connect('window-entered-monitor', + Lang.bind(this, this._windowEnteredOrLeftMonitor)); + this._windowLeftMonitorId = + global.screen.connect('window-left-monitor', + Lang.bind(this, this._windowEnteredOrLeftMonitor)); + } }, get active() { @@ -247,10 +259,16 @@ const BaseButton = new Lang.Class({ this.actor.remove_style_class_name('focused'); }, + _windowEnteredOrLeftMonitor: function(metaScreen, monitorIndex, metaWindow) { + throw new Error('Not implemented'); + }, + _isWindowVisible: function(window) { let workspace = global.screen.get_active_workspace(); - return !window.skip_taskbar && window.located_on_workspace(workspace); + return !window.skip_taskbar && + window.located_on_workspace(workspace) && + (!this._perMonitor || window.get_monitor() == this._monitorIndex); }, _updateVisibility: function() { @@ -272,6 +290,14 @@ const BaseButton = new Lang.Class({ _onDestroy: function() { global.window_manager.disconnect(this._switchWorkspaceId); + + if (this._windowEnteredMonitorId) + global.screen.disconnect(this._windowEnteredMonitorId); + this._windowEnteredMonitorId = 0; + + if (this._windowLeftMonitorId) + global.screen.disconnect(this._windowLeftMonitorId); + this._windowLeftMonitorId = 0; } }); @@ -280,8 +306,8 @@ const WindowButton = new Lang.Class({ Name: 'WindowButton', Extends: BaseButton, - _init: function(metaWindow) { - this.parent(); + _init: function(metaWindow, perMonitor, monitorIndex) { + this.parent(perMonitor, monitorIndex); this.metaWindow = metaWindow; this._updateVisibility(); @@ -330,6 +356,11 @@ const WindowButton = new Lang.Class({ this.actor.remove_style_class_name('minimized'); }, + _windowEnteredOrLeftMonitor: function(metaScreen, monitorIndex, metaWindow) { + if (monitorIndex == this._monitorIndex && metaWindow == this.metaWindow) + this._updateVisibility(); + }, + _updateVisibility: function() { this.actor.visible = this._isWindowVisible(this.metaWindow); }, @@ -422,8 +453,8 @@ const AppButton = new Lang.Class({ Name: 'AppButton', Extends: BaseButton, - _init: function(app) { - this.parent(); + _init: function(app, perMonitor, monitorIndex) { + this.parent(perMonitor, monitorIndex); this.app = app; this._updateVisibility(); @@ -476,9 +507,22 @@ const AppButton = new Lang.Class({ this._updateStyle(); }, + _windowEnteredOrLeftMonitor: function(metaScreen, monitorIndex, metaWindow) { + if (this._windowTracker.get_window_app(metaWindow) == this.app && + monitorIndex == this._monitorIndex) { + this._updateVisibility(); + this._windowsChanged(); + } + }, + _updateVisibility: function() { - let workspace = global.screen.get_active_workspace(); - this.actor.visible = this.app.is_on_workspace(workspace); + if (!this._perMonitor) { + // fast path: use ShellApp API to avoid iterating over all windows. + let workspace = global.screen.get_active_workspace(); + this.actor.visible = this.app.is_on_workspace(workspace); + } else { + this.actor.visible = this.getWindowList().length >= 1; + } }, _isFocused: function() { @@ -767,7 +811,10 @@ const WorkspaceIndicator = new Lang.Class({ const WindowList = new Lang.Class({ Name: 'WindowList', - _init: function() { + _init: function(perMonitor, monitor) { + this._perMonitor = perMonitor; + this._monitor = monitor; + this.actor = new St.Widget({ name: 'panel', style_class: 'bottom-panel', reactive: true, @@ -801,26 +848,35 @@ const WindowList = new Lang.Class({ this._workspaceIndicator = new WorkspaceIndicator(); indicatorsBox.add(this._workspaceIndicator.container, { expand: false, y_fill: true }); + this._workspaceSettings = new Gio.Settings({ schema_id: 'org.gnome.shell.overrides' }); + this._workspacesOnlyOnPrimaryChangedId = + this._workspaceSettings.connect('changed::workspaces-only-on-primary', + Lang.bind(this, this._updateWorkspaceIndicatorVisibility)); + this._updateWorkspaceIndicatorVisibility(); + this._menuManager = new PopupMenu.PopupMenuManager(this); this._menuManager.addMenu(this._workspaceIndicator.menu); - this._trayButton = new TrayButton(); - indicatorsBox.add(this._trayButton.actor, { expand: false }); + this._isOnBottomMonitor = this._monitor == Main.layoutManager.bottomMonitor; + + if (this._isOnBottomMonitor) { + this._trayButton = new TrayButton(); + indicatorsBox.add(this._trayButton.actor, { expand: false }); + } Main.layoutManager.addChrome(this.actor, { affectsStruts: true, trackFullscreen: true }); Main.ctrlAltTabManager.addGroup(this.actor, _("Window List"), 'start-here-symbolic'); + this.actor.width = this._monitor.width; + this.actor.set_position(this._monitor.x, + this._monitor.y + this._monitor.height - this.actor.height); + this._appSystem = Shell.AppSystem.get_default(); this._appStateChangedId = this._appSystem.connect('app-state-changed', Lang.bind(this, this._onAppStateChanged)); - this._monitorsChangedId = - Main.layoutManager.connect('monitors-changed', - Lang.bind(this, this._updatePosition)); - this._updatePosition(); - this._keyboardVisiblechangedId = Main.layoutManager.connect('keyboard-visible-changed', Lang.bind(this, function(o, state) { @@ -854,8 +910,6 @@ const WindowList = new Lang.Class({ this._updateMessageTrayAnchor(); })); - this._isOnBottomMonitor = Main.layoutManager.primaryIndex == Main.layoutManager.bottomIndex; - if (this._isOnBottomMonitor) { let actor = this.actor; this._bottomHoverChangedId = @@ -925,6 +979,12 @@ const WindowList = new Lang.Class({ children[active].activate(); }, + _updateWorkspaceIndicatorVisibility: function() { + this._workspaceIndicator.actor.visible = + this._monitor == Main.layoutManager.primaryMonitor || + !this._workspaceSettings.get_boolean('workspaces-only-on-primary'); + }, + _getPreferredUngroupedWindowListWidth: function() { if (this._windowList.get_n_children() == 0) return this._windowList.get_preferred_width(-1)[1]; @@ -934,13 +994,21 @@ const WindowList = new Lang.Class({ let spacing = this._windowList.layout_manager.spacing; let workspace = global.screen.get_active_workspace(); - let nWindows = global.display.get_tab_list(Meta.TabList.NORMAL, workspace).length; + let windows = global.display.get_tab_list(Meta.TabList.NORMAL, workspace); + if (this._perMonitor) { + windows = windows.filter(Lang.bind(this, function(window) { + return window.get_monitor() == this._monitor.index; + })); + } + let nWindows = windows.length; + if (nWindows == 0) + return this._windowList.get_preferred_width(-1)[1]; return nWindows * childWidth + (nWindows - 1) * spacing; }, _getMaxWindowListWidth: function() { - let indicatorsBox = this._trayButton.actor.get_parent(); + let indicatorsBox = this._workspaceIndicator.actor.get_parent(); return this.actor.width - indicatorsBox.get_preferred_width(-1)[1]; }, @@ -991,12 +1059,6 @@ const WindowList = new Lang.Class({ } }, - _updatePosition: function() { - let monitor = Main.layoutManager.primaryMonitor; - this.actor.width = monitor.width; - this.actor.set_position(monitor.x, monitor.y + monitor.height - this.actor.height); - }, - _updateKeyboardAnchor: function() { if (!Main.keyboard.actor) return; @@ -1026,7 +1088,7 @@ const WindowList = new Lang.Class({ }, _addApp: function(app) { - let button = new AppButton(app); + let button = new AppButton(app, this._perMonitor, this._monitor.index); this._windowList.layout_manager.pack(button.actor, true, true, true, Clutter.BoxAlignment.START, @@ -1059,7 +1121,7 @@ const WindowList = new Lang.Class({ return; } - let button = new WindowButton(win); + let button = new WindowButton(win, this._perMonitor, this._monitor.index); this._windowList.layout_manager.pack(button.actor, true, true, true, Clutter.BoxAlignment.START, @@ -1168,6 +1230,8 @@ const WindowList = new Lang.Class({ }, _onDestroy: function() { + this._workspaceSettings.disconnect(this._workspacesOnlyOnPrimaryChangedId); + this._workspaceIndicator.destroy(); Main.ctrlAltTabManager.removeGroup(this.actor); @@ -1175,9 +1239,6 @@ const WindowList = new Lang.Class({ this._appSystem.disconnect(this._appStateChangedId); this._appStateChangedId = 0; - Main.layoutManager.disconnect(this._monitorsChangedId); - this._monitorsChangedId = 0; - Main.layoutManager.disconnect(this._keyboardVisiblechangedId); this._keyboardVisiblechangedId = 0; @@ -1224,34 +1285,69 @@ const Extension = new Lang.Class({ Name: 'Extension', _init: function() { - this._windowList = null; + this._windowLists = null; this._injections = {}; }, enable: function() { - this._windowList = new WindowList(); + this._windowLists = []; this._injections['_trayDwellTimeout'] = MessageTray.MessageTray.prototype._trayDwellTimeout; MessageTray.MessageTray.prototype._trayDwellTimeout = function() { return false; }; + + this._settings = Convenience.getSettings(); + this._showOnAllMonitorsChangedId = + this._settings.connect('changed::show-on-all-monitors', + Lang.bind(this, this._buildWindowLists)); + + this._monitorsChangedId = + Main.layoutManager.connect('monitors-changed', + Lang.bind(this, this._buildWindowLists)); + + this._buildWindowLists(); + }, + + _buildWindowLists: function() { + this._windowLists.forEach(function(windowList) { + windowList.actor.destroy(); + }); + this._windowLists = []; + + let showOnAllMonitors = this._settings.get_boolean('show-on-all-monitors'); + + Main.layoutManager.monitors.forEach(Lang.bind(this, function(monitor) { + if (showOnAllMonitors || monitor == Main.layoutManager.primaryMonitor) + this._windowLists.push(new WindowList(showOnAllMonitors, monitor)); + })); }, disable: function() { - if (!this._windowList) + if (!this._windowLists) return; - this._windowList.actor.hide(); - this._windowList.actor.destroy(); - this._windowList = null; + this._settings.disconnect(this._showOnAllMonitorsChangedId); + this._showOnAllMonitorsChangedId = 0; + + Main.layoutManager.disconnect(this._monitorsChangedId); + this._monitorsChangedId = 0; + + this._windowLists.forEach(function(windowList) { + windowList.actor.hide(); + windowList.actor.destroy(); + }); + this._windowLists = null; for (let prop in this._injections) MessageTray.MessageTray.prototype[prop] = this._injections[prop]; }, - windowListContains: function(actor) { - return this._windowList.actor.contains(actor); + someWindowListContains: function(actor) { + return this._windowLists.some(function(windowList) { + return windowList.actor.contains(actor); + }); } }); diff --git a/extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml.in b/extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml.in index d5bbdf4e..f736e363 100644 --- a/extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml.in +++ b/extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml.in @@ -15,5 +15,13 @@ window list. Possible values are "never", "auto" and "always". + + false + <_summary>Show the window list on all monitors + <_description> + Whether to show the window list on all connected monitors or + only on the primary one. + + diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js index 3db09c30..dc200656 100644 --- a/extensions/window-list/prefs.js +++ b/extensions/window-list/prefs.js @@ -1,5 +1,6 @@ // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- +const Gio = imports.gi.Gio; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; @@ -19,25 +20,25 @@ function init() { const WindowListPrefsWidget = new GObject.Class({ Name: 'WindowList.Prefs.Widget', GTypeName: 'WindowListPrefsWidget', - Extends: Gtk.Frame, + Extends: Gtk.Grid, _init: function(params) { this.parent(params); - this.shadow_type = Gtk.ShadowType.NONE; this.margin = 24; + this.row_spacing = 6; + this.orientation = Gtk.Orientation.VERTICAL; - let title = '' + _("Window Grouping") + ''; - let titleLabel = new Gtk.Label({ use_markup: true, label: title }); - this.set_label_widget(titleLabel); + let groupingLabel = '' + _("Window Grouping") + ''; + this.add(new Gtk.Label({ label: groupingLabel, use_markup: true, + halign: Gtk.Align.START })); let align = new Gtk.Alignment({ left_padding: 12 }); this.add(align); let grid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL, row_spacing: 6, - column_spacing: 6, - margin_top: 6 }); + column_spacing: 6 }); align.add(grid); this._settings = Convenience.getSettings(); @@ -70,6 +71,11 @@ const WindowListPrefsWidget = new GObject.Class({ this._settings.set_string('grouping-mode', mode); })); } + + let check = new Gtk.CheckButton({ label: _("Show on all monitors"), + margin_top: 6 }); + this._settings.bind('show-on-all-monitors', check, 'active', Gio.SettingsBindFlags.DEFAULT); + this.add(check); } });