Blob Blame History Raw
From a69a71fafe3222026c938a0832f350654fe05c5c Mon Sep 17 00:00:00 2001
From: Sylvain Pasche <sylvain.pasche@gmail.com>
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 <sylvain.pasche@gmail.com>
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 <sylvain.pasche@gmail.com>
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 5669e5b591dc9b8f11f48d9d70fe87f0c1fcb0ae Mon Sep 17 00:00:00 2001
From: Sylvain Pasche <sylvain.pasche@gmail.com>
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                | 183 ++++++++++++++++-----
 ...ome.shell.extensions.window-list.gschema.xml.in |   8 +
 extensions/window-list/prefs.js                    |  20 ++-
 3 files changed, 162 insertions(+), 49 deletions(-)

diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index 897f448..2de46c8 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,17 @@ const WindowList = new Lang.Class({
         children[active].activate();
     },
 
+    _updatePosition: function() {
+        this.actor.set_position(this._monitor.x,
+                                this._monitor.y + this._monitor.height - this.actor.height);
+    },
+
+    _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 +1017,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];
     },
 
@@ -1009,14 +1082,6 @@ const WindowList = new Lang.Class({
         }
     },
 
-    _updatePosition: function() {
-        let monitor = Main.layoutManager.primaryMonitor;
-        if (!monitor)
-            return;
-        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;
@@ -1046,7 +1111,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 +1144,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 +1253,8 @@ const WindowList = new Lang.Class({
     },
 
     _onDestroy: function() {
+        this._workspaceSettings.disconnect(this._workspacesOnlyOnPrimaryChangedId);
+
         this._workspaceIndicator.destroy();
 
         Main.ctrlAltTabManager.removeGroup(this.actor);
@@ -1195,9 +1262,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 +1308,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".
       </_description>
     </key>
+    <key name="show-on-all-monitors" type="b">
+      <default>false</default>
+      <_summary>Show the window list on all monitors</_summary>
+      <_description>
+        Whether to show the window list on all connected monitors or
+        only on the primary one.
+      </_description>
+    </key>
   </schema>
 </schemalist>
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 = '<b>' + _("Window Grouping") + '</b>';
-        let titleLabel = new Gtk.Label({ use_markup: true, label: title });
-        this.set_label_widget(titleLabel);
+        let groupingLabel = '<b>' + _("Window Grouping") + '</b>';
+        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 2761b1344c771673634e929a866cacdb01ab4caa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
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 2de46c8..128af39 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