Blob Blame History Raw
From e8425ac158bda015b09861bab4224ca2fdd2b50f Mon Sep 17 00:00:00 2001
From: Jakub Steiner <jimmac@gmail.com>
Date: Mon, 15 Jul 2019 23:40:09 +0200
Subject: [PATCH 01/30] classic: hover state for panel buttons

- prelight before active
- lighten up slightly, similar to what the default does (inverted)

Fixes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/169
---
 data/gnome-classic.scss | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/data/gnome-classic.scss b/data/gnome-classic.scss
index 9e23506..9c0e06e 100644
--- a/data/gnome-classic.scss
+++ b/data/gnome-classic.scss
@@ -32,18 +32,20 @@ $variant: 'light';
     font-weight: normal;
     color: $fg_color;
     text-shadow: none;
+    &:hover {
+      color: lighten($fg_color,10%);
+      text-shadow: none;
+      & .system-status-icon { icon-shadow: none; }
+    }
     &:active, &:overview, &:focus, &:checked {
       // Trick due to St limitations. It needs a background to draw
       // a box-shadow
-      background-color: $selected_bg_color !important;
-      color: $selected_fg_color !important;
+      background-color: $selected_bg_color;
+      color: $selected_fg_color;
       box-shadow: none;
       & > .system-status-icon { icon-shadow: none; }
     }
-    &:hover {
-      text-shadow: none;
-      & .system-status-icon { icon-shadow: none; }
-    }
+
     .app-menu-icon { width: 0; height: 0; margin: 0; } // shell's display:none; :D
 
     .system-status-icon {
-- 
2.21.0


From fcbb1b1d8956d7447973a9d5ffaa4fede8d359ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Thu, 18 Jul 2019 00:39:49 +0200
Subject: [PATCH 02/30] apps-menu: Add drop-shadow to application icons

... to make sure they are readable on light backgrounds.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/168
---
 extensions/apps-menu/extension.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js
index cc399c6..1f95c16 100644
--- a/extensions/apps-menu/extension.js
+++ b/extensions/apps-menu/extension.js
@@ -103,7 +103,9 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem {
     }
 
     _updateIcon() {
-        this._iconBin.set_child(this.getDragActor());
+        let icon = this.getDragActor();
+        icon.style_class = 'icon-dropshadow';
+        this._iconBin.set_child(icon);
     }
 }
 
-- 
2.21.0


From 904e632c62aab6cd89a849a01c56b7c0f123eee8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 29 May 2019 10:17:20 +0000
Subject: [PATCH 03/30] places-menu: Don't hardcode position

The extension currently assumes that we have the "Activities" button
at the left of the top bar. This is currently true, not only in the
regular session, but also in GNOME classic where the button is hidden
(but still present).

However this is about to change: We will stop taking over the button
from the apps-menu extension, and instead disable "Activities" from
the session mode definition.

Prepare for this by adding the places menu before the application menu
instead of assuming a hardcoded position.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69
---
 extensions/places-menu/extension.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/extensions/places-menu/extension.js b/extensions/places-menu/extension.js
index c477a4a..5c038ae 100644
--- a/extensions/places-menu/extension.js
+++ b/extensions/places-menu/extension.js
@@ -135,9 +135,9 @@ let _indicator;
 function enable() {
     _indicator = new PlacesMenu;
 
-    let pos = 1;
+    let pos = Main.sessionMode.panel.left.indexOf('appMenu');
     if ('apps-menu' in Main.panel.statusArea)
-        pos = 2;
+        pos++;
     Main.panel.addToStatusArea('places-menu', _indicator, pos, 'left');
 }
 
-- 
2.21.0


From 2b1eb285d0c0e113a9be2bd801e1692b48fcfd78 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 29 May 2019 08:32:03 +0000
Subject: [PATCH 04/30] apps-menu: Stop taking over Activities button

