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