From a69a71fafe3222026c938a0832f350654fe05c5c Mon Sep 17 00:00:00 2001 From: Sylvain Pasche Date: Mon, 10 Nov 2014 21:35:27 +0100 Subject: [PATCH 1/5] window-list: Refactoring to use an Extension object Move the global state into a new Extension object. This is in preparation for adding more logic to the Extension object. https://bugzilla.gnome.org/show_bug.cgi?id=737486 --- extensions/window-list/extension.js | 81 +++++++++++++++++++++---------------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js index f37c850..8589c43 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 (windowList.actor.contains(actor)) + if (Me.stateObj.windowListContains(actor)) actor.sync_hover(); } @@ -1196,49 +1196,60 @@ const WindowList = new Lang.Class({ } }); -let windowList; -let injections = {}; -let notificationParent; +const Extension = new Lang.Class({ + Name: 'Extension', -function init() { -} + _init: function() { + this._windowList = null; + this._injections = {}; + this._notificationParent = null; + }, -function enable() { - windowList = new WindowList(); + enable: function() { + this._windowList = new WindowList(); + let windowListActor = this._windowList.actor; - windowList.actor.connect('notify::hover', Lang.bind(Main.messageTray, - function() { - this._pointerInNotification = windowList.actor.hover; - this._updateState(); - })); + windowListActor.connect('notify::hover', Lang.bind(Main.messageTray, + function() { + this._pointerInNotification = windowListActor.hover; + this._updateState(); + })); - injections['_trayDwellTimeout'] = MessageTray.MessageTray.prototype._trayDwellTimeout; - MessageTray.MessageTray.prototype._trayDwellTimeout = function() { - return false; - }; + this._injections['_trayDwellTimeout'] = + MessageTray.MessageTray.prototype._trayDwellTimeout; + MessageTray.MessageTray.prototype._trayDwellTimeout = function() { + return false; + }; - notificationParent = Main.messageTray._notificationWidget.get_parent(); - Main.messageTray._notificationWidget.hide(); - Main.messageTray._notificationWidget.reparent(windowList.actor); - Main.messageTray._notificationWidget.show(); -} + this._notificationParent = Main.messageTray._notificationWidget.get_parent(); + Main.messageTray._notificationWidget.hide(); + Main.messageTray._notificationWidget.reparent(windowListActor); + Main.messageTray._notificationWidget.show(); + }, -function disable() { - var prop; + disable: function() { + if (!this._windowList) + return; - if (!windowList) - return; + this._windowList.actor.hide(); - windowList.actor.hide(); + if (this._notificationParent) { + Main.messageTray._notificationWidget.reparent(this._notificationParent); + this._notificationParent = null; + } - if (notificationParent) { - Main.messageTray._notificationWidget.reparent(notificationParent); - notificationParent = null; - } + this._windowList.actor.destroy(); + this._windowList = null; - windowList.actor.destroy(); - windowList = null; + for (let prop in this._injections) + MessageTray.MessageTray.prototype[prop] = this._injections[prop]; + }, - for (prop in injections) - MessageTray.MessageTray.prototype[prop] = injections[prop]; + windowListContains: function(actor) { + return this._windowList.actor.contains(actor); + } +}); + +function init() { + return new Extension(); } -- 2.5.0 From ca4b95594d10e80d4b3a430d4a2b3fb79472970d Mon Sep 17 00:00:00 2001 From: Sylvain Pasche Date: Mon, 10 Nov 2014 21:37:22 +0100 Subject: [PATCH 2/5] window-list: Move messageTray patching to the WindowList class Move messageTray patching form the Extension object to the WindowList class. Moreover, only do the patching if the window list is on the bottom monitor. This refactoring will make it easier to have several instances of WindowList (one on each monitor). https://bugzilla.gnome.org/show_bug.cgi?id=737486 --- extensions/window-list/extension.js | 58 ++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js index 8589c43..d537f7c 100644 --- a/extensions/window-list/extension.js +++ b/extensions/window-list/extension.js @@ -858,6 +858,24 @@ const WindowList = new Lang.Class({ this._updateKeyboardAnchor(); this._updateMessageTrayAnchor(); })); + + this._isOnBottomMonitor = Main.layoutManager.primaryIndex == Main.layoutManager.bottomIndex; + + if (this._isOnBottomMonitor) { + let actor = this.actor; + this._bottomHoverChangedId = + actor.connect('notify::hover', Lang.bind(Main.messageTray, + function() { + this._pointerInNotification = actor.hover; + this._updateState(); + })); + + this._notificationParent = Main.messageTray._notificationWidget.get_parent(); + Main.messageTray._notificationWidget.hide(); + Main.messageTray._notificationWidget.reparent(this.actor); + Main.messageTray._notificationWidget.show(); + } + this._updateMessageTrayAnchor(); this._fullscreenChangedId = @@ -995,8 +1013,10 @@ const WindowList = new Lang.Class({ }, _updateMessageTrayAnchor: function() { - let sameMonitor = Main.layoutManager.primaryIndex == Main.layoutManager.bottomIndex; - let anchorY = this.actor.visible && sameMonitor ? this.actor.height : 0; + if (!this._isOnBottomMonitor) + return; + + let anchorY = this.actor.visible ? this.actor.height : 0; Main.messageTray.actor.anchor_y = anchorY; Main.messageTray._notificationWidget.anchor_y = -anchorY; @@ -1177,8 +1197,19 @@ const WindowList = new Lang.Class({ global.window_manager.disconnect(this._switchWorkspaceId); this._switchWorkspaceId = 0; - Main.messageTray.actor.anchor_y = 0; - Main.messageTray._notificationWidget.anchor_y = 0; + if (this._bottomHoverChangedId) + this.actor.disconnect(this._bottomHoverChangedId); + this._bottomHoverChangedId = 0; + + if (this._notificationParent) { + Main.messageTray._notificationWidget.reparent(this._notificationParent); + this._notificationParent = null; + } + + if (this._isOnBottomMonitor) { + Main.messageTray.actor.anchor_y = 0; + Main.messageTray._notificationWidget.anchor_y = 0; + } Main.overview.disconnect(this._overviewShowingId); Main.overview.disconnect(this._overviewHidingId); @@ -1202,29 +1233,16 @@ const Extension = new Lang.Class({ _init: function() { this._windowList = null; this._injections = {}; - this._notificationParent = null; }, enable: function() { this._windowList = new WindowList(); - let windowListActor = this._windowList.actor; - - windowListActor.connect('notify::hover', Lang.bind(Main.messageTray, - function() { - this._pointerInNotification = windowListActor.hover; - this._updateState(); - })); this._injections['_trayDwellTimeout'] = MessageTray.MessageTray.prototype._trayDwellTimeout; MessageTray.MessageTray.prototype._trayDwellTimeout = function() { return false; }; - - this._notificationParent = Main.messageTray._notificationWidget.get_parent(); - Main.messageTray._notificationWidget.hide(); - Main.messageTray._notificationWidget.reparent(windowListActor); - Main.messageTray._notificationWidget.show(); }, disable: function() { @@ -1232,12 +1250,6 @@ const Extension = new Lang.Class({ return; this._windowList.actor.hide(); - - if (this._notificationParent) { - Main.messageTray._notificationWidget.reparent(this._notificationParent); - this._notificationParent = null; - } - this._windowList.actor.destroy(); this._windowList = null; -- 2.5.0 From 352352112596ea4b6d21120b2c6d49dcabf0d0c2 Mon Sep 17 00:00:00 2001 From: Sylvain Pasche Date: Mon, 10 Nov 2014 21:37:29 +0100 Subject: [PATCH 3/5] window-list: Refactor {Window,App}Button shared code into BaseButton BaseButton is a new class that shares the common logic of WindowButton and AppButton. AppButton is passed to AppContextMenu so that it can reuse code from the now public getWindowList() method. https://bugzilla.gnome.org/show_bug.cgi?id=737486 --- extensions/window-list/extension.js | 233 +++++++++++++++++++----------------- 1 file changed, 123 insertions(+), 110 deletions(-) diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js index d537f7c..897f448 100644 --- a/extensions/window-list/extension.js +++ b/extensions/window-list/extension.js @@ -195,47 +195,30 @@ const WindowTitle = new Lang.Class({ }); -const WindowButton = new Lang.Class({ - Name: 'WindowButton', - - _init: function(metaWindow) { - this.metaWindow = metaWindow; +const BaseButton = new Lang.Class({ + Name: 'BaseButton', + Abstract: true, - this._windowTitle = new WindowTitle(this.metaWindow); + _init: function() { this.actor = new St.Button({ style_class: 'window-button', x_fill: true, y_fill: true, can_focus: true, button_mask: St.ButtonMask.ONE | - St.ButtonMask.THREE, - child: this._windowTitle.actor }); + St.ButtonMask.THREE }); this.actor._delegate = this; - this._menuManager = new PopupMenu.PopupMenuManager(this); - this._contextMenu = new WindowContextMenu(this.actor, this.metaWindow); - this._contextMenu.connect('open-state-changed', _onMenuStateChanged); - this._contextMenu.actor.hide(); - this._menuManager.addMenu(this._contextMenu); - Main.uiGroup.add_actor(this._contextMenu.actor); - this.actor.connect('allocation-changed', Lang.bind(this, this._updateIconGeometry)); this.actor.connect('clicked', Lang.bind(this, this._onClicked)); this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); this.actor.connect('popup-menu', Lang.bind(this, this._onPopupMenu)); + this._contextMenuManager = new PopupMenu.PopupMenuManager(this); + this._switchWorkspaceId = global.window_manager.connect('switch-workspace', Lang.bind(this, this._updateVisibility)); - this._workspaceChangedId = - this.metaWindow.connect('workspace-changed', - Lang.bind(this, this._updateVisibility)); - this._updateVisibility(); - - this._notifyFocusId = - global.display.connect('notify::focus-window', - Lang.bind(this, this._updateStyle)); - this._updateStyle(); }, get active() { @@ -250,6 +233,89 @@ const WindowButton = new Lang.Class({ }, _onClicked: function(actor, button) { + throw new Error('Not implemented'); + }, + + _canOpenPopupMenu: function() { + return true; + }, + + _onPopupMenu: function(actor) { + if (!this._canOpenPopupMenu() || this._contextMenu.isOpen) + return; + _openMenu(this._contextMenu); + }, + + _isFocused: function() { + throw new Error('Not implemented'); + }, + + _updateStyle: function() { + if (this._isFocused()) + this.actor.add_style_class_name('focused'); + else + this.actor.remove_style_class_name('focused'); + }, + + _isWindowVisible: function(window) { + let workspace = global.screen.get_active_workspace(); + + return !window.skip_taskbar && window.located_on_workspace(workspace); + }, + + _updateVisibility: function() { + throw new Error('Not implemented'); + }, + + _getIconGeometry: function() { + let rect = new Meta.Rectangle(); + + [rect.x, rect.y] = this.actor.get_transformed_position(); + [rect.width, rect.height] = this.actor.get_transformed_size(); + + return rect; + }, + + _updateIconGeometry: function() { + throw new Error('Not implemented'); + }, + + _onDestroy: function() { + global.window_manager.disconnect(this._switchWorkspaceId); + } +}); + + +const WindowButton = new Lang.Class({ + Name: 'WindowButton', + Extends: BaseButton, + + _init: function(metaWindow) { + this.parent(); + + this.metaWindow = metaWindow; + this._updateVisibility(); + + this._windowTitle = new WindowTitle(this.metaWindow); + this.actor.set_child(this._windowTitle.actor); + + this._contextMenu = new WindowContextMenu(this.actor, this.metaWindow); + this._contextMenu.connect('open-state-changed', _onMenuStateChanged); + this._contextMenu.actor.hide(); + this._contextMenuManager.addMenu(this._contextMenu); + Main.uiGroup.add_actor(this._contextMenu.actor); + + this._workspaceChangedId = + this.metaWindow.connect('workspace-changed', + Lang.bind(this, this._updateVisibility)); + + this._notifyFocusId = + global.display.connect('notify::focus-window', + Lang.bind(this, this._updateStyle)); + this._updateStyle(); + }, + + _onClicked: function(actor, button) { if (this._contextMenu.isOpen) { this._contextMenu.close(); return; @@ -261,41 +327,30 @@ const WindowButton = new Lang.Class({ _openMenu(this._contextMenu); }, - _onPopupMenu: function(actor) { - if (this._contextMenu.isOpen) - return; - _openMenu(this._contextMenu); + _isFocused: function() { + return global.display.focus_window == this.metaWindow; }, _updateStyle: function() { + this.parent(); + if (this.metaWindow.minimized) this.actor.add_style_class_name('minimized'); else this.actor.remove_style_class_name('minimized'); - - if (global.display.focus_window == this.metaWindow) - this.actor.add_style_class_name('focused'); - else - this.actor.remove_style_class_name('focused'); }, _updateVisibility: function() { - let workspace = global.screen.get_active_workspace(); - this.actor.visible = this.metaWindow.located_on_workspace(workspace); + this.actor.visible = this._isWindowVisible(this.metaWindow); }, _updateIconGeometry: function() { - let rect = new Meta.Rectangle(); - - [rect.x, rect.y] = this.actor.get_transformed_position(); - [rect.width, rect.height] = this.actor.get_transformed_size(); - - this.metaWindow.set_icon_geometry(rect); + this.metaWindow.set_icon_geometry(this._getIconGeometry()); }, _onDestroy: function() { + this.parent(); this.metaWindow.disconnect(this._workspaceChangedId); - global.window_manager.disconnect(this._switchWorkspaceId); global.display.disconnect(this._notifyFocusId); this._contextMenu.destroy(); } @@ -306,14 +361,14 @@ const AppContextMenu = new Lang.Class({ Name: 'AppContextMenu', Extends: PopupMenu.PopupMenu, - _init: function(source, app) { + _init: function(source, appButton) { this.parent(source, 0.5, St.Side.BOTTOM); - this._app = app; + this._appButton = appButton; this._minimizeItem = new PopupMenu.PopupMenuItem(_("Minimize all")); this._minimizeItem.connect('activate', Lang.bind(this, function() { - this._getWindowList().forEach(function(w) { + this._appButton.getWindowList().forEach(function(w) { w.minimize(); }); })); @@ -321,7 +376,7 @@ const AppContextMenu = new Lang.Class({ this._unminimizeItem = new PopupMenu.PopupMenuItem(_("Unminimize all")); this._unminimizeItem.connect('activate', Lang.bind(this, function() { - this._getWindowList().forEach(function(w) { + this._appButton.getWindowList().forEach(function(w) { w.unminimize(); }); })); @@ -329,7 +384,7 @@ const AppContextMenu = new Lang.Class({ this._maximizeItem = new PopupMenu.PopupMenuItem(_("Maximize all")); this._maximizeItem.connect('activate', Lang.bind(this, function() { - this._getWindowList().forEach(function(w) { + this._appButton.getWindowList().forEach(function(w) { w.maximize(Meta.MaximizeFlags.HORIZONTAL | Meta.MaximizeFlags.VERTICAL); }); @@ -338,7 +393,7 @@ const AppContextMenu = new Lang.Class({ this._unmaximizeItem = new PopupMenu.PopupMenuItem(_("Unmaximize all")); this._unmaximizeItem.connect('activate', Lang.bind(this, function() { - this._getWindowList().forEach(function(w) { + this._appButton.getWindowList().forEach(function(w) { w.unmaximize(Meta.MaximizeFlags.HORIZONTAL | Meta.MaximizeFlags.VERTICAL); }); @@ -347,22 +402,15 @@ const AppContextMenu = new Lang.Class({ let item = new PopupMenu.PopupMenuItem(_("Close all")); item.connect('activate', Lang.bind(this, function() { - this._getWindowList().forEach(function(w) { + this._appButton.getWindowList().forEach(function(w) { w.delete(global.get_current_time()); }); })); this.addMenuItem(item); }, - _getWindowList: function() { - let workspace = global.screen.get_active_workspace(); - return this._app.get_windows().filter(function(win) { - return !win.skip_taskbar && win.located_on_workspace(workspace); - }); - }, - open: function(animate) { - let windows = this._getWindowList(); + let windows = this._appButton.getWindowList(); this._minimizeItem.actor.visible = windows.some(function(w) { return !w.minimized; }); @@ -382,21 +430,16 @@ const AppContextMenu = new Lang.Class({ const AppButton = new Lang.Class({ Name: 'AppButton', + Extends: BaseButton, _init: function(app) { + this.parent(); + this.app = app; + this._updateVisibility(); let stack = new St.Widget({ layout_manager: new Clutter.BinLayout() }); - this.actor = new St.Button({ style_class: 'window-button', - x_fill: true, - can_focus: true, - button_mask: St.ButtonMask.ONE | - St.ButtonMask.THREE, - child: stack }); - this.actor._delegate = this; - - this.actor.connect('allocation-changed', - Lang.bind(this, this._updateIconGeometry)); + this.actor.set_child(stack); this._singleWindowTitle = new St.Bin({ x_expand: true, y_fill: true, @@ -421,8 +464,7 @@ const AppButton = new Lang.Class({ this._menuManager.addMenu(this._menu); Main.uiGroup.add_actor(this._menu.actor); - this._contextMenuManager = new PopupMenu.PopupMenuManager(this); - this._appContextMenu = new AppContextMenu(this.actor, this.app); + this._appContextMenu = new AppContextMenu(this.actor, this); this._appContextMenu.connect('open-state-changed', _onMenuStateChanged); this._appContextMenu.actor.hide(); Main.uiGroup.add_actor(this._appContextMenu.actor); @@ -433,14 +475,6 @@ const AppButton = new Lang.Class({ function() { this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); })); - this.actor.connect('clicked', Lang.bind(this, this._onClicked)); - this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); - this.actor.connect('popup-menu', Lang.bind(this, this._onPopupMenu)); - - this._switchWorkspaceId = - global.window_manager.connect('switch-workspace', - Lang.bind(this, this._updateVisibility)); - this._updateVisibility(); this._windowsChangedId = this.app.connect('windows-changed', @@ -459,18 +493,12 @@ const AppButton = new Lang.Class({ this.actor.visible = this.app.is_on_workspace(workspace); }, - _updateStyle: function() { - if (this._windowTracker.focus_app == this.app) - this.actor.add_style_class_name('focused'); - else - this.actor.remove_style_class_name('focused'); + _isFocused: function() { + return this._windowTracker.focus_app == this.app; }, _updateIconGeometry: function() { - let rect = new Meta.Rectangle(); - - [rect.x, rect.y] = this.actor.get_transformed_position(); - [rect.width, rect.height] = this.actor.get_transformed_size(); + let rect = this._getIconGeometry(); let windows = this.app.get_windows(); windows.forEach(function(w) { @@ -479,15 +507,14 @@ const AppButton = new Lang.Class({ }, - _getWindowList: function() { - let workspace = global.screen.get_active_workspace(); - return this.app.get_windows().filter(function(win) { - return !win.skip_taskbar && win.located_on_workspace(workspace); - }); + getWindowList: function() { + return this.app.get_windows().filter(Lang.bind(this, function(win) { + return this._isWindowVisible(win); + })); }, _windowsChanged: function() { - let windows = this._getWindowList(); + let windows = this.getWindowList(); this._singleWindowTitle.visible = windows.length == 1; this._multiWindowTitle.visible = !this._singleWindowTitle.visible; @@ -519,17 +546,6 @@ const AppButton = new Lang.Class({ }, - get active() { - return this.actor.has_style_class_name('focused'); - }, - - activate: function() { - if (this.active) - return; - - this._onClicked(this.actor, 1); - }, - _onClicked: function(actor, button) { let menuWasOpen = this._menu.isOpen; if (menuWasOpen) @@ -543,7 +559,7 @@ const AppButton = new Lang.Class({ if (menuWasOpen) return; - let windows = this._getWindowList(); + let windows = this.getWindowList(); if (windows.length == 1) { if (contextMenuWasOpen) return; @@ -567,20 +583,17 @@ const AppButton = new Lang.Class({ } }, - _onPopupMenu: function(actor) { - if (this._menu.isOpen || this._contextMenu.isOpen) - return; - _openMenu(this._contextMenu); + _canOpenPopupMenu: function() { + return !this._menu.isOpen; }, - _onMenuActivate: function(menu, child) { child._window.activate(global.get_current_time()); }, _onDestroy: function() { + this.parent(); this._textureCache.disconnect(this._iconThemeChangedId); - global.window_manager.disconnect(this._switchWorkspaceId); this._windowTracker.disconnect(this._notifyFocusId); this.app.disconnect(this._windowsChangedId); this._menu.destroy(); -- 2.5.0 From e8853c4a5e35a0e78d29fe3a861891533e8ac723 Mon Sep 17 00:00:00 2001 From: Sylvain Pasche Date: Sun, 16 Nov 2014 16:03:39 +0100 Subject: [PATCH 4/5] 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 | 170 ++++++++++++++++----- ...ome.shell.extensions.window-list.gschema.xml.in | 8 + extensions/window-list/prefs.js | 20 ++- 3 files changed, 157 insertions(+), 41 deletions(-) diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js index 897f448..4f92c3c 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(); } @@ -199,7 +199,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, y_fill: true, @@ -219,6 +222,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() { @@ -257,10 +269,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() { @@ -282,6 +300,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; } }); @@ -290,8 +316,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(); @@ -340,6 +366,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); }, @@ -432,8 +463,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(); @@ -488,9 +519,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() { @@ -779,7 +823,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, @@ -813,27 +860,36 @@ 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.uiGroup.set_child_above_sibling(this.actor, Main.layoutManager.trayBox); Main.ctrlAltTabManager.addGroup(this.actor, _("Window List"), 'start-here-symbolic'); + this.actor.width = this._monitor.width; + this.actor.connect('notify::height', Lang.bind(this, this._updatePosition)); + this._updatePosition(); + 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.actor.connect('notify::height', Lang.bind(this, this._updatePosition)); - this._updatePosition(); this._keyboardVisiblechangedId = Main.layoutManager.connect('keyboard-visible-changed', @@ -872,8 +928,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 = @@ -943,6 +997,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]; @@ -952,13 +1012,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]; }, @@ -1046,7 +1114,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, @@ -1079,7 +1147,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, @@ -1188,6 +1256,8 @@ const WindowList = new Lang.Class({ }, _onDestroy: function() { + this._workspaceSettings.disconnect(this._workspacesOnlyOnPrimaryChangedId); + this._workspaceIndicator.destroy(); Main.ctrlAltTabManager.removeGroup(this.actor); @@ -1195,9 +1265,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; @@ -1244,34 +1311,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 d5bbdf4..f736e36 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 3db09c3..dc20065 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); } }); -- 2.5.0 From 85dc86579e17f290a2ceaea2b00db54094d96543 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 19 Dec 2014 14:58:52 +0100 Subject: [PATCH 5/5] window-list: Do not hardcode overrides schema Classic mode uses a different overrides schema, so make sure we use the correct setting instead of hardcoding the usual org.gnome.shell.overrides schema. https://bugzilla.gnome.org/show_bug.cgi?id=737486 --- extensions/window-list/extension.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js index 4f92c3c..64f3c72 100644 --- a/extensions/window-list/extension.js +++ b/extensions/window-list/extension.js @@ -860,7 +860,7 @@ 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._workspaceSettings = this._getWorkspaceSettings(); this._workspacesOnlyOnPrimaryChangedId = this._workspaceSettings.connect('changed::workspaces-only-on-primary', Lang.bind(this, this._updateWorkspaceIndicatorVisibility)); @@ -972,6 +972,13 @@ const WindowList = new Lang.Class({ this._groupingModeChanged(); }, + _getWorkspaceSettings: function() { + let settings = global.get_overrides_settings(); + if (settings.list_keys().indexOf('workspaces-only-on-primary') > -1) + return settings; + return new Gio.Settings({ schema_id: 'org.gnome.mutter' }); + }, + _onScrollEvent: function(actor, event) { let direction = event.get_scroll_direction(); let diff = 0; -- 2.5.0