We don't want the "Activities" button in GNOME Classic, but the current
way of handling it is confusing:

 - the button is hidden, but the corresponding hot corner
   sometimes works (when the application menu isn't open)

 - the button is effectively moved inside the menu, although
   it's clearly not an app or category

 - the apps-menu can be used independent from classic mode, in
   which case removing the "Activities" button may not be wanted

Address those points by removing any handling of the activities button
from the apps-menu extension. We will remove it again from the classic
session via a session mode tweak.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69
---
 extensions/apps-menu/extension.js | 67 +++----------------------------
 1 file changed, 6 insertions(+), 61 deletions(-)

diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js
index 1f95c16..865a87e 100644
--- a/extensions/apps-menu/extension.js
+++ b/extensions/apps-menu/extension.js
@@ -25,22 +25,6 @@ const NAVIGATION_REGION_OVERSHOOT = 50;
 Gio._promisify(Gio._LocalFilePrototype, 'query_info_async', 'query_info_finish');
 Gio._promisify(Gio._LocalFilePrototype, 'set_attributes_async', 'set_attributes_finish');
 
-class ActivitiesMenuItem extends PopupMenu.PopupBaseMenuItem {
-    constructor(button) {
-        super();
-        this._button = button;
-        let label = new St.Label({ text: _('Activities Overview') });
-        this.actor.add_child(label);
-        this.actor.label_actor = label;
-    }
-
-    activate(event) {
-        this._button.menu.toggle();
-        Main.overview.toggle();
-        super.activate(event);
-    }
-}
-
 class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem {
     constructor(button, app) {
         super();
@@ -235,21 +219,6 @@ class ApplicationsMenu extends PopupMenu.PopupMenu {
         return false;
     }
 
-    open(animate) {
-        this._button.hotCorner.setBarrierSize(0);
-        if (this._button.hotCorner.actor) // fallback corner
-            this._button.hotCorner.actor.hide();
-        super.open(animate);
-    }
-
-    close(animate) {
-        let size = Main.layoutManager.panelBox.height;
-        this._button.hotCorner.setBarrierSize(size);
-        if (this._button.hotCorner.actor) // fallback corner
-            this._button.hotCorner.actor.show();
-        super.close(animate);
-    }
-
     toggle() {
         if (this.isOpen) {
             this._button.selectCategory(null);
@@ -383,7 +352,7 @@ Signals.addSignalMethods(DesktopTarget.prototype);
 
 let ApplicationsButton = GObject.registerClass(
 class ApplicationsButton extends PanelMenu.Button {
-    _init() {
+    _init(includeIcon) {
         super._init(1.0, null, false);
 
         this.setMenu(new ApplicationsMenu(this, 1.0, St.Side.TOP, this));
@@ -400,7 +369,8 @@ class ApplicationsButton extends PanelMenu.Button {
             '/usr/share/icons/hicolor/scalable/apps/start-here.svg');
         this._icon = new St.Icon({
             gicon: new Gio.FileIcon({ file: iconFile }),
-            style_class: 'panel-logo-icon'
+            style_class: 'panel-logo-icon',
+            visible: includeIcon
         });
         hbox.add_actor(this._icon);
 
@@ -416,8 +386,6 @@ class ApplicationsButton extends PanelMenu.Button {
         this.name = 'panelApplications';
         this.label_actor = this._label;
 
-        this.connect('captured-event', this._onCapturedEvent.bind(this));
-
         this._showingId = Main.overview.connect('showing', () => {
             this.add_accessible_state (Atk.StateType.CHECKED);
         });
@@ -459,10 +427,6 @@ class ApplicationsButton extends PanelMenu.Button {
         }
     }
 
-    get hotCorner() {
-        return Main.layoutManager.hotCorners[Main.layoutManager.primaryIndex];
-    }
-
     _createVertSeparator() {
         let separator = new St.DrawingArea({
             style_class: 'calendar-vertical-separator',
@@ -489,14 +453,6 @@ class ApplicationsButton extends PanelMenu.Button {
         this._desktopTarget.destroy();
     }
 
-    _onCapturedEvent(actor, event) {
-        if (event.type() == Clutter.EventType.BUTTON_PRESS) {
-            if (!Main.overview.shouldToggleByCornerOrButton())
-                return true;
-        }
-        return false;
-    }
-
     _onMenuKeyPress(actor, event) {
         let symbol = event.get_key_symbol();
         if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) {
@@ -640,14 +596,6 @@ class ApplicationsButton extends PanelMenu.Button {
             y_align: St.Align.START
         });
 
-        let activities = new ActivitiesMenuItem(this);
-        this.leftBox.add(activities.actor, {
-            expand: false,
-            x_fill: true,
-            y_fill: false,
-            y_align: St.Align.START
-        });
-
         this.applicationsBox = new St.BoxLayout({ vertical: true });
         this.applicationsScrollBox.add_actor(this.applicationsBox);
         this.categoriesBox = new St.BoxLayout({ vertical: true });
@@ -759,19 +707,16 @@ class ApplicationsButton extends PanelMenu.Button {
 });
 
 let appsMenuButton;
-let activitiesButton;
 
 function enable() {
-    activitiesButton = Main.panel.statusArea['activities'];
-    activitiesButton.container.hide();
-    appsMenuButton = new ApplicationsButton();
-    Main.panel.addToStatusArea('apps-menu', appsMenuButton, 1, 'left');
+    let index = Main.sessionMode.panel.left.indexOf('activities') + 1;
+    appsMenuButton = new ApplicationsButton(index == 0);
+    Main.panel.addToStatusArea('apps-menu', appsMenuButton, index, 'left');
 }
 
 function disable() {
     Main.panel.menuManager.removeMenu(appsMenuButton.menu);
     appsMenuButton.destroy();
-    activitiesButton.container.show();
 }
 
 function init() {
-- 
2.21.0


From 45b5f9b8f73032a87017131d8f29b429a8b75ef1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Fri, 7 Jun 2019 14:30:16 +0000
Subject: [PATCH 05/30] apps-menu: Stop hiding the overview when toggled

Now that the extension no longer doubles as the "Activities" button,
that behavior is confusing.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69
---
 extensions/apps-menu/extension.js | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js
index 865a87e..1e2882b 100644
--- a/extensions/apps-menu/extension.js
+++ b/extensions/apps-menu/extension.js
@@ -220,12 +220,8 @@ class ApplicationsMenu extends PopupMenu.PopupMenu {
     }
 
     toggle() {
-        if (this.isOpen) {
+        if (this.isOpen)
             this._button.selectCategory(null);
-        } else {
-            if (Main.overview.visible)
-                Main.overview.hide();
-        }
         super.toggle();
     }
 }
-- 
2.21.0


From 0f143dc87bd501a44843aad7d0a1cd0d20ad4d31 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Fri, 7 Jun 2019 20:07:19 +0000
Subject: [PATCH 06/30] apps-menu: Hide overview when launching app

Now that we no longer hide the overview when the menu is opened,
it is possible to activate menu entries from the overview. Start
hiding the overview in that case, which is consistent with app
launching elsewhere.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69
---
 extensions/apps-menu/extension.js | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js
index 1e2882b..3dbe43f 100644
--- a/extensions/apps-menu/extension.js
+++ b/extensions/apps-menu/extension.js
@@ -66,6 +66,8 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem {
         this._button.selectCategory(null);
         this._button.menu.toggle();
         super.activate(event);
+
+        Main.overview.hide();
     }
 
     setActive(active, params) {
-- 
2.21.0


From 72f956cc589d9789ae6ad7b99c1dfd60f9a9f8e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 29 May 2019 09:44:30 +0000
Subject: [PATCH 07/30] classic: Disable overview

The overview is one of the defining features of GNOME 3, and thus
almost by definition at odds with the classic session, which
emulates a traditional GNOME 2 desktop.

Even with the less prominent placement inside the application menu
it never quite fit in - it doesn't help that besides the different
UI paradigma, the overview keeps its "normal" styling which differs
greatly with classic's normal mode.

So besides removing the "Activities" button via the session mode
definition, now that the apps-menu extension doesn't replace it anymore,
disable the overview completely in the classic session.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69
---
 data/classic.json.in | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/data/classic.json.in b/data/classic.json.in
index fdb3762..c1c0544 100644
--- a/data/classic.json.in
+++ b/data/classic.json.in
@@ -1,8 +1,9 @@
 {
     "parentMode": "user",
     "stylesheetName": "gnome-classic.css",
+    "hasOverview": false,
     "enabledExtensions": [@CLASSIC_EXTENSIONS@],
-    "panel": { "left": ["activities", "appMenu"],
+    "panel": { "left": ["appMenu"],
                "center": [],
                "right": ["a11y", "keyboard", "dateMenu", "aggregateMenu"]
              }
-- 
2.21.0


From 6277f55210a2f1a3283b3eaad7ae1f2cf2f48d8b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 14 May 2019 19:51:22 +0200
Subject: [PATCH 08/30] window-list: Add window picker button
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

With the latest changes, GNOME Classic has become so classic that it
is bordering dull. Salvage at least a tiny piece of GNOME 3 in form
of a window-pick button which toggles an exposé-like reduced overview.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/73
---
 extensions/window-list/classic.css     |  20 +-
 extensions/window-list/extension.js    |  36 +++-
 extensions/window-list/meson.build     |   2 +-
 extensions/window-list/stylesheet.css  |  27 ++-
 extensions/window-list/windowPicker.js | 260 +++++++++++++++++++++++++
 5 files changed, 332 insertions(+), 13 deletions(-)
 create mode 100644 extensions/window-list/windowPicker.js

diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css
index f3c44a3..c506bea 100644
--- a/extensions/window-list/classic.css
+++ b/extensions/window-list/classic.css
@@ -6,14 +6,13 @@
     height: 2.25em ;
   }
 
-  .bottom-panel .window-button > StWidget {
+  .bottom-panel .window-button > StWidget,
+  .bottom-panel .window-picker-toggle > StWidget {
     background-gradient-drection: vertical;
     background-color: #fff;
     background-gradient-start: #fff;
     background-gradient-end: #eee;
     color: #000;
-    -st-natural-width: 18.7em;
-    max-width: 18.75em;
     color: #2e3436;
     background-color: #eee;
     border-radius: 2px;
@@ -22,7 +21,17 @@
     text-shadow: 0 0 transparent;
   }
 
-  .bottom-panel .window-button:hover > StWidget {
+  .bottom-panel .window-button > StWidget {
+    -st-natural-width: 18.7em;
+    max-width: 18.75em;
+  }
+
+  .bottom-panel .window-picker-toggle > StWidet {
+    border: 1px solid rgba(0,0,0,0.3);
+  }
+
+  .bottom-panel .window-button:hover > StWidget,
+  .bottom-panel .window-picker-toggle:hover > StWidget {
     background-color: #f9f9f9;
   }
 
@@ -31,7 +40,8 @@
     box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5);
   }
 
-  .bottom-panel .window-button.focused > StWidget {
+  .bottom-panel .window-button.focused > StWidget,
+  .bottom-panel .window-picker-toggle:checked > StWidget {
     background-color: #ddd;
     box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5);
   }
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index e1ea742..b2784b4 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -3,11 +3,14 @@ const { Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St } = imports.gi;
 
 const DND = imports.ui.dnd;
 const Main = imports.ui.main;
+const Overview = imports.ui.overview;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
+const Tweener = imports.ui.tweener;
 
 const ExtensionUtils = imports.misc.extensionUtils;
 const Me = ExtensionUtils.getCurrentExtension();
+const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker;
 
 const Gettext = imports.gettext.domain('gnome-shell-extensions');
 const _ = Gettext.gettext;
@@ -787,6 +790,12 @@ class WindowList {
         let box = new St.BoxLayout({ x_expand: true, y_expand: true });
         this.actor.add_actor(box);
 
+        let toggle = new WindowPickerToggle();
+        box.add_actor(toggle);
+
+        toggle.connect('notify::checked',
+            this._updateWindowListVisibility.bind(this));
+
         let layout = new Clutter.BoxLayout({ homogeneous: true });
         this._windowList = new St.Widget({
             style_class: 'window-list',
@@ -936,6 +945,19 @@ class WindowList {
         this._workspaceIndicator.actor.visible = hasWorkspaces && workspacesOnMonitor;
     }
 
+    _updateWindowListVisibility() {
+        let visible = !Main.windowPicker.visible;
+
+        Tweener.addTween(this._windowList, {
+            opacity: visible ? 255 : 0,
+            transition: 'ease-out-quad',
+            time: Overview.ANIMATION_TIME
+        });
+
+        this._windowList.reactive = visible;
+        this._windowList.get_children().forEach(c => c.reactive = visible);
+    }
+
     _getPreferredUngroupedWindowListWidth() {
         if (this._windowList.get_n_children() == 0)
             return this._windowList.get_preferred_width(-1)[1];
@@ -1206,7 +1228,7 @@ class WindowList {
 class Extension {
     constructor() {
         this._windowLists = null;
-        this._injections = {};
+        this._hideOverviewOrig = Main.overview.hide;
     }
 
     enable() {
@@ -1221,6 +1243,13 @@ class Extension {
             Main.layoutManager.connect('monitors-changed',
                                        this._buildWindowLists.bind(this));
 
+        Main.windowPicker = new WindowPicker();
+
+        Main.overview.hide = () => {
+            Main.windowPicker.close();
+            this._hideOverviewOrig.call(Main.overview);
+        };
+
         this._buildWindowLists();
     }
 
@@ -1251,6 +1280,11 @@ class Extension {
             windowList.actor.destroy();
         });
         this._windowLists = null;
+
+        Main.windowPicker.actor.destroy();
+        delete Main.windowPicker;
+
+        Main.overview.hide = this._hideOverviewOrig;
     }
 
     someWindowListContains(actor) {
diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build
index b4aa4db..5b1f5f5 100644
--- a/extensions/window-list/meson.build
+++ b/extensions/window-list/meson.build
@@ -4,7 +4,7 @@ extension_data += configure_file(
   configuration: metadata_conf
 )
 
-extension_sources += files('prefs.js')
+extension_sources += files('prefs.js', 'windowPicker.js')
 extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
 
 if classic_mode_enabled
diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
index f5285cb..91383ab 100644
--- a/extensions/window-list/stylesheet.css
+++ b/extensions/window-list/stylesheet.css
@@ -26,9 +26,8 @@
   spacing: 4px;
 }
 
-.window-button > StWidget {
-  -st-natural-width: 18.75em;
-  max-width: 18.75em;
+.window-button > StWidget,
+.window-picker-toggle > StWidget {
   color: #bbb;
   background-color: black;
   border-radius: 4px;
@@ -37,7 +36,21 @@
   text-shadow: 1px 1px 4px rgba(0,0,0,0.8);
 }
 
-.window-button:hover > StWidget {
+.window-picker-toggle {
+  padding: 3px;
+}
+
+.window-picker-toggle > StWidet {
+  border: 1px solid rgba(255,255,255,0.3);
+}
+
+.window-button > StWidget {
+  -st-natural-width: 18.75em;
+  max-width: 18.75em;
+}
+
+.window-button:hover > StWidget,
+.window-picker-toggle:hover > StWidget {
   color: white;
   background-color: #1f1f1f;
 }
@@ -47,12 +60,14 @@
   box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5);
 }
 
-.window-button.focused > StWidget {
+.window-button.focused > StWidget,
+.window-picker-toggle:checked > StWidget {
   color: white;
   box-shadow: inset 1px 1px 4px rgba(255,255,255,0.7);
 }
 
-.window-button.focused:active > StWidget {
+.window-button.focused:active > StWidget,
+.window-picker-toggle:checked:active > StWidget {
   box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7);
 }
 
diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js
new file mode 100644
index 0000000..024fd80
--- /dev/null
+++ b/extensions/window-list/windowPicker.js
@@ -0,0 +1,260 @@
+/* exported WindowPicker, WindowPickerToggle */
+const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi;
+const Signals = imports.signals;
+
+const Layout = imports.ui.layout;
+const Main = imports.ui.main;
+const Overview = imports.ui.overview;
+const { WorkspacesDisplay } = imports.ui.workspacesView;
+
+let MyWorkspacesDisplay = class extends WorkspacesDisplay {
+    constructor() {
+        super();
+
+        this.actor.add_constraint(
+            new Layout.MonitorConstraint({
+                primary: true,
+                work_area: true
+            }));
+
+        this.actor.connect('destroy', this._onDestroy.bind(this));
+
+        this._workareasChangedId = global.display.connect('workareas-changed',
+            this._onWorkAreasChanged.bind(this));
+        this._onWorkAreasChanged();
+    }
+
+    show(...args) {
+        if (this._scrollEventId == 0)
+            this._scrollEventId = Main.windowPicker.connect('scroll-event',
+                this._onScrollEvent.bind(this));
+
+        super.show(...args);
+    }
+
+    hide(...args) {
+        if (this._scrollEventId > 0)
+            Main.windowPicker.disconnect(this._scrollEventId);
+        this._scrollEventId = 0;
+
+        super.hide(...args);
+    }
+
+    _onWorkAreasChanged() {
+        let { primaryIndex } = Main.layoutManager;
+        let workarea = Main.layoutManager.getWorkAreaForMonitor(primaryIndex);
+        this.setWorkspacesFullGeometry(workarea);
+    }
+
+    _updateWorkspacesViews() {
+        super._updateWorkspacesViews();
+
+        this._workspacesViews.forEach(v => {
+            Main.layoutManager.overviewGroup.remove_actor(v.actor);
+            Main.windowPicker.actor.add_actor(v.actor);
+        });
+    }
+
+    _onDestroy() {
+        if (this._workareasChangedId)
+            global.display.disconnect(this._workareasChangedId);
+        this._workareasChangedId = 0;
+    }
+};
+
+var WindowPicker = class {
+    constructor() {
+        this._visible = false;
+        this._modal = false;
+
+        this.actor = new Clutter.Actor();
+
+        this.actor.connect('destroy', this._onDestroy.bind(this));
+
+        global.bind_property('screen-width',
+            this.actor, 'width',
+            GObject.BindingFlags.SYNC_CREATE);
+        global.bind_property('screen-height',
+            this.actor, 'height',
+            GObject.BindingFlags.SYNC_CREATE);
+
+        this._backgroundGroup = new Meta.BackgroundGroup({ reactive: true });
+        this.actor.add_child(this._backgroundGroup);
+
+        this._backgroundGroup.connect('scroll-event', (a, ev) => {
+            this.emit('scroll-event', ev);
+        });
+
+        // Trick WorkspacesDisplay constructor into adding actions here
+        let addActionOrig = Main.overview.addAction;
+        Main.overview.addAction = a => this._backgroundGroup.add_action(a);
+
+        this._workspacesDisplay = new MyWorkspacesDisplay();
+        this.actor.add_child(this._workspacesDisplay.actor);
+
+        Main.overview.addAction = addActionOrig;
+
+        this._bgManagers = [];
+
+        this._monitorsChangedId = Main.layoutManager.connect('monitors-changed',
+            this._updateBackgrounds.bind(this));
+        this._updateBackgrounds();
+
+        Main.uiGroup.insert_child_below(this.actor, global.window_group);
+    }
+
+    get visible() {
+        return this._visible;
+    }
+
+    open() {
+        if (this._visible)
+            return;
+
+        this._visible = true;
+
+        if (!this._syncGrab())
+            return;
+
+        this._fakeOverviewVisible(true);
+        this._shadeBackgrounds();
+        this._fakeOverviewAnimation();
+        this._workspacesDisplay.show(false);
+
+        this.emit('open-state-changed', this._visible);
+    }
+
+    close() {
+        if (!this._visible)
+            return;
+
+        this._visible = false;
+
+        if (!this._syncGrab())
+            return;
+
+        this._workspacesDisplay.animateFromOverview(false);
+        this._unshadeBackgrounds();
+        this._fakeOverviewAnimation(() => {
+            this._workspacesDisplay.hide();
+            this._fakeOverviewVisible(false);
+        });
+
+        this.emit('open-state-changed', this._visible);
+    }
+
+    _fakeOverviewAnimation(onComplete) {
+        Main.overview.animationInProgress = true;
+        GLib.timeout_add(
+            GLib.PRIORITY_DEFAULT,
+            Overview.ANIMATION_TIME * 1000,
+            () => {
+                Main.overview.animationInProgress = false;
+                if (onComplete)
+                    onComplete();
+            });
+    }
+
+    _fakeOverviewVisible(visible) {
+        // Fake overview state for WorkspacesDisplay
+        Main.overview.visible = visible;
+
+        // Hide real windows
+        Main.layoutManager._inOverview = visible;
+        Main.layoutManager._updateVisibility();
+    }
+
+    _syncGrab() {
+        if (this._visible) {
+            if (this._modal)
+                return true;
+
+            this._modal = Main.pushModal(this.actor, {
+                actionMode: Shell.ActionMode.OVERVIEW
+            });
+
+            if (!this._modal) {
+                this.hide();
+                return false;
+            }
+        } else if (this._modal) {
+            Main.popModal(this.actor);
+            this._modal = false;
+        }
+        return true;
+    }
+
+    _onDestroy() {
+        if (this._monitorsChangedId)
+            Main.layoutManager.disconnect(this._monitorsChangedId);
+        this._monitorsChangedId = 0;
+    }
+
+    _updateBackgrounds() {
+        Main.overview._updateBackgrounds.call(this);
+    }
+
+    _shadeBackgrounds() {
+        Main.overview._shadeBackgrounds.call(this);
+    }
+
+    _unshadeBackgrounds() {
+        Main.overview._unshadeBackgrounds.call(this);
+    }
+};
+Signals.addSignalMethods(WindowPicker.prototype);
+
+var WindowPickerToggle = GObject.registerClass(
+class WindowPickerToggle extends St.Button {
+    _init() {
+        let iconBin = new St.Widget({
+            layout_manager: new Clutter.BinLayout()
+        });
+        iconBin.add_child(new St.Icon({
+            icon_name: 'focus-windows-symbolic',
+            icon_size: 16,
+            x_expand: true,
+            y_expand: true,
+            x_align: Clutter.ActorAlign.CENTER,
+            y_align: Clutter.ActorAlign.CENTER
+        }));
+        super._init({
+            style_class: 'window-picker-toggle',
+            child: iconBin,
+            visible: !Main.sessionMode.hasOverview,
+            x_fill: true,
+            y_fill: true,
+            toggle_mode: true
+        });
+
+        this._overlayKeyId = 0;
+
+        this.connect('destroy', this._onDestroy.bind(this));
+
+        this.connect('notify::checked', () => {
+            if (this.checked)
+                Main.windowPicker.open();
+            else
+                Main.windowPicker.close();
+        });
+
+        if (!Main.sessionMode.hasOverview) {
+            this._overlayKeyId = global.display.connect('overlay-key', () => {
+                if (!Main.windowPicker.visible)
+                    Main.windowPicker.open();
+                else
+                    Main.windowPicker.close();
+            });
+        }
+
+        Main.windowPicker.connect('open-state-changed', () => {
+            this.checked = Main.windowPicker.visible;
+        });
+    }
+
+    _onDestroy() {
+        if (this._overlayKeyId)
+            global.display.disconnect(this._overlayKeyId);
+        this._overlayKeyId == 0;
+    }
+});
-- 
2.21.0


From 5d82db41ac53a1c7462e1ee69b155588b3e321fc Mon Sep 17 00:00:00 2001
From: Jakub Steiner <jimmac@gmail.com>
Date: Mon, 15 Jul 2019 23:03:41 +0200
Subject: [PATCH 09/30] classic: Update window-list styling

Make buttons flatter, rounder to match default styling.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/82
---
 extensions/window-list/classic.css | 25 +++++++++----------------
 1 file changed, 9 insertions(+), 16 deletions(-)

diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css
index c506bea..cc967e0 100644
--- a/extensions/window-list/classic.css
+++ b/extensions/window-list/classic.css
@@ -4,21 +4,18 @@
     border-top-width: 1px;
     border-bottom-width: 0px;
     height: 2.25em ;
+    padding: 2px;
   }
 
   .bottom-panel .window-button > StWidget,
   .bottom-panel .window-picker-toggle > StWidget {
-    background-gradient-drection: vertical;
-    background-color: #fff;
-    background-gradient-start: #fff;
-    background-gradient-end: #eee;
-    color: #000;
     color: #2e3436;
     background-color: #eee;
-    border-radius: 2px;
+    border-radius: 3px;
     padding: 3px 6px 1px;
-    box-shadow: inset -1px -1px 1px rgba(0,0,0,0.5);
-    text-shadow: 0 0 transparent;
+    box-shadow: none;
+    text-shadow: none;
+    border: 1px solid rgba(0,0,0,0.2);
   }
 
   .bottom-panel .window-button > StWidget {
@@ -26,10 +23,6 @@
     max-width: 18.75em;
   }
 
-  .bottom-panel .window-picker-toggle > StWidet {
-    border: 1px solid rgba(0,0,0,0.3);
-  }
-
   .bottom-panel .window-button:hover > StWidget,
   .bottom-panel .window-picker-toggle:hover > StWidget {
     background-color: #f9f9f9;
@@ -37,13 +30,13 @@
 
   .bottom-panel .window-button:active > StWidget,
   .bottom-panel .window-button:focus > StWidget {
-    box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5);
+    box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
   }
 
   .bottom-panel .window-button.focused > StWidget,
   .bottom-panel .window-picker-toggle:checked > StWidget {
-    background-color: #ddd;
-    box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5);
+    background-color: #ccc;
+    box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
   }
 
   .bottom-panel .window-button.focused:hover > StWidget {
@@ -52,5 +45,5 @@
 
   .bottom-panel .window-button.minimized > StWidget {
     color: #888;
-    box-shadow: inset -1px -1px 1px rgba(0,0,0,0.5);
+    box-shadow: none;
   }
-- 
2.21.0


From 479a8907063970fb6f7e08cea9a2cd6f2e518b84 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 5 Jun 2019 00:23:13 +0000
Subject: [PATCH 10/30] window-list: Split out workspaceIndicator

The extension has grown unwieldily big, so before starting to improve
on the workspace indicator, move it to its own source file.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70
---
 extensions/window-list/extension.js          | 130 +-----------------
 extensions/window-list/meson.build           |   2 +-
 extensions/window-list/workspaceIndicator.js | 135 +++++++++++++++++++
 3 files changed, 138 insertions(+), 129 deletions(-)
 create mode 100644 extensions/window-list/workspaceIndicator.js

diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
index b2784b4..1f854aa 100644
--- a/extensions/window-list/extension.js
+++ b/extensions/window-list/extension.js
@@ -1,16 +1,16 @@
 /* exported init */
-const { Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St } = imports.gi;
+const { Clutter, Gio, GLib, Gtk, Meta, Shell, St } = imports.gi;
 
 const DND = imports.ui.dnd;
 const Main = imports.ui.main;
 const Overview = imports.ui.overview;
-const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
 const Tweener = imports.ui.tweener;
 
 const ExtensionUtils = imports.misc.extensionUtils;
 const Me = ExtensionUtils.getCurrentExtension();
 const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker;
+const { WorkspaceIndicator } = Me.imports.workspaceIndicator;
 
 const Gettext = imports.gettext.domain('gnome-shell-extensions');
 const _ = Gettext.gettext;
@@ -647,132 +647,6 @@ class AppButton extends BaseButton {
 }
 
 
-let WorkspaceIndicator = GObject.registerClass(
-class WorkspaceIndicator extends PanelMenu.Button {
-    _init() {
-        super._init(0.0, _('Workspace Indicator'), true);
-        this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM));
-        this.add_style_class_name('window-list-workspace-indicator');
-        this.menu.actor.remove_style_class_name('panel-menu');
-
-        let container = new St.Widget({
-            layout_manager: new Clutter.BinLayout(),
-            x_expand: true,
-            y_expand: true
-        });
-        this.add_actor(container);
-
-        let workspaceManager = global.workspace_manager;
-
-        this._currentWorkspace = workspaceManager.get_active_workspace().index();
-        this.statusLabel = new St.Label({
-            text: this._getStatusText(),
-            x_align: Clutter.ActorAlign.CENTER,
-            y_align: Clutter.ActorAlign.CENTER
-        });
-        container.add_actor(this.statusLabel);
-
-        this.workspacesItems = [];
-
-        this._workspaceManagerSignals = [];
-        this._workspaceManagerSignals.push(workspaceManager.connect('notify::n-workspaces',
-                                                                    this._updateMenu.bind(this)));
-        this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-switched',
-                                                                          this._updateIndicator.bind(this)));
-
-        this.connect('scroll-event', this._onScrollEvent.bind(this));
-        this._updateMenu();
-
-        this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' });
-        this._settingsChangedId =
-            this._settings.connect('changed::workspace-names',
-                                   this._updateMenu.bind(this));
-    }
-
-    _onDestroy() {
-        for (let i = 0; i < this._workspaceManagerSignals.length; i++)
-            global.workspace_manager.disconnect(this._workspaceManagerSignals[i]);
-
-        if (this._settingsChangedId) {
-            this._settings.disconnect(this._settingsChangedId);
-            this._settingsChangedId = 0;
-        }
-
-        super._onDestroy();
-    }
-
-    _updateIndicator() {
-        this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
-        this._currentWorkspace = global.workspace_manager.get_active_workspace().index();
-        this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
-
-        this.statusLabel.set_text(this._getStatusText());
-    }
-
-    _getStatusText() {
-        let workspaceManager = global.workspace_manager;
-        let current = workspaceManager.get_active_workspace().index();
-        let total = workspaceManager.n_workspaces;
-
-        return '%d / %d'.format(current + 1, total);
-    }
-
-    _updateMenu() {
-        let workspaceManager = global.workspace_manager;
-
-        this.menu.removeAll();
-        this.workspacesItems = [];
-        this._currentWorkspace = workspaceManager.get_active_workspace().index();
-
-        for (let i = 0; i < workspaceManager.n_workspaces; i++) {
-            let name = Meta.prefs_get_workspace_name(i);
-            let item = new PopupMenu.PopupMenuItem(name);
-            item.workspaceId = i;
-
-            item.connect('activate', (item, _event) => {
-                this._activate(item.workspaceId);
-            });
-
-            if (i == this._currentWorkspace)
-                item.setOrnament(PopupMenu.Ornament.DOT);
-
-            this.menu.addMenuItem(item);
-            this.workspacesItems[i] = item;
-        }
-
-        this.statusLabel.set_text(this._getStatusText());
-    }
-
-    _activate(index) {
-        let workspaceManager = global.workspace_manager;
-
-        if (index >= 0 && index < workspaceManager.n_workspaces) {
-            let metaWorkspace = workspaceManager.get_workspace_by_index(index);
-            metaWorkspace.activate(global.get_current_time());
-        }
-    }
-
-    _onScrollEvent(actor, event) {
-        let direction = event.get_scroll_direction();
-        let diff = 0;
-        if (direction == Clutter.ScrollDirection.DOWN) {
-            diff = 1;
-        } else if (direction == Clutter.ScrollDirection.UP) {
-            diff = -1;
-        } else {
-            return;
-        }
-
-        let newIndex = this._currentWorkspace + diff;
-        this._activate(newIndex);
-    }
-
-    _allocate(actor, box, flags) {
-        if (actor.get_n_children() > 0)
-            actor.get_first_child().allocate(box, flags);
-    }
-});
-
 class WindowList {
     constructor(perMonitor, monitor) {
         this._perMonitor = perMonitor;
diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build
index 5b1f5f5..34d7c3f 100644
--- a/extensions/window-list/meson.build
+++ b/extensions/window-list/meson.build
@@ -4,7 +4,7 @@ extension_data += configure_file(
   configuration: metadata_conf
 )
 
-extension_sources += files('prefs.js', 'windowPicker.js')
+extension_sources += files('prefs.js', 'windowPicker.js', 'workspaceIndicator.js')
 extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml')
 
 if classic_mode_enabled
diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
new file mode 100644
index 0000000..fb3ffe7
--- /dev/null
+++ b/extensions/window-list/workspaceIndicator.js
@@ -0,0 +1,135 @@
+/* exported WorkspaceIndicator */
+const { Clutter, Gio, GObject, Meta, St } = imports.gi;
+
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+
+const Gettext = imports.gettext.domain('gnome-shell-extensions');
+const _ = Gettext.gettext;
+
+var WorkspaceIndicator = GObject.registerClass(
+class WorkspaceIndicator extends PanelMenu.Button {
+    _init() {
+        super._init(0.0, _('Workspace Indicator'), true);
+        this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM));
+        this.add_style_class_name('window-list-workspace-indicator');
+        this.menu.actor.remove_style_class_name('panel-menu');
+
+        let container = new St.Widget({
+            layout_manager: new Clutter.BinLayout(),
+            x_expand: true,
+            y_expand: true
+        });
+        this.add_actor(container);
+
+        let workspaceManager = global.workspace_manager;
+
+        this._currentWorkspace = workspaceManager.get_active_workspace().index();
+        this.statusLabel = new St.Label({
+            text: this._getStatusText(),
+            x_align: Clutter.ActorAlign.CENTER,
+            y_align: Clutter.ActorAlign.CENTER
+        });
+        container.add_actor(this.statusLabel);
+
+        this.workspacesItems = [];
+
+        this._workspaceManagerSignals = [
+            workspaceManager.connect('notify::n-workspaces',
+                this._updateMenu.bind(this)),
+            workspaceManager.connect_after('workspace-switched',
+                this._updateIndicator.bind(this))
+        ];
+
+        this.connect('scroll-event', this._onScrollEvent.bind(this));
+        this._updateMenu();
+
+        this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' });
+        this._settingsChangedId = this._settings.connect(
+            'changed::workspace-names', this._updateMenu.bind(this));
+    }
+
+    _onDestroy() {
+        for (let i = 0; i < this._workspaceManagerSignals.length; i++)
+            global.workspace_manager.disconnect(this._workspaceManagerSignals[i]);
+
+        if (this._settingsChangedId) {
+            this._settings.disconnect(this._settingsChangedId);
+            this._settingsChangedId = 0;
+        }
+
+        super._onDestroy();
+    }
+
+    _updateIndicator() {
+        this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
+        this._currentWorkspace = global.workspace_manager.get_active_workspace().index();
+        this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
+
+        this.statusLabel.set_text(this._getStatusText());
+    }
+
+    _getStatusText() {
+        let workspaceManager = global.workspace_manager;
+        let current = workspaceManager.get_active_workspace().index();
+        let total = workspaceManager.n_workspaces;
+
+        return '%d / %d'.format(current + 1, total);
+    }
+
+    _updateMenu() {
+        let workspaceManager = global.workspace_manager;
+
+        this.menu.removeAll();
+        this.workspacesItems = [];
+        this._currentWorkspace = workspaceManager.get_active_workspace().index();
+
+        for (let i = 0; i < workspaceManager.n_workspaces; i++) {
+            let name = Meta.prefs_get_workspace_name(i);
+            let item = new PopupMenu.PopupMenuItem(name);
+            item.workspaceId = i;
+
+            item.connect('activate', (item, _event) => {
+                this._activate(item.workspaceId);
+            });
+
+            if (i == this._currentWorkspace)
+                item.setOrnament(PopupMenu.Ornament.DOT);
+
+            this.menu.addMenuItem(item);
+            this.workspacesItems[i] = item;
+        }
+
+        this.statusLabel.set_text(this._getStatusText());
+    }
+
+    _activate(index) {
+        let workspaceManager = global.workspace_manager;
+
+        if (index >= 0 && index < workspaceManager.n_workspaces) {
+            let metaWorkspace = workspaceManager.get_workspace_by_index(index);
+            metaWorkspace.activate(global.get_current_time());
+        }
+    }
+
+    _onScrollEvent(actor, event) {
+        let direction = event.get_scroll_direction();
+        let diff = 0;
+        if (direction == Clutter.ScrollDirection.DOWN) {
+            diff = 1;
+        } else if (direction == Clutter.ScrollDirection.UP) {
+            diff = -1;
+        } else {
+            return;
+        }
+
+        let newIndex = this._currentWorkspace + diff;
+        this._activate(newIndex);
+    }
+
+    _allocate(actor, box, flags) {
+        if (actor.get_n_children() > 0)
+            actor.get_first_child().allocate(box, flags);
+    }
+});
+
-- 
2.21.0


From 3380c9ca5c85a61101bf916a2b326c478d83475e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 5 Jun 2019 17:39:53 +0000
Subject: [PATCH 11/30] window-list: Use a more specific GTypeName for
 workspace indicator

Now that the class inherits from GObject, the generic name easily
conflicts with other classes otherwise, for example with the one
from the workspace-indicator extension.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70
---
 extensions/window-list/workspaceIndicator.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
index fb3ffe7..8ac43eb 100644
--- a/extensions/window-list/workspaceIndicator.js
+++ b/extensions/window-list/workspaceIndicator.js
@@ -7,8 +7,9 @@ const PopupMenu = imports.ui.popupMenu;
 const Gettext = imports.gettext.domain('gnome-shell-extensions');
 const _ = Gettext.gettext;
 
-var WorkspaceIndicator = GObject.registerClass(
-class WorkspaceIndicator extends PanelMenu.Button {
+var WorkspaceIndicator = GObject.registerClass({
+    GTypeName: 'WindowListWorkspaceIndicator'
+}, class WorkspaceIndicator extends PanelMenu.Button {
     _init() {
         super._init(0.0, _('Workspace Indicator'), true);
         this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM));
-- 
2.21.0


From ad21dfc00c53420794f940e8710b60d2dd25bb9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 5 Jun 2019 04:54:50 +0200
Subject: [PATCH 12/30] window-list: Make some properties private

There's no reason why they should be public.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70
---
 extensions/window-list/workspaceIndicator.js | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
index 8ac43eb..7c0360a 100644
--- a/extensions/window-list/workspaceIndicator.js
+++ b/extensions/window-list/workspaceIndicator.js
@@ -26,14 +26,14 @@ var WorkspaceIndicator = GObject.registerClass({
         let workspaceManager = global.workspace_manager;
 
         this._currentWorkspace = workspaceManager.get_active_workspace().index();
-        this.statusLabel = new St.Label({
+        this._statusLabel = new St.Label({
             text: this._getStatusText(),
             x_align: Clutter.ActorAlign.CENTER,
             y_align: Clutter.ActorAlign.CENTER
         });
-        container.add_actor(this.statusLabel);
+        container.add_actor(this._statusLabel);
 
-        this.workspacesItems = [];
+        this._workspacesItems = [];
 
         this._workspaceManagerSignals = [
             workspaceManager.connect('notify::n-workspaces',
@@ -63,11 +63,11 @@ var WorkspaceIndicator = GObject.registerClass({
     }
 
     _updateIndicator() {
-        this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
+        this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
         this._currentWorkspace = global.workspace_manager.get_active_workspace().index();
-        this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
+        this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
 
-        this.statusLabel.set_text(this._getStatusText());
+        this._statusLabel.set_text(this._getStatusText());
     }
 
     _getStatusText() {
@@ -82,7 +82,7 @@ var WorkspaceIndicator = GObject.registerClass({
         let workspaceManager = global.workspace_manager;
 
         this.menu.removeAll();
-        this.workspacesItems = [];
+        this._workspacesItems = [];
         this._currentWorkspace = workspaceManager.get_active_workspace().index();
 
         for (let i = 0; i < workspaceManager.n_workspaces; i++) {
@@ -98,10 +98,10 @@ var WorkspaceIndicator = GObject.registerClass({
                 item.setOrnament(PopupMenu.Ornament.DOT);
 
             this.menu.addMenuItem(item);
-            this.workspacesItems[i] = item;
+            this._workspacesItems[i] = item;
         }
 
-        this.statusLabel.set_text(this._getStatusText());
+        this._statusLabel.set_text(this._getStatusText());
     }
 
     _activate(index) {
-- 
2.21.0


From 14515b02845a6fe53c9a76eefc3359183dd8076f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 5 Jun 2019 04:57:39 +0200
Subject: [PATCH 13/30] window-list: Update workspace names in-place

There's no good reason to rebuild the entire menu on workspace names
changes, we can simply update the labels in-place.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70
---
 extensions/window-list/workspaceIndicator.js | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
index 7c0360a..9888838 100644
--- a/extensions/window-list/workspaceIndicator.js
+++ b/extensions/window-list/workspaceIndicator.js
@@ -47,7 +47,7 @@ var WorkspaceIndicator = GObject.registerClass({
 
         this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' });
         this._settingsChangedId = this._settings.connect(
-            'changed::workspace-names', this._updateMenu.bind(this));
+            'changed::workspace-names', this._updateMenuLabels.bind(this));
     }
 
     _onDestroy() {
@@ -78,6 +78,14 @@ var WorkspaceIndicator = GObject.registerClass({
         return '%d / %d'.format(current + 1, total);
     }
 
+    _updateMenuLabels() {
+        for (let i = 0; i < this._workspacesItems.length; i++) {
+            let item = this._workspacesItems[i];
+            let name = Meta.prefs_get_workspace_name(i);
+            item.label.text = name;
+        }
+    }
+
     _updateMenu() {
         let workspaceManager = global.workspace_manager;
 
-- 
2.21.0


From 7ecdd7d2c61694df8ca9f76016bd8113bfbd7b51 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 5 Jun 2019 04:59:19 +0200
Subject: [PATCH 14/30] window-list: Minor cleanup

Mutter has a dedicated method for getting the index of the active
workspace, use that instead of getting first the active workspace
and then its index.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70
---
 extensions/window-list/workspaceIndicator.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
index 9888838..1f2e1c1 100644
--- a/extensions/window-list/workspaceIndicator.js
+++ b/extensions/window-list/workspaceIndicator.js
@@ -25,7 +25,7 @@ var WorkspaceIndicator = GObject.registerClass({
 
         let workspaceManager = global.workspace_manager;
 
-        this._currentWorkspace = workspaceManager.get_active_workspace().index();
+        this._currentWorkspace = workspaceManager.get_active_workspace_index();
         this._statusLabel = new St.Label({
             text: this._getStatusText(),
             x_align: Clutter.ActorAlign.CENTER,
@@ -64,7 +64,7 @@ var WorkspaceIndicator = GObject.registerClass({
 
     _updateIndicator() {
         this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
-        this._currentWorkspace = global.workspace_manager.get_active_workspace().index();
+        this._currentWorkspace = global.workspace_manager.get_active_workspace_index();
         this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
 
         this._statusLabel.set_text(this._getStatusText());
@@ -72,7 +72,7 @@ var WorkspaceIndicator = GObject.registerClass({
 
     _getStatusText() {
         let workspaceManager = global.workspace_manager;
-        let current = workspaceManager.get_active_workspace().index();
+        let current = workspaceManager.get_active_workspace_index();
         let total = workspaceManager.n_workspaces;
 
         return '%d / %d'.format(current + 1, total);
@@ -91,7 +91,7 @@ var WorkspaceIndicator = GObject.registerClass({
 
         this.menu.removeAll();
         this._workspacesItems = [];
-        this._currentWorkspace = workspaceManager.get_active_workspace().index();
+        this._currentWorkspace = workspaceManager.get_active_workspace_index();
 
         for (let i = 0; i < workspaceManager.n_workspaces; i++) {
             let name = Meta.prefs_get_workspace_name(i);
-- 
2.21.0


From 42ef09c097b297ee68207c4f439ec40e5998baa0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 5 Jun 2019 05:11:34 +0200
Subject: [PATCH 15/30] window-list: Improve workspace label styling

The border currently looks off - it extends all the way vertically
and leaves zero spacing to the label horizontally. Fix both issues
by setting appropriate padding/margins.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70
---
 extensions/window-list/stylesheet.css        |  8 +++-----
 extensions/window-list/workspaceIndicator.js | 13 ++++++++-----
 2 files changed, 11 insertions(+), 10 deletions(-)

diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
index 91383ab..bab8f76 100644
--- a/extensions/window-list/stylesheet.css
+++ b/extensions/window-list/stylesheet.css
@@ -85,13 +85,11 @@
   height: 24px;
 }
 
-.window-list-workspace-indicator {
-  padding: 3px;
-}
-
-.window-list-workspace-indicator > StWidget {
+.window-list-workspace-indicator .status-label-bin {
   background-color: rgba(200, 200, 200, .3);
   border: 1px solid #cccccc;
+  padding: 0 3px;
+  margin: 3px 0;
 }
 
 .notification {
diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
index 1f2e1c1..598c516 100644
--- a/extensions/window-list/workspaceIndicator.js
+++ b/extensions/window-list/workspaceIndicator.js
@@ -26,12 +26,15 @@ var WorkspaceIndicator = GObject.registerClass({
         let workspaceManager = global.workspace_manager;
 
         this._currentWorkspace = workspaceManager.get_active_workspace_index();
-        this._statusLabel = new St.Label({
-            text: this._getStatusText(),
-            x_align: Clutter.ActorAlign.CENTER,
-            y_align: Clutter.ActorAlign.CENTER
+        this._statusLabel = new St.Label({ text: this._getStatusText() });
+
+        this._statusBin = new St.Bin({
+            style_class: 'status-label-bin',
+            x_expand: true,
+            y_expand: true,
+            child: this._statusLabel
         });
-        container.add_actor(this._statusLabel);
+        container.add_actor(this._statusBin);
 
         this._workspacesItems = [];
 
-- 
2.21.0


From 0bf800b529ddfdc8663c63ff5fbf4ba5ac5d64d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 5 Jun 2019 05:08:31 +0200
Subject: [PATCH 16/30] window-list: Refactor workspace signal handlers

We are about to support a separate representation if horizontal
workspaces are used. To prepare for that, rename the handlers to
something more generic and split out menu-specific bits into a
dedicated help function.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70
---
 extensions/window-list/workspaceIndicator.js | 25 +++++++++++++++-----
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
index 598c516..78ca97e 100644
--- a/extensions/window-list/workspaceIndicator.js
+++ b/extensions/window-list/workspaceIndicator.js
@@ -40,9 +40,9 @@ var WorkspaceIndicator = GObject.registerClass({
 
         this._workspaceManagerSignals = [
             workspaceManager.connect('notify::n-workspaces',
-                this._updateMenu.bind(this)),
+                this._nWorkspacesChanged.bind(this)),
             workspaceManager.connect_after('workspace-switched',
-                this._updateIndicator.bind(this))
+                this._onWorkspaceSwitched.bind(this))
         ];
 
         this.connect('scroll-event', this._onScrollEvent.bind(this));
@@ -65,14 +65,27 @@ var WorkspaceIndicator = GObject.registerClass({
         super._onDestroy();
     }
 
-    _updateIndicator() {
-        this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
-        this._currentWorkspace = global.workspace_manager.get_active_workspace_index();
-        this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
+    _onWorkspaceSwitched() {
+        let workspaceManager = global.workspace_manager;
+        this._currentWorkspace = workspaceManager.get_active_workspace_index();
+
+        this._updateMenuOrnament();
 
         this._statusLabel.set_text(this._getStatusText());
     }
 
+    _nWorkspacesChanged() {
+        this._updateMenu();
+    }
+
+    _updateMenuOrnament() {
+        for (let i = 0; i < this._workspacesItems.length; i++) {
+            this._workspacesItems[i].setOrnament(i == this._currentWorkspace
+                ? PopupMenu.Ornament.DOT
+                : PopupMenu.Ornament.NONE);
+        }
+    }
+
     _getStatusText() {
         let workspaceManager = global.workspace_manager;
         let current = workspaceManager.get_active_workspace_index();
-- 
2.21.0


From 263aeea7be1e5bd244664bb3aefd9b81d8a80e32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 5 Jun 2019 02:53:38 +0000
Subject: [PATCH 17/30] window-list: Support horizontal workspace layout

Unlike in GNOME 2, the workspace indicator we display in the window list
isn't a workspace switcher, but a menu button that allows switching
workspaces via its menu. The reason for that is that a horizontal
in-place switcher would be at odds with the vertical workspace layout
used in GNOME 3.

However that reasoning doesn't apply when the layout is changed to a
horizontal one, so replace the button with a traditional workspace
switcher in that case.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70
---
 extensions/window-list/classic.css           |  9 +++
 extensions/window-list/stylesheet.css        | 29 +++++++++
 extensions/window-list/workspaceIndicator.js | 66 +++++++++++++++++++-
 3 files changed, 103 insertions(+), 1 deletion(-)

diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css
index cc967e0..c533473 100644
--- a/extensions/window-list/classic.css
+++ b/extensions/window-list/classic.css
@@ -47,3 +47,12 @@
     color: #888;
     box-shadow: none;
   }
+
+/* workspace switcher */
+.window-list-workspace-indicator .workspace {
+  background-color: #ddd;
+}
+
+.window-list-workspace-indicator .workspace.active {
+  background-color: #ccc;
+}
diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
index bab8f76..ad5978a 100644
--- a/extensions/window-list/stylesheet.css
+++ b/extensions/window-list/stylesheet.css
@@ -92,6 +92,35 @@
   margin: 3px 0;
 }
 
+.window-list-workspace-indicator .workspaces-box {
+  spacing: 3px;
+  padding: 3px;
+}
+
+.window-list-workspace-indicator .workspace {
+  border: 1px solid #cccccc;
+  width: 52px;
+}
+
+.window-list-workspace-indicator .workspace:first-child:last-child:ltr,
+.window-list-workspace-indicator .workspace:first-child:last-child:rtl {
+  border-radius: 4px;
+}
+
+.window-list-workspace-indicator .workspace:first-child:ltr,
+.window-list-workspace-indicator .workspace:last-child:rtl {
+  border-radius: 4px 0 0 4px;
+}
+
+.window-list-workspace-indicator .workspace:first-child:rtl,
+.window-list-workspace-indicator .workspace:last-child:ltr {
+  border-radius: 0 4px 4px 0;
+}
+
+.window-list-workspace-indicator .workspace.active {
+  background-color: rgba(200, 200, 200, .3);
+}
+
 .notification {
   font-weight: normal;
 }
diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
index 78ca97e..1258ed2 100644
--- a/extensions/window-list/workspaceIndicator.js
+++ b/extensions/window-list/workspaceIndicator.js
@@ -7,6 +7,25 @@ const PopupMenu = imports.ui.popupMenu;
 const Gettext = imports.gettext.domain('gnome-shell-extensions');
 const _ = Gettext.gettext;
 
+let WorkspaceThumbnail = GObject.registerClass({
+    GTypeName: 'WindowListWorkspaceThumbnail'
+}, class WorkspaceThumbnail extends St.Button {
+    _init(index) {
+        super._init({
+            style_class: 'workspace'
+        });
+
+        this._index = index;
+    }
+
+    // eslint-disable-next-line camelcase
+    on_clicked() {
+        let ws = global.workspace_manager.get_workspace_by_index(this._index);
+        if (ws)
+            ws.activate(global.get_current_time());
+    }
+});
+
 var WorkspaceIndicator = GObject.registerClass({
     GTypeName: 'WindowListWorkspaceIndicator'
 }, class WorkspaceIndicator extends PanelMenu.Button {
@@ -36,17 +55,30 @@ var WorkspaceIndicator = GObject.registerClass({
         });
         container.add_actor(this._statusBin);
 
+        this._thumbnailsBox = new St.BoxLayout({
+            style_class: 'workspaces-box',
+            y_expand: true,
+            reactive: true
+        });
+        this._thumbnailsBox.connect('scroll-event',
+            this._onScrollEvent.bind(this));
+        container.add_actor(this._thumbnailsBox);
+
         this._workspacesItems = [];
 
         this._workspaceManagerSignals = [
             workspaceManager.connect('notify::n-workspaces',
                 this._nWorkspacesChanged.bind(this)),
             workspaceManager.connect_after('workspace-switched',
-                this._onWorkspaceSwitched.bind(this))
+                this._onWorkspaceSwitched.bind(this)),
+            workspaceManager.connect('notify::layout-rows',
+                this._onWorkspaceOrientationChanged.bind(this))
         ];
 
         this.connect('scroll-event', this._onScrollEvent.bind(this));
         this._updateMenu();
+        this._updateThumbnails();
+        this._onWorkspaceOrientationChanged();
 
         this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' });
         this._settingsChangedId = this._settings.connect(
@@ -65,17 +97,27 @@ var WorkspaceIndicator = GObject.registerClass({
         super._onDestroy();
     }
 
+    _onWorkspaceOrientationChanged() {
+        let vertical = global.workspace_manager.layout_rows == -1;
+        this.reactive = vertical;
+
+        this._statusBin.visible = vertical;
+        this._thumbnailsBox.visible = !vertical;
+    }
+
     _onWorkspaceSwitched() {
         let workspaceManager = global.workspace_manager;
         this._currentWorkspace = workspaceManager.get_active_workspace_index();
 
         this._updateMenuOrnament();
+        this._updateActiveThumbnail();
 
         this._statusLabel.set_text(this._getStatusText());
     }
 
     _nWorkspacesChanged() {
         this._updateMenu();
+        this._updateThumbnails();
     }
 
     _updateMenuOrnament() {
@@ -86,6 +128,16 @@ var WorkspaceIndicator = GObject.registerClass({
         }
     }
 
+    _updateActiveThumbnail() {
+        let thumbs = this._thumbnailsBox.get_children();
+        for (let i = 0; i < thumbs.length; i++) {
+            if (i == this._currentWorkspace)
+                thumbs[i].add_style_class_name('active');
+            else
+                thumbs[i].remove_style_class_name('active');
+        }
+    }
+
     _getStatusText() {
         let workspaceManager = global.workspace_manager;
         let current = workspaceManager.get_active_workspace_index();
@@ -128,6 +180,18 @@ var WorkspaceIndicator = GObject.registerClass({
         this._statusLabel.set_text(this._getStatusText());
     }
 
+    _updateThumbnails() {
+        let workspaceManager = global.workspace_manager;
+
+        this._thumbnailsBox.destroy_all_children();
+
+        for (let i = 0; i < workspaceManager.n_workspaces; i++) {
+            let thumb = new WorkspaceThumbnail(i);
+            this._thumbnailsBox.add_actor(thumb);
+        }
+        this._updateActiveThumbnail();
+    }
+
     _activate(index) {
         let workspaceManager = global.workspace_manager;
 
-- 
2.21.0


From 2a2958831f51968089262b1e41bc908a69ef6834 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 5 Jun 2019 03:31:16 +0000
Subject: [PATCH 18/30] classic: Add 'horizontal-workspaces' extension

Vertical workspaces are another defining characteristics of GNOME 3,
and thus rather un-classic. That switch was driven by the overall
layout of the overview, and now that we disable the overview in
GNOME Classic, we can just return to the traditional workspace
layout as well.

Add a small extension that does just that.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/72
---
 extensions/horizontal-workspaces/extension.js  | 18 ++++++++++++++++++
 extensions/horizontal-workspaces/meson.build   |  5 +++++
 .../horizontal-workspaces/metadata.json.in     | 10 ++++++++++
 .../horizontal-workspaces/stylesheet.css       |  1 +
 meson.build                                    |  1 +
 5 files changed, 35 insertions(+)
 create mode 100644 extensions/horizontal-workspaces/extension.js
 create mode 100644 extensions/horizontal-workspaces/meson.build
 create mode 100644 extensions/horizontal-workspaces/metadata.json.in
 create mode 100644 extensions/horizontal-workspaces/stylesheet.css

diff --git a/extensions/horizontal-workspaces/extension.js b/extensions/horizontal-workspaces/extension.js
new file mode 100644
index 0000000..b3937ce
--- /dev/null
+++ b/extensions/horizontal-workspaces/extension.js
@@ -0,0 +1,18 @@
+/* exported enable disable */
+const { Meta } = imports.gi;
+
+function enable() {
+    global.workspace_manager.override_workspace_layout(
+        Meta.DisplayCorner.TOPLEFT,
+        false,
+        1,
+        -1);
+}
+
+function disable() {
+    global.workspace_manager.override_workspace_layout(
+        Meta.DisplayCorner.TOPLEFT,
+        false,
+        -1,
+        1);
+}
diff --git a/extensions/horizontal-workspaces/meson.build b/extensions/horizontal-workspaces/meson.build
new file mode 100644
index 0000000..48504f6
--- /dev/null
+++ b/extensions/horizontal-workspaces/meson.build
@@ -0,0 +1,5 @@
+extension_data += configure_file(
+  input: metadata_name + '.in',
+  output: metadata_name,
+  configuration: metadata_conf
+)
diff --git a/extensions/horizontal-workspaces/metadata.json.in b/extensions/horizontal-workspaces/metadata.json.in
new file mode 100644
index 0000000..f109e06
--- /dev/null
+++ b/extensions/horizontal-workspaces/metadata.json.in
@@ -0,0 +1,10 @@
+{
+"extension-id": "@extension_id@",
+"uuid": "@uuid@",
+"settings-schema": "@gschemaname@",
+"gettext-domain": "@gettext_domain@",
+"name": "Horizontal workspaces",
+"description": "Use a horizontal workspace layout",
+"shell-version": [ "@shell_current@" ],
+"url": "@url@"
+}
diff --git a/extensions/horizontal-workspaces/stylesheet.css b/extensions/horizontal-workspaces/stylesheet.css
new file mode 100644
index 0000000..25134b6
--- /dev/null
+++ b/extensions/horizontal-workspaces/stylesheet.css
@@ -0,0 +1 @@
+/* This extensions requires no special styling */
diff --git a/meson.build b/meson.build
index 32743ed..23bd5ad 100644
--- a/meson.build
+++ b/meson.build
@@ -34,6 +34,7 @@ uuid_suffix = '@gnome-shell-extensions.gcampax.github.com'
 classic_extensions = [
   'apps-menu',
   'desktop-icons',
+  'horizontal-workspaces',
   'places-menu',
   'launch-new-instance',
   'top-icons',
-- 
2.21.0


From 6a7b057028a1e4264312974d3ca21e16f5c07776 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 11 Jun 2019 23:01:20 +0000
Subject: [PATCH 19/30] window-list: Turn workspace thumbs into drop targets

It makes some sense to allow using the workspace indicator for moving
windows between workspaces as well as for workspace switching. This
applies particularly in GNOME classic after we disabled the overview
there, so that there is again a non-shortcut way of moving windows
between workspaces.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/74
---
 extensions/window-list/workspaceIndicator.js | 27 ++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
index 1258ed2..4831768 100644
--- a/extensions/window-list/workspaceIndicator.js
+++ b/extensions/window-list/workspaceIndicator.js
@@ -1,6 +1,8 @@
 /* exported WorkspaceIndicator */
 const { Clutter, Gio, GObject, Meta, St } = imports.gi;
 
+const DND = imports.ui.dnd;
+const Main = imports.ui.main;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
 
@@ -16,6 +18,31 @@ let WorkspaceThumbnail = GObject.registerClass({
         });
 
         this._index = index;
+        this._delegate = this; // needed for DND
+    }
+
+    acceptDrop(source) {
+        if (!source.realWindow)
+            return false;
+
+        let window = source.realWindow.get_meta_window();
+        this._moveWindow(window);
+        return true;
+    }
+
+    handleDragOver(source) {
+        if (source.realWindow)
+            return DND.DragMotionResult.MOVE_DROP;
+        else
+            return DND.DragMotionResult.CONTINUE;
+    }
+
+
+    _moveWindow(window) {
+        let monitorIndex = Main.layoutManager.findIndexForActor(this);
+        if (monitorIndex != window.get_monitor())
+            window.move_to_monitor(monitorIndex);
+        window.change_workspace_by_index(this._index, false);
     }
 
     // eslint-disable-next-line camelcase
-- 
2.21.0


From 7dca894705ddfd73016f4d073f6fe486563ce324 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Wed, 26 Jun 2019 23:55:58 +0000
Subject: [PATCH 20/30] window-list: Show previews in workspace switcher

Currently the new horizontal workspace switcher only shows a series of
buttons, with no indication of the workspaces' contents. Go full GNOME 2
and add tiny draggable preview rectangles that represent the windows
on a particular workspace.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/74
---
 extensions/window-list/classic.css           |  10 ++
 extensions/window-list/stylesheet.css        |  10 ++
 extensions/window-list/workspaceIndicator.js | 154 ++++++++++++++++++-
 3 files changed, 173 insertions(+), 1 deletion(-)

diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css
index c533473..7079d3e 100644
--- a/extensions/window-list/classic.css
+++ b/extensions/window-list/classic.css
@@ -56,3 +56,13 @@
 .window-list-workspace-indicator .workspace.active {
   background-color: #ccc;
 }
+
+.window-list-window-preview {
+  background-color: #ededed;
+  border: 1px solid #ccc;
+}
+
+.window-list-window-preview.active {
+  background-color: #f6f5f4;
+  border: 2px solid #888;
+}
diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
index ad5978a..79d56ba 100644
--- a/extensions/window-list/stylesheet.css
+++ b/extensions/window-list/stylesheet.css
@@ -121,6 +121,16 @@
   background-color: rgba(200, 200, 200, .3);
 }
 
+.window-list-window-preview {
+  background-color: #252525;
+  border: 1px solid #ccc;
+}
+
+.window-list-window-preview.active {
+  background-color: #353535;
+  border: 2px solid #ccc;
+}
+
 .notification {
   font-weight: normal;
 }
diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
index 4831768..ca47611 100644
--- a/extensions/window-list/workspaceIndicator.js
+++ b/extensions/window-list/workspaceIndicator.js
@@ -9,16 +9,131 @@ const PopupMenu = imports.ui.popupMenu;
 const Gettext = imports.gettext.domain('gnome-shell-extensions');
 const _ = Gettext.gettext;
 
+let WindowPreview = GObject.registerClass({
+    GTypeName: 'WindowListWindowPreview'
+}, class WindowPreview extends St.Button {
+    _init(window) {
+        super._init({
+            style_class: 'window-list-window-preview'
+        });
+
+        this._delegate = this;
+        DND.makeDraggable(this, { restoreOnSuccess: true });
+
+        this._window = window;
+
+        this.connect('destroy', this._onDestroy.bind(this));
+
+        this._sizeChangedId = this._window.connect('size-changed',
+            this._relayout.bind(this));
+        this._positionChangedId = this._window.connect('position-changed',
+            this._relayout.bind(this));
+        this._minimizedChangedId = this._window.connect('notify::minimized',
+            this._relayout.bind(this));
+        this._monitorEnteredId = global.display.connect('window-entered-monitor',
+            this._relayout.bind(this));
+        this._monitorLeftId = global.display.connect('window-left-monitor',
+            this._relayout.bind(this));
+
+        // Do initial layout when we get a parent
+        let id = this.connect('parent-set', () => {
+            this.disconnect(id);
+            if (!this.get_parent())
+                return;
+            this._laterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
+                this._laterId = 0;
+                this._relayout();
+                return false;
+            });
+        });
+
+        this._focusChangedId = global.display.connect('notify::focus-window',
+            this._onFocusChanged.bind(this));
+        this._onFocusChanged();
+    }
+
+    // needed for DND
+    get realWindow() {
+        return this._window.get_compositor_private();
+    }
+
+    _onDestroy() {
+        this._window.disconnect(this._sizeChangedId);
+        this._window.disconnect(this._positionChangedId);
+        this._window.disconnect(this._minimizedChangedId);
+        global.display.disconnect(this._monitorEnteredId);
+        global.display.disconnect(this._monitorLeftId);
+        global.display.disconnect(this._focusChangedId);
+        if (this._laterId)
+            Meta.later_remove(this._laterId);
+    }
+
+    _onFocusChanged() {
+        if (global.display.focus_window == this._window)
+            this.add_style_class_name('active');
+        else
+            this.remove_style_class_name('active');
+    }
+
+    _relayout() {
+        let monitor = Main.layoutManager.findIndexForActor(this);
+        this.visible = monitor == this._window.get_monitor() &&
+            this._window.showing_on_its_workspace();
+
+        if (!this.visible)
+            return;
+
+        let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
+        let hscale = this.get_parent().allocation.get_width() / workArea.width;
+        let vscale = this.get_parent().allocation.get_height() / workArea.height;
+
+        let frameRect = this._window.get_frame_rect();
+        this.set_size(
+            Math.round(Math.min(frameRect.width, workArea.width) * hscale),
+            Math.round(Math.min(frameRect.height, workArea.height) * vscale));
+        this.set_position(
+            Math.round(frameRect.x * hscale),
+            Math.round(frameRect.y * vscale));
+    }
+});
+
 let WorkspaceThumbnail = GObject.registerClass({
     GTypeName: 'WindowListWorkspaceThumbnail'
 }, class WorkspaceThumbnail extends St.Button {
     _init(index) {
         super._init({
-            style_class: 'workspace'
+            style_class: 'workspace',
+            child: new Clutter.Actor({
+                layout_manager: new Clutter.BinLayout(),
+                clip_to_allocation: true
+            }),
+            x_fill: true,
+            y_fill: true
         });
 
+        this.connect('destroy', this._onDestroy.bind(this));
+
         this._index = index;
         this._delegate = this; // needed for DND
+
+        this._windowPreviews = new Map();
+
+        let workspaceManager = global.workspace_manager;
+        this._workspace = workspaceManager.get_workspace_by_index(index);
+
+        this._windowAddedId = this._workspace.connect('window-added',
+            (ws, window) => {
+                this._addWindow(window);
+            });
+        this._windowRemovedId = this._workspace.connect('window-removed',
+            (ws, window) => {
+                this._removeWindow(window);
+            });
+        this._restackedId = global.display.connect('restacked',
+            this._onRestacked.bind(this));
+
+        this._workspace.list_windows().forEach(w => this._addWindow(w));
+        this._onRestacked();
     }
 
     acceptDrop(source) {
@@ -37,6 +152,37 @@ let WorkspaceThumbnail = GObject.registerClass({
             return DND.DragMotionResult.CONTINUE;
     }
 
+    _addWindow(window) {
+        if (this._windowPreviews.has(window))
+            return;
+
+        let preview = new WindowPreview(window);
+        preview.connect('clicked', (a, btn) => this.emit('clicked', btn));
+        this._windowPreviews.set(window, preview);
+        this.child.add_child(preview);
+    }
+
+    _removeWindow(window) {
+        let preview = this._windowPreviews.get(window);
+        if (!preview)
+            return;
+
+        this._windowPreviews.delete(window);
+        preview.destroy();
+    }
+
+    _onRestacked() {
+        let lastPreview = null;
+        let windows = global.get_window_actors().map(a => a.meta_window);
+        for (let i = 0; i < windows.length; i++) {
+            let preview = this._windowPreviews.get(windows[i]);
+            if (!preview)
+                continue;
+
+            this.child.set_child_above_sibling(preview, lastPreview);
+            lastPreview = preview;
+        }
+    }
 
     _moveWindow(window) {
         let monitorIndex = Main.layoutManager.findIndexForActor(this);
@@ -51,6 +197,12 @@ let WorkspaceThumbnail = GObject.registerClass({
         if (ws)
             ws.activate(global.get_current_time());
     }
+
+    _onDestroy() {
+        this._workspace.disconnect(this._windowAddedId);
+        this._workspace.disconnect(this._windowRemovedId);
+        global.display.disconnect(this._restackedId);
+    }
 });
 
 var WorkspaceIndicator = GObject.registerClass({
-- 
2.21.0


From 77b9d4fee20d84816cb64bfa6b95fddd589b4788 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Sat, 29 Jun 2019 01:24:54 +0200
Subject: [PATCH 21/30] workspace-indicator: Fix whitespace error

We only want a single space before and after operators, not at least
one. Unfortunately eslint only enforces the latter ...

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71
---
 extensions/workspace-indicator/extension.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
index 3be1268..c3c4d5f 100644
--- a/extensions/workspace-indicator/extension.js
+++ b/extensions/workspace-indicator/extension.js
@@ -109,7 +109,7 @@ class WorkspaceIndicator extends PanelMenu.Button {
     _activate(index) {
         let workspaceManager = global.workspace_manager;
 
-        if (index >= 0 && index <  workspaceManager.n_workspaces) {
+        if (index >= 0 && index < workspaceManager.n_workspaces) {
             let metaWorkspace = workspaceManager.get_workspace_by_index(index);
             metaWorkspace.activate(global.get_current_time());
         }
-- 
2.21.0


From a68ef2096fed7580f7177991e354910add1ab79c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Sun, 9 Jun 2019 22:58:29 +0000
Subject: [PATCH 22/30] workspace-indicator: Make some properties private

There's no reason why they should be public.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71
---
 extensions/workspace-indicator/extension.js | 30 ++++++++++-----------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
index c3c4d5f..e052181 100644
--- a/extensions/workspace-indicator/extension.js
+++ b/extensions/workspace-indicator/extension.js
@@ -23,14 +23,14 @@ class WorkspaceIndicator extends PanelMenu.Button {
         let workspaceManager = global.workspace_manager;
 
         this._currentWorkspace = workspaceManager.get_active_workspace().index();
-        this.statusLabel = new St.Label({
+        this._statusLabel = new St.Label({
             y_align: Clutter.ActorAlign.CENTER,
             text: this._labelText()
         });
 
-        this.add_actor(this.statusLabel);
+        this.add_actor(this._statusLabel);
 
-        this.workspacesItems = [];
+        this._workspacesItems = [];
         this._workspaceSection = new PopupMenu.PopupMenuSection();
         this.menu.addMenuItem(this._workspaceSection);
 
@@ -46,7 +46,7 @@ class WorkspaceIndicator extends PanelMenu.Button {
         this._createWorkspacesSection();
 
         //styling
-        this.statusLabel.add_style_class_name('panel-workspace-indicator');
+        this._statusLabel.add_style_class_name('panel-workspace-indicator');
 
         this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA });
         this._settingsChangedId =
@@ -67,11 +67,11 @@ class WorkspaceIndicator extends PanelMenu.Button {
     }
 
     _updateIndicator() {
-        this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
+        this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
         this._currentWorkspace = global.workspace_manager.get_active_workspace().index();
-        this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
+        this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
 
-        this.statusLabel.set_text(this._labelText());
+        this._statusLabel.set_text(this._labelText());
     }
 
     _labelText(workspaceIndex) {
@@ -86,24 +86,24 @@ class WorkspaceIndicator extends PanelMenu.Button {
         let workspaceManager = global.workspace_manager;
 
         this._workspaceSection.removeAll();
-        this.workspacesItems = [];
+        this._workspacesItems = [];
         this._currentWorkspace = workspaceManager.get_active_workspace().index();
 
         let i = 0;
         for (; i < workspaceManager.n_workspaces; i++) {
-            this.workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i));
-            this._workspaceSection.addMenuItem(this.workspacesItems[i]);
-            this.workspacesItems[i].workspaceId = i;
-            this.workspacesItems[i].label_actor = this.statusLabel;
-            this.workspacesItems[i].connect('activate', (actor, _event) => {
+            this._workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i));
+            this._workspaceSection.addMenuItem(this._workspacesItems[i]);
+            this._workspacesItems[i].workspaceId = i;
+            this._workspacesItems[i].label_actor = this._statusLabel;
+            this._workspacesItems[i].connect('activate', (actor, _event) => {
                 this._activate(actor.workspaceId);
             });
 
             if (i == this._currentWorkspace)
-                this.workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT);
+                this._workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT);
         }
 
-        this.statusLabel.set_text(this._labelText());
+        this._statusLabel.set_text(this._labelText());
     }
 
     _activate(index) {
-- 
2.21.0


From 7e74dd6e331846564898ed220d58eb54cad83524 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Sun, 9 Jun 2019 23:03:55 +0000
Subject: [PATCH 23/30] workspace-indicator: Update workspace names in-place

There's no good reason to rebuild the entire menu on workspace names
changes, we can simply update the labels in-place.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71
---
 extensions/workspace-indicator/extension.js | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
index e052181..205ee36 100644
--- a/extensions/workspace-indicator/extension.js
+++ b/extensions/workspace-indicator/extension.js
@@ -49,9 +49,9 @@ class WorkspaceIndicator extends PanelMenu.Button {
         this._statusLabel.add_style_class_name('panel-workspace-indicator');
 
         this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA });
-        this._settingsChangedId =
-            this._settings.connect(`changed::${WORKSPACE_KEY}`,
-                                   this._createWorkspacesSection.bind(this));
+        this._settingsChangedId = this._settings.connect(
+            `changed::${WORKSPACE_KEY}`,
+            this._updateMenuLabels.bind(this));
     }
 
     _onDestroy() {
@@ -82,6 +82,11 @@ class WorkspaceIndicator extends PanelMenu.Button {
         return Meta.prefs_get_workspace_name(workspaceIndex);
     }
 
+    _updateMenuLabels() {
+        for (let i = 0; i < this._workspacesItems.length; i++)
+            this._workspacesItems[i].label.text = this._labelText(i);
+    }
+
     _createWorkspacesSection() {
         let workspaceManager = global.workspace_manager;
 
-- 
2.21.0


From dfcd296cf33de8a3e739c340734332a63f2c01f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Sun, 9 Jun 2019 23:05:00 +0000
Subject: [PATCH 24/30] workspace-indicator: Minor cleanup

Mutter has a dedicated method for getting the index of the active
workspace, use that instead of getting first the active workspace
and then its index.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71
---
 extensions/workspace-indicator/extension.js | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
index 205ee36..7ebc1f0 100644
--- a/extensions/workspace-indicator/extension.js
+++ b/extensions/workspace-indicator/extension.js
@@ -22,7 +22,7 @@ class WorkspaceIndicator extends PanelMenu.Button {
 
         let workspaceManager = global.workspace_manager;
 
-        this._currentWorkspace = workspaceManager.get_active_workspace().index();
+        this._currentWorkspace = workspaceManager.get_active_workspace_index();
         this._statusLabel = new St.Label({
             y_align: Clutter.ActorAlign.CENTER,
             text: this._labelText()
@@ -68,7 +68,7 @@ class WorkspaceIndicator extends PanelMenu.Button {
 
     _updateIndicator() {
         this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
-        this._currentWorkspace = global.workspace_manager.get_active_workspace().index();
+        this._currentWorkspace = global.workspace_manager.get_active_workspace_index();
         this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
 
         this._statusLabel.set_text(this._labelText());
@@ -92,7 +92,7 @@ class WorkspaceIndicator extends PanelMenu.Button {
 
         this._workspaceSection.removeAll();
         this._workspacesItems = [];
-        this._currentWorkspace = workspaceManager.get_active_workspace().index();
+        this._currentWorkspace = workspaceManager.get_active_workspace_index();
 
         let i = 0;
         for (; i < workspaceManager.n_workspaces; i++) {
@@ -131,7 +131,7 @@ class WorkspaceIndicator extends PanelMenu.Button {
             return;
         }
 
-        let newIndex = global.workspace_manager.get_active_workspace().index() + diff;
+        let newIndex = global.workspace_manager.get_active_workspace_index() + diff;
         this._activate(newIndex);
     }
 });
-- 
2.21.0


From 00d1e9637f0b7f51b271c384fb55ecaea673bb70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Sun, 9 Jun 2019 23:09:12 +0000
Subject: [PATCH 25/30] workspace-indicator: Refactor workspace signal handlers

We are about to support a separate representation if horizontal
workspaces are used. To prepare for that, rename the handlers to
something more generic and split out menu-specific bits into a
dedicated help function.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71
---
 extensions/workspace-indicator/extension.js | 31 ++++++++++++++-------
 1 file changed, 21 insertions(+), 10 deletions(-)

diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
index 7ebc1f0..34fc275 100644
--- a/extensions/workspace-indicator/extension.js
+++ b/extensions/workspace-indicator/extension.js
@@ -34,13 +34,12 @@ class WorkspaceIndicator extends PanelMenu.Button {
         this._workspaceSection = new PopupMenu.PopupMenuSection();
         this.menu.addMenuItem(this._workspaceSection);
 
-        this._workspaceManagerSignals = [];
-        this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-added',
-                                                                          this._createWorkspacesSection.bind(this)));
-        this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-removed',
-                                                                          this._createWorkspacesSection.bind(this)));
-        this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-switched',
-                                                                          this._updateIndicator.bind(this)));
+        this._workspaceManagerSignals = [
+            workspaceManager.connect_after('notify::n-workspaces',
+                this._nWorkspacesChanged.bind(this)),
+            workspaceManager.connect_after('workspace-switched',
+                this._onWorkspaceSwitched.bind(this))
+        ];
 
         this.connect('scroll-event', this._onScrollEvent.bind(this));
         this._createWorkspacesSection();
@@ -66,14 +65,26 @@ class WorkspaceIndicator extends PanelMenu.Button {
         super._onDestroy();
     }
 
-    _updateIndicator() {
-        this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE);
+    _onWorkspaceSwitched() {
         this._currentWorkspace = global.workspace_manager.get_active_workspace_index();
-        this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT);
+
+        this._updateMenuOrnament();
 
         this._statusLabel.set_text(this._labelText());
     }
 
+    _nWorkspacesChanged() {
+        this._createWorkspacesSection();
+    }
+
+    _updateMenuOrnament() {
+        for (let i = 0; i < this._workspacesItems.length; i++) {
+            this._workspacesItems[i].setOrnament(i == this._currentWorkspace
+                ? PopupMenu.Ornament.DOT
+                : PopupMenu.Ornament.NONE);
+        }
+    }
+
     _labelText(workspaceIndex) {
         if (workspaceIndex == undefined) {
             workspaceIndex = this._currentWorkspace;
-- 
2.21.0


From 0834d04691de28f3a216578e55335c6e9b3e4fad Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Sun, 9 Jun 2019 23:17:35 +0000
Subject: [PATCH 26/30] workspace-indicator: Minor cleanup

Pass the style class at construction time instead of setting it later.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71
---
 extensions/workspace-indicator/extension.js | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
index 34fc275..672b98d 100644
--- a/extensions/workspace-indicator/extension.js
+++ b/extensions/workspace-indicator/extension.js
@@ -24,6 +24,7 @@ class WorkspaceIndicator extends PanelMenu.Button {
 
         this._currentWorkspace = workspaceManager.get_active_workspace_index();
         this._statusLabel = new St.Label({
+            style_class: 'panel-workspace-indicator',
             y_align: Clutter.ActorAlign.CENTER,
             text: this._labelText()
         });
@@ -44,9 +45,6 @@ class WorkspaceIndicator extends PanelMenu.Button {
         this.connect('scroll-event', this._onScrollEvent.bind(this));
         this._createWorkspacesSection();
 
-        //styling
-        this._statusLabel.add_style_class_name('panel-workspace-indicator');
-
         this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA });
         this._settingsChangedId = this._settings.connect(
             `changed::${WORKSPACE_KEY}`,
-- 
2.21.0


From 07fca6b1ee27a1eaf97027e4353ab46ea9cb745e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Sun, 9 Jun 2019 23:45:24 +0000
Subject: [PATCH 27/30] workspace-indicator: Support horizontal workspace
 layout

Just like we did for the workspace indicator in the window-list, improve
the handling of horizontal workspace layouts by showing the switcher
in-place instead of delegating the functionality to a menu.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71
---
 extensions/workspace-indicator/extension.js   | 76 ++++++++++++++++++-
 extensions/workspace-indicator/stylesheet.css | 18 ++++-
 2 files changed, 90 insertions(+), 4 deletions(-)

diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
index 672b98d..48019da 100644
--- a/extensions/workspace-indicator/extension.js
+++ b/extensions/workspace-indicator/extension.js
@@ -15,11 +15,38 @@ const ExtensionUtils = imports.misc.extensionUtils;
 const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences';
 const WORKSPACE_KEY = 'workspace-names';
 
+let WorkspaceThumbnail = GObject.registerClass({
+    GTypeName: 'WorkspaceIndicatorWorkspaceThumbnail'
+}, class WorkspaceThumbnail extends St.Button {
+    _init(index) {
+        super._init({
+            style_class: 'workspace',
+        });
+
+        this._index = index;
+    }
+
+    // eslint-disable-next-line camelcase
+    on_clicked() {
+        let ws = global.workspace_manager.get_workspace_by_index(this._index);
+        if (ws)
+            ws.activate(global.get_current_time());
+    }
+});
+
+
 let WorkspaceIndicator = GObject.registerClass(
 class WorkspaceIndicator extends PanelMenu.Button {
     _init() {
         super._init(0.0, _('Workspace Indicator'));
 
+        let container = new St.Widget({
+            layout_manager: new Clutter.BinLayout(),
+            x_expand: true,
+            y_expand: true
+        });
+        this.add_actor(container);
+
         let workspaceManager = global.workspace_manager;
 
         this._currentWorkspace = workspaceManager.get_active_workspace_index();
@@ -29,7 +56,15 @@ class WorkspaceIndicator extends PanelMenu.Button {
             text: this._labelText()
         });
 
-        this.add_actor(this._statusLabel);
+        container.add_actor(this._statusLabel);
+
+        this._thumbnailsBox = new St.BoxLayout({
+            style_class: 'panel-workspace-indicator-box',
+            y_expand: true,
+            reactive: true
+        });
+
+        container.add_actor(this._thumbnailsBox);
 
         this._workspacesItems = [];
         this._workspaceSection = new PopupMenu.PopupMenuSection();
@@ -39,11 +74,16 @@ class WorkspaceIndicator extends PanelMenu.Button {
             workspaceManager.connect_after('notify::n-workspaces',
                 this._nWorkspacesChanged.bind(this)),
             workspaceManager.connect_after('workspace-switched',
-                this._onWorkspaceSwitched.bind(this))
+                this._onWorkspaceSwitched.bind(this)),
+            workspaceManager.connect('notify::layout-rows',
+                this._onWorkspaceOrientationChanged.bind(this))
         ];
 
         this.connect('scroll-event', this._onScrollEvent.bind(this));
+        this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this));
         this._createWorkspacesSection();
+        this._updateThumbnails();
+        this._onWorkspaceOrientationChanged();
 
         this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA });
         this._settingsChangedId = this._settings.connect(
@@ -63,16 +103,26 @@ class WorkspaceIndicator extends PanelMenu.Button {
         super._onDestroy();
     }
 
+    _onWorkspaceOrientationChanged() {
+        let vertical = global.workspace_manager.layout_rows == -1;
+        this.reactive = vertical;
+
+        this._statusLabel.visible = vertical;
+        this._thumbnailsBox.visible = !vertical;
+    }
+
     _onWorkspaceSwitched() {
         this._currentWorkspace = global.workspace_manager.get_active_workspace_index();
 
         this._updateMenuOrnament();
+        this._updateActiveThumbnail();
 
         this._statusLabel.set_text(this._labelText());
     }
 
     _nWorkspacesChanged() {
         this._createWorkspacesSection();
+        this._updateThumbnails();
     }
 
     _updateMenuOrnament() {
@@ -83,6 +133,16 @@ class WorkspaceIndicator extends PanelMenu.Button {
         }
     }
 
+    _updateActiveThumbnail() {
+        let thumbs = this._thumbnailsBox.get_children();
+        for (let i = 0; i < thumbs.length; i++) {
+            if (i == this._currentWorkspace)
+                thumbs[i].add_style_class_name('active');
+            else
+                thumbs[i].remove_style_class_name('active');
+        }
+    }
+
     _labelText(workspaceIndex) {
         if (workspaceIndex == undefined) {
             workspaceIndex = this._currentWorkspace;
@@ -120,6 +180,18 @@ class WorkspaceIndicator extends PanelMenu.Button {
         this._statusLabel.set_text(this._labelText());
     }
 
+    _updateThumbnails() {
+        let workspaceManager = global.workspace_manager;
+
+        this._thumbnailsBox.destroy_all_children();
+
+        for (let i = 0; i < workspaceManager.n_workspaces; i++) {
+            let thumb = new WorkspaceThumbnail(i);
+            this._thumbnailsBox.add_actor(thumb);
+        }
+        this._updateActiveThumbnail();
+    }
+
     _activate(index) {
         let workspaceManager = global.workspace_manager;
 
diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css
index 1271f1c..a15081e 100644
--- a/extensions/workspace-indicator/stylesheet.css
+++ b/extensions/workspace-indicator/stylesheet.css
@@ -1,5 +1,19 @@
-.panel-workspace-indicator {
+.panel-workspace-indicator,
+.panel-workspace-indicator-box .workspace {
 	padding: 0 8px;
-	background-color: rgba(200, 200, 200, .5);
 	border: 1px solid #cccccc;
 }
+
+.panel-workspace-indicator,
+.panel-workspace-indicator-box .workspace.active {
+	background-color: rgba(200, 200, 200, .5);
+}
+
+.panel-workspace-indicator-box .workspace {
+    background-color: rgba(200, 200, 200, .3);
+    border-left-width: 0;
+}
+
+.panel-workspace-indicator-box .workspace:first-child {
+    border-left-width: 1px;
+}
-- 
2.21.0


From 58496daaf5591e86238d7ab7f470b5ad7797b3e3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Fri, 28 Jun 2019 11:33:16 +0200
Subject: [PATCH 28/30] workspace-indicator: Show previews in workspace
 switcher

Currently the new horizontal workspace switcher only shows a series of
buttons, with no indication of the workspaces' contents. Go full GNOME 2
and add tiny draggable preview rectangles that represent the windows
on a particular workspace.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/77
---
 extensions/workspace-indicator/extension.js   | 194 +++++++++++++++++-
 extensions/workspace-indicator/stylesheet.css |  22 +-
 2 files changed, 209 insertions(+), 7 deletions(-)

diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
index 48019da..69eef88 100644
--- a/extensions/workspace-indicator/extension.js
+++ b/extensions/workspace-indicator/extension.js
@@ -2,28 +2,199 @@
 /* exported init enable disable */
 
 const { Clutter, Gio, GObject, Meta, St } = imports.gi;
+
+const DND = imports.ui.dnd;
+const ExtensionUtils = imports.misc.extensionUtils;
+const Main = imports.ui.main;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
 
 const Gettext = imports.gettext.domain('gnome-shell-extensions');
 const _ = Gettext.gettext;
 
-const Main = imports.ui.main;
-
-const ExtensionUtils = imports.misc.extensionUtils;
-
 const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences';
 const WORKSPACE_KEY = 'workspace-names';
 
+let WindowPreview = GObject.registerClass({
+    GTypeName: 'WorkspaceIndicatorWindowPreview'
+}, class WindowPreview extends St.Button {
+    _init(window) {
+        super._init({
+            style_class: 'workspace-indicator-window-preview'
+        });
+
+        this._delegate = this;
+        DND.makeDraggable(this, { restoreOnSuccess: true });
+
+        this._window = window;
+
+        this.connect('destroy', this._onDestroy.bind(this));
+
+        this._sizeChangedId = this._window.connect('size-changed',
+            this._relayout.bind(this));
+        this._positionChangedId = this._window.connect('position-changed',
+            this._relayout.bind(this));
+        this._minimizedChangedId = this._window.connect('notify::minimized',
+            this._relayout.bind(this));
+        this._monitorEnteredId = global.display.connect('window-entered-monitor',
+            this._relayout.bind(this));
+        this._monitorLeftId = global.display.connect('window-left-monitor',
+            this._relayout.bind(this));
+
+        // Do initial layout when we get a parent
+        let id = this.connect('parent-set', () => {
+            this.disconnect(id);
+            if (!this.get_parent())
+                return;
+            this._laterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
+                this._laterId = 0;
+                this._relayout();
+                return false;
+            });
+        });
+
+        this._focusChangedId = global.display.connect('notify::focus-window',
+            this._onFocusChanged.bind(this));
+        this._onFocusChanged();
+    }
+
+    // needed for DND
+    get realWindow() {
+        return this._window.get_compositor_private();
+    }
+
+    _onDestroy() {
+        this._window.disconnect(this._sizeChangedId);
+        this._window.disconnect(this._positionChangedId);
+        this._window.disconnect(this._minimizedChangedId);
+        global.display.disconnect(this._monitorEnteredId);
+        global.display.disconnect(this._monitorLeftId);
+        global.display.disconnect(this._focusChangedId);
+        if (this._laterId)
+            Meta.later_remove(this._laterId);
+    }
+
+    _onFocusChanged() {
+        if (global.display.focus_window == this._window)
+            this.add_style_class_name('active');
+        else
+            this.remove_style_class_name('active');
+    }
+
+    _relayout() {
+        let monitor = Main.layoutManager.findIndexForActor(this);
+        this.visible = monitor == this._window.get_monitor() &&
+            this._window.showing_on_its_workspace();
+
+        if (!this.visible)
+            return;
+
+        let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
+        let hscale = this.get_parent().allocation.get_width() / workArea.width;
+        let vscale = this.get_parent().allocation.get_height() / workArea.height;
+
+        let frameRect = this._window.get_frame_rect();
+        this.set_size(
+            Math.round(Math.min(frameRect.width, workArea.width) * hscale),
+            Math.round(Math.min(frameRect.height, workArea.height) * vscale));
+        this.set_position(
+            Math.round(frameRect.x * hscale),
+            Math.round(frameRect.y * vscale));
+    }
+});
+
 let WorkspaceThumbnail = GObject.registerClass({
     GTypeName: 'WorkspaceIndicatorWorkspaceThumbnail'
 }, class WorkspaceThumbnail extends St.Button {
     _init(index) {
         super._init({
             style_class: 'workspace',
+            child: new Clutter.Actor({
+                layout_manager: new Clutter.BinLayout(),
+                clip_to_allocation: true
+            }),
+            x_fill: true,
+            y_fill: true
         });
 
+        this.connect('destroy', this._onDestroy.bind(this));
+
         this._index = index;
+        this._delegate = this; // needed for DND
+
+        this._windowPreviews = new Map();
+
+        let workspaceManager = global.workspace_manager;
+        this._workspace = workspaceManager.get_workspace_by_index(index);
+
+        this._windowAddedId = this._workspace.connect('window-added',
+            (ws, window) => {
+                this._addWindow(window);
+            });
+        this._windowRemovedId = this._workspace.connect('window-removed',
+            (ws, window) => {
+                this._removeWindow(window);
+            });
+        this._restackedId = global.display.connect('restacked',
+            this._onRestacked.bind(this));
+
+        this._workspace.list_windows().forEach(w => this._addWindow(w));
+        this._onRestacked();
+    }
+
+    acceptDrop(source) {
+        if (!source.realWindow)
+            return false;
+
+        let window = source.realWindow.get_meta_window();
+        this._moveWindow(window);
+        return true;
+    }
+
+    handleDragOver(source) {
+        if (source.realWindow)
+            return DND.DragMotionResult.MOVE_DROP;
+        else
+            return DND.DragMotionResult.CONTINUE;
+    }
+
+    _addWindow(window) {
+        if (this._windowPreviews.has(window))
+            return;
+
+        let preview = new WindowPreview(window);
+        preview.connect('clicked', (a, btn) => this.emit('clicked', btn));
+        this._windowPreviews.set(window, preview);
+        this.child.add_child(preview);
+    }
+
+    _removeWindow(window) {
+        let preview = this._windowPreviews.get(window);
+        if (!preview)
+            return;
+
+        this._windowPreviews.delete(window);
+        preview.destroy();
+    }
+
+    _onRestacked() {
+        let lastPreview = null;
+        let windows = global.get_window_actors().map(a => a.meta_window);
+        for (let i = 0; i < windows.length; i++) {
+            let preview = this._windowPreviews.get(windows[i]);
+            if (!preview)
+                continue;
+
+            this.child.set_child_above_sibling(preview, lastPreview);
+            lastPreview = preview;
+        }
+    }
+
+    _moveWindow(window) {
+        let monitorIndex = Main.layoutManager.findIndexForActor(this);
+        if (monitorIndex != window.get_monitor())
+            window.move_to_monitor(monitorIndex);
+        window.change_workspace_by_index(this._index, false);
     }
 
     // eslint-disable-next-line camelcase
@@ -32,8 +203,13 @@ let WorkspaceThumbnail = GObject.registerClass({
         if (ws)
             ws.activate(global.get_current_time());
     }
-});
 
+    _onDestroy() {
+        this._workspace.disconnect(this._windowAddedId);
+        this._workspace.disconnect(this._windowRemovedId);
+        global.display.disconnect(this._restackedId);
+    }
+});
 
 let WorkspaceIndicator = GObject.registerClass(
 class WorkspaceIndicator extends PanelMenu.Button {
@@ -100,6 +276,8 @@ class WorkspaceIndicator extends PanelMenu.Button {
             this._settingsChangedId = 0;
         }
 
+        Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
+
         super._onDestroy();
     }
 
@@ -109,6 +287,12 @@ class WorkspaceIndicator extends PanelMenu.Button {
 
         this._statusLabel.visible = vertical;
         this._thumbnailsBox.visible = !vertical;
+
+        // Disable offscreen-redirect when showing the workspace switcher
+        // so that clip-to-allocation works
+        Main.panel.set_offscreen_redirect(vertical
+            ? Clutter.OffscreenRedirect.ALWAYS
+            : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY);
     }
 
     _onWorkspaceSwitched() {
diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css
index a15081e..8c101e7 100644
--- a/extensions/workspace-indicator/stylesheet.css
+++ b/extensions/workspace-indicator/stylesheet.css
@@ -1,9 +1,17 @@
-.panel-workspace-indicator,
-.panel-workspace-indicator-box .workspace {
+.panel-workspace-indicator {
 	padding: 0 8px;
 	border: 1px solid #cccccc;
 }
 
+.panel-workspace-indicator-box {
+    padding: 2px 0;
+}
+
+.panel-workspace-indicator-box .workspace {
+    border: 1px solid #cccccc;
+    width: 48px;
+}
+
 .panel-workspace-indicator,
 .panel-workspace-indicator-box .workspace.active {
 	background-color: rgba(200, 200, 200, .5);
@@ -17,3 +25,13 @@
 .panel-workspace-indicator-box .workspace:first-child {
     border-left-width: 1px;
 }
+
+.workspace-indicator-window-preview {
+    background-color: #252525;
+    border: 1px solid #ccc;
+}
+
+.workspace-indicator-window-preview {
+    background-color: #353535;
+    border: 2px solid #ccc;
+}
-- 
2.21.0


From 06674bf6aff9fb4ae2224a448ba928c1687b5f6b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 2 Jul 2019 17:39:55 +0200
Subject: [PATCH 29/30] window-list: Move super-key handling into WindowPicker

We have an option to put a window list on each monitor, so we may have
more than one window picker toggle. We don't want each of those try to
toggle the window picker simultanuously, so move handling of the super
key directly into the picker.

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/80
---
 extensions/window-list/windowPicker.js | 34 ++++++++++++--------------
 1 file changed, 15 insertions(+), 19 deletions(-)

diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js
index 024fd80..ba0a697 100644
--- a/extensions/window-list/windowPicker.js
+++ b/extensions/window-list/windowPicker.js
@@ -67,6 +67,8 @@ var WindowPicker = class {
         this._visible = false;
         this._modal = false;
 
+        this._overlayKeyId = 0;
+
         this.actor = new Clutter.Actor();
 
         this.actor.connect('destroy', this._onDestroy.bind(this));
@@ -101,6 +103,15 @@ var WindowPicker = class {
         this._updateBackgrounds();
 
         Main.uiGroup.insert_child_below(this.actor, global.window_group);
+
+        if (!Main.sessionMode.hasOverview) {
+            this._overlayKeyId = global.display.connect('overlay-key', () => {
+                if (!this._visible)
+                    this.open();
+                else
+                    this.close();
+            });
+        }
     }
 
     get visible() {
@@ -188,6 +199,10 @@ var WindowPicker = class {
         if (this._monitorsChangedId)
             Main.layoutManager.disconnect(this._monitorsChangedId);
         this._monitorsChangedId = 0;
+
+        if (this._overlayKeyId)
+            global.display.disconnect(this._overlayKeyId);
+        this._overlayKeyId = 0;
     }
 
     _updateBackgrounds() {
@@ -227,10 +242,6 @@ class WindowPickerToggle extends St.Button {
             toggle_mode: true
         });
 
-        this._overlayKeyId = 0;
-
-        this.connect('destroy', this._onDestroy.bind(this));
-
         this.connect('notify::checked', () => {
             if (this.checked)
                 Main.windowPicker.open();
@@ -238,23 +249,8 @@ class WindowPickerToggle extends St.Button {
                 Main.windowPicker.close();
         });
 
-        if (!Main.sessionMode.hasOverview) {
-            this._overlayKeyId = global.display.connect('overlay-key', () => {
-                if (!Main.windowPicker.visible)
-                    Main.windowPicker.open();
-                else
-                    Main.windowPicker.close();
-            });
-        }
-
         Main.windowPicker.connect('open-state-changed', () => {
             this.checked = Main.windowPicker.visible;
         });
     }
-
-    _onDestroy() {
-        if (this._overlayKeyId)
-            global.display.disconnect(this._overlayKeyId);
-        this._overlayKeyId == 0;
-    }
 });
-- 
2.21.0


From c37d082487cce3548844c150d1976bf1735552c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
Date: Tue, 2 Jul 2019 18:12:31 +0200
Subject: [PATCH 30/30] window-list: Handle closing window picker with Escape

Just like the overview can be closed with Escape, it makes sense to
allow the same for the window picker (in addition to pressing super
repeatedly).

https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/80
---
 extensions/window-list/windowPicker.js | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js
index ba0a697..12a7627 100644
--- a/extensions/window-list/windowPicker.js
+++ b/extensions/window-list/windowPicker.js
@@ -68,6 +68,7 @@ var WindowPicker = class {
         this._modal = false;
 
         this._overlayKeyId = 0;
+        this._stageKeyPressId = 0;
 
         this.actor = new Clutter.Actor();
 
@@ -132,6 +133,16 @@ var WindowPicker = class {
         this._fakeOverviewAnimation();
         this._workspacesDisplay.show(false);
 
+        this._stageKeyPressId = global.stage.connect('key-press-event',
+            (a, event) => {
+                let sym = event.get_key_symbol();
+                if (sym == Clutter.KEY_Escape) {
+                    this.close();
+                    return Clutter.EVENT_STOP;
+                }
+                return Clutter.EVENT_PROPAGATE;
+            });
+
         this.emit('open-state-changed', this._visible);
     }
 
@@ -151,6 +162,9 @@ var WindowPicker = class {
             this._fakeOverviewVisible(false);
         });
 
+        global.stage.disconnect(this._stageKeyPressId);
+        this._stageKeyPressId = 0;
+
         this.emit('open-state-changed', this._visible);
     }
 
@@ -203,6 +217,10 @@ var WindowPicker = class {
         if (this._overlayKeyId)
             global.display.disconnect(this._overlayKeyId);
         this._overlayKeyId = 0;
+
+        if (this._stageKeyPressId)
+            global.stage.disconnect(this._stageKeyPressId);
+        this._stageKeyPressId = 0;
     }
 
     _updateBackgrounds() {
-- 
2.21.0