diff --git a/gnome-shell-extensions.spec b/gnome-shell-extensions.spec index 6416204..5fc57ef 100644 --- a/gnome-shell-extensions.spec +++ b/gnome-shell-extensions.spec @@ -6,7 +6,7 @@ Name: gnome-shell-extensions Version: 3.32.1 -Release: 42%{?dist} +Release: 43%{?dist} Summary: Modify and extend GNOME Shell functionality and behavior Group: User Interface/Desktops @@ -63,6 +63,7 @@ Patch0034: 0001-desktop-icons-Fix-k-in-.desktop-files.patch Patch0035: window-list-attention-indicator.patch Patch0036: apps-menu-custom-layout-manager.patch Patch0037: dash-to-panel-attention-indicator.patch +Patch0038: improve-workspace-names.patch %description GNOME Shell Extensions is a collection of extensions providing additional and @@ -574,9 +575,14 @@ cp $RPM_SOURCE_DIR/gnome-classic.desktop $RPM_BUILD_ROOT%{_datadir}/xsessions %files -n %{pkg_prefix}-workspace-indicator %{_datadir}/gnome-shell/extensions/workspace-indicator*/ +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml %changelog +* Wed Jul 16 2025 Florian Müllner - 3.32.1-43 +- Make workspace names more prominent + Resolves: RHEL-96219 + * Tue May 06 2025 Florian Müllner - 3.32.1-42 - Indicate urgency-hint in dash-to-panel Resolves: RHEL-76834 diff --git a/improve-workspace-names.patch b/improve-workspace-names.patch new file mode 100644 index 0000000..e31ac04 --- /dev/null +++ b/improve-workspace-names.patch @@ -0,0 +1,6490 @@ +From 7e65ba671f3fe2999a7b5a473f934879a540d734 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 23 Mar 2022 19:59:14 +0100 +Subject: [PATCH 01/43] build: Remove unused stylesheets + +The only reason for installing empty stylesheets is minimizing +build system differences between extensions. That's not a very +good reason and we don't do this for other optional files like +schemas. + +Part-of: +--- + extensions/apps-menu/meson.build | 1 + + extensions/auto-move-windows/stylesheet.css | 1 - + extensions/classification-banner/meson.build | 1 + + extensions/custom-menu/stylesheet.css | 1 - + extensions/dash-to-dock/meson.build | 1 + + extensions/dash-to-panel/meson.build | 1 + + extensions/desktop-icons/meson.build | 1 + + extensions/drive-menu/meson.build | 1 + + extensions/gesture-inhibitor/stylesheet.css | 1 - + extensions/heads-up-display/meson.build | 1 + + extensions/launch-new-instance/stylesheet.css | 1 - + extensions/meson.build | 2 +- + extensions/meson.build.template | 1 + + extensions/native-window-placement/meson.build | 1 + + extensions/panel-favorites/meson.build | 1 + + extensions/places-menu/meson.build | 1 + + extensions/screenshot-window-sizer/meson.build | 1 + + extensions/top-icons/stylesheet.css | 1 - + extensions/updates-dialog/stylesheet.css | 1 - + extensions/user-theme/stylesheet.css | 1 - + extensions/window-list/meson.build | 1 + + extensions/windowsNavigator/meson.build | 1 + + extensions/workspace-indicator/meson.build | 1 + + 23 files changed, 16 insertions(+), 8 deletions(-) + delete mode 100644 extensions/auto-move-windows/stylesheet.css + delete mode 100644 extensions/custom-menu/stylesheet.css + delete mode 100644 extensions/gesture-inhibitor/stylesheet.css + delete mode 100644 extensions/launch-new-instance/stylesheet.css + delete mode 100644 extensions/top-icons/stylesheet.css + delete mode 100644 extensions/updates-dialog/stylesheet.css + delete mode 100644 extensions/user-theme/stylesheet.css + +diff --git a/extensions/apps-menu/meson.build b/extensions/apps-menu/meson.build +index 48504f63..6b9bb19c 100644 +--- a/extensions/apps-menu/meson.build ++++ b/extensions/apps-menu/meson.build +@@ -3,3 +3,4 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') +diff --git a/extensions/auto-move-windows/stylesheet.css b/extensions/auto-move-windows/stylesheet.css +deleted file mode 100644 +index 25134b65..00000000 +--- a/extensions/auto-move-windows/stylesheet.css ++++ /dev/null +@@ -1 +0,0 @@ +-/* This extensions requires no special styling */ +diff --git a/extensions/classification-banner/meson.build b/extensions/classification-banner/meson.build +index c55a7830..aa943741 100644 +--- a/extensions/classification-banner/meson.build ++++ b/extensions/classification-banner/meson.build +@@ -3,6 +3,7 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') + + extension_sources += files('prefs.js') + extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/custom-menu/stylesheet.css b/extensions/custom-menu/stylesheet.css +deleted file mode 100644 +index 25134b65..00000000 +--- a/extensions/custom-menu/stylesheet.css ++++ /dev/null +@@ -1 +0,0 @@ +-/* This extensions requires no special styling */ +diff --git a/extensions/dash-to-dock/meson.build b/extensions/dash-to-dock/meson.build +index 290374fb..749c2723 100644 +--- a/extensions/dash-to-dock/meson.build ++++ b/extensions/dash-to-dock/meson.build +@@ -3,6 +3,7 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') + + extension_sources += files( + 'appIconIndicators.js', +diff --git a/extensions/dash-to-panel/meson.build b/extensions/dash-to-panel/meson.build +index 70680479..0cae5eef 100644 +--- a/extensions/dash-to-panel/meson.build ++++ b/extensions/dash-to-panel/meson.build +@@ -3,6 +3,7 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') + + extension_sources += files( + 'appIcons.js', +diff --git a/extensions/desktop-icons/meson.build b/extensions/desktop-icons/meson.build +index 8431af62..3512db2c 100644 +--- a/extensions/desktop-icons/meson.build ++++ b/extensions/desktop-icons/meson.build +@@ -3,6 +3,7 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') + + extension_schemas += files(join_paths('schemas', metadata_conf.get('gschemaname') + '.gschema.xml')) + +diff --git a/extensions/drive-menu/meson.build b/extensions/drive-menu/meson.build +index 48504f63..6b9bb19c 100644 +--- a/extensions/drive-menu/meson.build ++++ b/extensions/drive-menu/meson.build +@@ -3,3 +3,4 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') +diff --git a/extensions/gesture-inhibitor/stylesheet.css b/extensions/gesture-inhibitor/stylesheet.css +deleted file mode 100644 +index 37b93f21..00000000 +--- a/extensions/gesture-inhibitor/stylesheet.css ++++ /dev/null +@@ -1 +0,0 @@ +-/* Add your custom extension styling here */ +diff --git a/extensions/heads-up-display/meson.build b/extensions/heads-up-display/meson.build +index 40c3de0a..678fd325 100644 +--- a/extensions/heads-up-display/meson.build ++++ b/extensions/heads-up-display/meson.build +@@ -3,6 +3,7 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') + + extension_sources += files('headsUpMessage.js', 'prefs.js') + extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/launch-new-instance/stylesheet.css b/extensions/launch-new-instance/stylesheet.css +deleted file mode 100644 +index 25134b65..00000000 +--- a/extensions/launch-new-instance/stylesheet.css ++++ /dev/null +@@ -1 +0,0 @@ +-/* This extensions requires no special styling */ +diff --git a/extensions/meson.build b/extensions/meson.build +index d1bf92c9..e8945ed3 100644 +--- a/extensions/meson.build ++++ b/extensions/meson.build +@@ -15,7 +15,7 @@ foreach e : all_extensions + metadata_conf.set('url', 'https://gitlab.gnome.org/GNOME/gnome-shell-extensions') + + extension_sources = files(e + '/extension.js') +- extension_data = files(e + '/stylesheet.css') ++ extension_data = [] + + subdir(e) + +diff --git a/extensions/meson.build.template b/extensions/meson.build.template +index e83e528b..a9915994 100644 +--- a/extensions/meson.build.template ++++ b/extensions/meson.build.template +@@ -4,5 +4,6 @@ extension_data += configure_file( + configuration: metadata_conf + ) + ++# extension_data += files('stylesheet.css') + # extension_sources += files('prefs.js') + # extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/native-window-placement/meson.build b/extensions/native-window-placement/meson.build +index 585c02da..cf73fc03 100644 +--- a/extensions/native-window-placement/meson.build ++++ b/extensions/native-window-placement/meson.build +@@ -5,3 +5,4 @@ extension_data += configure_file( + ) + + extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') ++extension_data += files('stylesheet.css') +diff --git a/extensions/panel-favorites/meson.build b/extensions/panel-favorites/meson.build +index ab6967fa..d335e362 100644 +--- a/extensions/panel-favorites/meson.build ++++ b/extensions/panel-favorites/meson.build +@@ -9,3 +9,4 @@ extension_sources += files( + ) + + extension_schemas += files('schemas/' + metadata_conf.get('gschemaname') + '.gschema.xml') ++extension_data += files('stylesheet.css') +diff --git a/extensions/places-menu/meson.build b/extensions/places-menu/meson.build +index d9a59691..cbc2a02b 100644 +--- a/extensions/places-menu/meson.build ++++ b/extensions/places-menu/meson.build +@@ -3,5 +3,6 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') + + extension_sources += files('placeDisplay.js') +diff --git a/extensions/screenshot-window-sizer/meson.build b/extensions/screenshot-window-sizer/meson.build +index 585c02da..8257dee0 100644 +--- a/extensions/screenshot-window-sizer/meson.build ++++ b/extensions/screenshot-window-sizer/meson.build +@@ -3,5 +3,6 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') + + extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/top-icons/stylesheet.css b/extensions/top-icons/stylesheet.css +deleted file mode 100644 +index 25134b65..00000000 +--- a/extensions/top-icons/stylesheet.css ++++ /dev/null +@@ -1 +0,0 @@ +-/* This extensions requires no special styling */ +diff --git a/extensions/updates-dialog/stylesheet.css b/extensions/updates-dialog/stylesheet.css +deleted file mode 100644 +index 25134b65..00000000 +--- a/extensions/updates-dialog/stylesheet.css ++++ /dev/null +@@ -1 +0,0 @@ +-/* This extensions requires no special styling */ +diff --git a/extensions/user-theme/stylesheet.css b/extensions/user-theme/stylesheet.css +deleted file mode 100644 +index 6d914832..00000000 +--- a/extensions/user-theme/stylesheet.css ++++ /dev/null +@@ -1 +0,0 @@ +-/* none used */ +diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build +index 34d7c3fd..599f45e1 100644 +--- a/extensions/window-list/meson.build ++++ b/extensions/window-list/meson.build +@@ -3,6 +3,7 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') + + extension_sources += files('prefs.js', 'windowPicker.js', 'workspaceIndicator.js') + extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/windowsNavigator/meson.build b/extensions/windowsNavigator/meson.build +index 48504f63..6b9bb19c 100644 +--- a/extensions/windowsNavigator/meson.build ++++ b/extensions/windowsNavigator/meson.build +@@ -3,3 +3,4 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') +diff --git a/extensions/workspace-indicator/meson.build b/extensions/workspace-indicator/meson.build +index 71efa039..19858a39 100644 +--- a/extensions/workspace-indicator/meson.build ++++ b/extensions/workspace-indicator/meson.build +@@ -3,5 +3,6 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') + + extension_sources += files('prefs.js') +-- +2.50.0 + + +From c84bfeb065d0200dee5e5ac1eb7ba30feb105360 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Oct 2020 02:34:41 +0200 +Subject: [PATCH 02/43] workspace-indicator: Use custom layout manager for + thumbnails + +The current code positions window previews explicitly using a fixed +layout manager. For that it relies on a valid parent allocation, +which is error-prone and frequently results in warnings. + +Address this by moving the positioning code into a custom layout +manager, and only update the visibility from the window preview. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/260 +--- + extensions/workspace-indicator/extension.js | 71 +++++++++++---------- + 1 file changed, 38 insertions(+), 33 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index d377f288..101ef7be 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -35,27 +35,15 @@ let WindowPreview = GObject.registerClass({ + this.connect('destroy', this._onDestroy.bind(this)); + + this._sizeChangedId = this._window.connect('size-changed', +- this._relayout.bind(this)); ++ () => this.queue_relayout()); + this._positionChangedId = this._window.connect('position-changed', +- this._relayout.bind(this)); ++ () => this.queue_relayout()); + this._minimizedChangedId = this._window.connect('notify::minimized', +- this._relayout.bind(this)); ++ this._updateVisible.bind(this)); + this._monitorEnteredId = global.display.connect('window-entered-monitor', +- this._relayout.bind(this)); ++ this._updateVisible.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._updateVisible.bind(this)); + + this._focusChangedId = global.display.connect('notify::focus-window', + this._onFocusChanged.bind(this)); +@@ -67,6 +55,10 @@ let WindowPreview = GObject.registerClass({ + return this._window.get_compositor_private(); + } + ++ get metaWindow() { ++ return this._window; ++ } ++ + _onDestroy() { + this._window.disconnect(this._sizeChangedId); + this._window.disconnect(this._positionChangedId); +@@ -74,8 +66,6 @@ let WindowPreview = GObject.registerClass({ + global.display.disconnect(this._monitorEnteredId); + global.display.disconnect(this._monitorLeftId); + global.display.disconnect(this._focusChangedId); +- if (this._laterId) +- Meta.later_remove(this._laterId); + } + + _onFocusChanged() { +@@ -85,25 +75,40 @@ let WindowPreview = GObject.registerClass({ + this.remove_style_class_name('active'); + } + +- _relayout() { ++ _updateVisible() { + let monitor = Main.layoutManager.findIndexForActor(this); + this.visible = monitor == this._window.get_monitor() && + this._window.showing_on_its_workspace(); ++ } ++}); + +- if (!this.visible) +- return; ++let WorkspaceLayout = GObject.registerClass( ++class WorkspaceLayout extends Clutter.LayoutManager { ++ vfunc_get_preferred_width() { ++ return [0, 0]; ++ } + +- 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; ++ vfunc_get_preferred_height() { ++ return [0, 0]; ++ } + +- 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)); ++ vfunc_allocate(container, box, flags) { ++ const monitor = Main.layoutManager.findIndexForActor(container); ++ const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); ++ const hscale = box.get_width() / workArea.width; ++ const vscale = box.get_height() / workArea.height; ++ ++ for (const child of container.get_children()) { ++ const childBox = new Clutter.ActorBox(); ++ const frameRect = child.metaWindow.get_frame_rect(); ++ childBox.set_size( ++ Math.min(frameRect.width, workArea.width) * hscale, ++ Math.min(frameRect.height, workArea.height) * vscale); ++ childBox.set_origin( ++ Math.round(frameRect.x * hscale), ++ Math.round(frameRect.y * vscale)); ++ child.allocate(childBox, flags); ++ } + } + }); + +@@ -114,7 +119,7 @@ let WorkspaceThumbnail = GObject.registerClass({ + super._init({ + style_class: 'workspace', + child: new Clutter.Actor({ +- layout_manager: new Clutter.BinLayout(), ++ layout_manager: new WorkspaceLayout(), + clip_to_allocation: true + }), + x_fill: true, +-- +2.50.0 + + +From e2fd7a6f4ab4f783bc475a6a9cf3156ab4ddecad Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 12:38:33 +0100 +Subject: [PATCH 03/43] workspace-indicator: Move indicator code into separate + file + +Shortly after the window-list extension was added, it gained a +workspace switcher based on the workspace indicator extension. + +Duplicating the code wasn't a big issue while the switcher was +a simple menu, but since it gained previews with a fair bit of +custom styling, syncing changes between the two extensions has +become tedious, in particular as the two copies have slightly +diverged over time. + +In order to allow the two copies to converge again, the indicator +code needs to be separate from the extension boilerplate, so +split out the code into a separate module. +--- + extensions/workspace-indicator/extension.js | 443 +----------------- + extensions/workspace-indicator/meson.build | 2 +- + .../workspace-indicator/workspaceIndicator.js | 443 ++++++++++++++++++ + po/POTFILES.in | 2 +- + 4 files changed, 447 insertions(+), 443 deletions(-) + create mode 100644 extensions/workspace-indicator/workspaceIndicator.js + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 101ef7be..6f7d07c5 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -1,450 +1,11 @@ + // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- + /* 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 Tweener = imports.ui.tweener; +- +-const Gettext = imports.gettext.domain('gnome-shell-extensions'); +-const _ = Gettext.gettext; +- +-const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; +-const WORKSPACE_KEY = 'workspace-names'; +- +-const TOOLTIP_OFFSET = 6; +-const TOOLTIP_ANIMATION_TIME = 150; +- +-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.queue_relayout()); +- this._positionChangedId = this._window.connect('position-changed', +- () => this.queue_relayout()); +- this._minimizedChangedId = this._window.connect('notify::minimized', +- this._updateVisible.bind(this)); +- this._monitorEnteredId = global.display.connect('window-entered-monitor', +- this._updateVisible.bind(this)); +- this._monitorLeftId = global.display.connect('window-left-monitor', +- this._updateVisible.bind(this)); +- +- 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(); +- } +- +- get metaWindow() { +- return this._window; +- } +- +- _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); +- } +- +- _onFocusChanged() { +- if (global.display.focus_window == this._window) +- this.add_style_class_name('active'); +- else +- this.remove_style_class_name('active'); +- } +- +- _updateVisible() { +- let monitor = Main.layoutManager.findIndexForActor(this); +- this.visible = monitor == this._window.get_monitor() && +- this._window.showing_on_its_workspace(); +- } +-}); +- +-let WorkspaceLayout = GObject.registerClass( +-class WorkspaceLayout extends Clutter.LayoutManager { +- vfunc_get_preferred_width() { +- return [0, 0]; +- } +- +- vfunc_get_preferred_height() { +- return [0, 0]; +- } +- +- vfunc_allocate(container, box, flags) { +- const monitor = Main.layoutManager.findIndexForActor(container); +- const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); +- const hscale = box.get_width() / workArea.width; +- const vscale = box.get_height() / workArea.height; +- +- for (const child of container.get_children()) { +- const childBox = new Clutter.ActorBox(); +- const frameRect = child.metaWindow.get_frame_rect(); +- childBox.set_size( +- Math.min(frameRect.width, workArea.width) * hscale, +- Math.min(frameRect.height, workArea.height) * vscale); +- childBox.set_origin( +- Math.round(frameRect.x * hscale), +- Math.round(frameRect.y * vscale)); +- child.allocate(childBox, flags); +- } +- } +-}); +- +-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 WorkspaceLayout(), +- clip_to_allocation: true +- }), +- x_fill: true, +- y_fill: true +- }); +- +- this._tooltip = new St.Label({ +- style_class: 'dash-label', +- visible: false, +- }); +- Main.uiGroup.add_child(this._tooltip); +- +- this.connect('destroy', this._onDestroy.bind(this)); +- this.connect('notify::hover', this._syncTooltip.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 +- on_clicked() { +- let ws = global.workspace_manager.get_workspace_by_index(this._index); +- if (ws) +- ws.activate(global.get_current_time()); +- } +- +- _syncTooltip() { +- if (this.hover) { +- this._tooltip.text = Meta.prefs_get_workspace_name(this._index); +- this._tooltip.opacity = 0; +- this._tooltip.show(); +- +- const [stageX, stageY] = this.get_transformed_position(); +- const thumbWidth = this.allocation.get_width(); +- const thumbHeight = this.allocation.get_height(); +- const tipWidth = this._tooltip.width; +- const xOffset = Math.floor((thumbWidth - tipWidth) / 2); +- const monitor = Main.layoutManager.findMonitorForActor(this); +- const x = Math.min( +- Math.max(stageX + xOffset, monitor.x), +- monitor.x + monitor.width - tipWidth); +- const y = stageY + thumbHeight + TOOLTIP_OFFSET; +- this._tooltip.set_position(x, y); +- } +- +- Tweener.addTween(this._tooltip, { +- opacity: this.hover ? 255 : 0, +- time: TOOLTIP_ANIMATION_TIME / 1000, +- transition: 'easeOutQuad', +- onComplete: () => (this._tooltip.visible = this.hover), +- }); +- } +- +- _onDestroy() { +- this._tooltip.destroy(); +- +- this._workspace.disconnect(this._windowAddedId); +- this._workspace.disconnect(this._windowRemovedId); +- global.display.disconnect(this._restackedId); +- } +-}); +- +-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(); +- this._statusLabel = new St.Label({ +- style_class: 'panel-workspace-indicator', +- y_align: Clutter.ActorAlign.CENTER, +- text: this._labelText() +- }); +- +- 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(); +- this.menu.addMenuItem(this._workspaceSection); +- +- this._workspaceManagerSignals = [ +- workspaceManager.connect_after('notify::n-workspaces', +- this._nWorkspacesChanged.bind(this)), +- workspaceManager.connect_after('workspace-switched', +- 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( +- `changed::${WORKSPACE_KEY}`, +- this._updateMenuLabels.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; +- } +- +- Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); +- +- super._onDestroy(); +- } +- +- _onWorkspaceOrientationChanged() { +- let vertical = global.workspace_manager.layout_rows == -1; +- this.reactive = vertical; +- +- 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() { +- 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() { +- for (let i = 0; i < this._workspacesItems.length; i++) { +- this._workspacesItems[i].setOrnament(i == this._currentWorkspace +- ? PopupMenu.Ornament.DOT +- : PopupMenu.Ornament.NONE); +- } +- } +- +- _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; +- return (workspaceIndex + 1).toString(); +- } +- 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; +- +- this._workspaceSection.removeAll(); +- 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._activate(actor.workspaceId); +- }); +- +- if (i == this._currentWorkspace) +- this._workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); +- } +- +- 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; +- +- 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 = global.workspace_manager.get_active_workspace_index() + diff; +- this._activate(newIndex); +- } +-}); ++const Me = ExtensionUtils.getCurrentExtension(); ++const { WorkspaceIndicator } = Me.imports.workspaceIndicator; + + function init() { + ExtensionUtils.initTranslations(); +diff --git a/extensions/workspace-indicator/meson.build b/extensions/workspace-indicator/meson.build +index 19858a39..eb25b9cc 100644 +--- a/extensions/workspace-indicator/meson.build ++++ b/extensions/workspace-indicator/meson.build +@@ -5,4 +5,4 @@ extension_data += configure_file( + ) + extension_data += files('stylesheet.css') + +-extension_sources += files('prefs.js') ++extension_sources += files('prefs.js', 'workspaceIndicator.js') +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +new file mode 100644 +index 00000000..83a0dffa +--- /dev/null ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -0,0 +1,443 @@ ++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; ++const Tweener = imports.ui.tweener; ++ ++const Gettext = imports.gettext.domain('gnome-shell-extensions'); ++const _ = Gettext.gettext; ++ ++const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; ++const WORKSPACE_KEY = 'workspace-names'; ++ ++const TOOLTIP_OFFSET = 6; ++const TOOLTIP_ANIMATION_TIME = 150; ++ ++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.queue_relayout()); ++ this._positionChangedId = this._window.connect('position-changed', ++ () => this.queue_relayout()); ++ this._minimizedChangedId = this._window.connect('notify::minimized', ++ this._updateVisible.bind(this)); ++ this._monitorEnteredId = global.display.connect('window-entered-monitor', ++ this._updateVisible.bind(this)); ++ this._monitorLeftId = global.display.connect('window-left-monitor', ++ this._updateVisible.bind(this)); ++ ++ 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(); ++ } ++ ++ get metaWindow() { ++ return this._window; ++ } ++ ++ _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); ++ } ++ ++ _onFocusChanged() { ++ if (global.display.focus_window == this._window) ++ this.add_style_class_name('active'); ++ else ++ this.remove_style_class_name('active'); ++ } ++ ++ _updateVisible() { ++ let monitor = Main.layoutManager.findIndexForActor(this); ++ this.visible = monitor == this._window.get_monitor() && ++ this._window.showing_on_its_workspace(); ++ } ++}); ++ ++let WorkspaceLayout = GObject.registerClass( ++class WorkspaceLayout extends Clutter.LayoutManager { ++ vfunc_get_preferred_width() { ++ return [0, 0]; ++ } ++ ++ vfunc_get_preferred_height() { ++ return [0, 0]; ++ } ++ ++ vfunc_allocate(container, box, flags) { ++ const monitor = Main.layoutManager.findIndexForActor(container); ++ const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); ++ const hscale = box.get_width() / workArea.width; ++ const vscale = box.get_height() / workArea.height; ++ ++ for (const child of container.get_children()) { ++ const childBox = new Clutter.ActorBox(); ++ const frameRect = child.metaWindow.get_frame_rect(); ++ childBox.set_size( ++ Math.min(frameRect.width, workArea.width) * hscale, ++ Math.min(frameRect.height, workArea.height) * vscale); ++ childBox.set_origin( ++ Math.round(frameRect.x * hscale), ++ Math.round(frameRect.y * vscale)); ++ child.allocate(childBox, flags); ++ } ++ } ++}); ++ ++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 WorkspaceLayout(), ++ clip_to_allocation: true ++ }), ++ x_fill: true, ++ y_fill: true ++ }); ++ ++ this._tooltip = new St.Label({ ++ style_class: 'dash-label', ++ visible: false, ++ }); ++ Main.uiGroup.add_child(this._tooltip); ++ ++ this.connect('destroy', this._onDestroy.bind(this)); ++ this.connect('notify::hover', this._syncTooltip.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 ++ on_clicked() { ++ let ws = global.workspace_manager.get_workspace_by_index(this._index); ++ if (ws) ++ ws.activate(global.get_current_time()); ++ } ++ ++ _syncTooltip() { ++ if (this.hover) { ++ this._tooltip.text = Meta.prefs_get_workspace_name(this._index); ++ this._tooltip.opacity = 0; ++ this._tooltip.show(); ++ ++ const [stageX, stageY] = this.get_transformed_position(); ++ const thumbWidth = this.allocation.get_width(); ++ const thumbHeight = this.allocation.get_height(); ++ const tipWidth = this._tooltip.width; ++ const xOffset = Math.floor((thumbWidth - tipWidth) / 2); ++ const monitor = Main.layoutManager.findMonitorForActor(this); ++ const x = Math.min( ++ Math.max(stageX + xOffset, monitor.x), ++ monitor.x + monitor.width - tipWidth); ++ const y = stageY + thumbHeight + TOOLTIP_OFFSET; ++ this._tooltip.set_position(x, y); ++ } ++ ++ Tweener.addTween(this._tooltip, { ++ opacity: this.hover ? 255 : 0, ++ time: TOOLTIP_ANIMATION_TIME / 1000, ++ transition: 'easeOutQuad', ++ onComplete: () => (this._tooltip.visible = this.hover), ++ }); ++ } ++ ++ _onDestroy() { ++ this._tooltip.destroy(); ++ ++ this._workspace.disconnect(this._windowAddedId); ++ this._workspace.disconnect(this._windowRemovedId); ++ global.display.disconnect(this._restackedId); ++ } ++}); ++ ++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(); ++ this._statusLabel = new St.Label({ ++ style_class: 'panel-workspace-indicator', ++ y_align: Clutter.ActorAlign.CENTER, ++ text: this._labelText() ++ }); ++ ++ 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(); ++ this.menu.addMenuItem(this._workspaceSection); ++ ++ this._workspaceManagerSignals = [ ++ workspaceManager.connect_after('notify::n-workspaces', ++ this._nWorkspacesChanged.bind(this)), ++ workspaceManager.connect_after('workspace-switched', ++ 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( ++ `changed::${WORKSPACE_KEY}`, ++ this._updateMenuLabels.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; ++ } ++ ++ Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); ++ ++ super._onDestroy(); ++ } ++ ++ _onWorkspaceOrientationChanged() { ++ let vertical = global.workspace_manager.layout_rows == -1; ++ this.reactive = vertical; ++ ++ 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() { ++ 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() { ++ for (let i = 0; i < this._workspacesItems.length; i++) { ++ this._workspacesItems[i].setOrnament(i == this._currentWorkspace ++ ? PopupMenu.Ornament.DOT ++ : PopupMenu.Ornament.NONE); ++ } ++ } ++ ++ _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; ++ return (workspaceIndex + 1).toString(); ++ } ++ 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; ++ ++ this._workspaceSection.removeAll(); ++ 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._activate(actor.workspaceId); ++ }); ++ ++ if (i == this._currentWorkspace) ++ this._workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); ++ } ++ ++ 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; ++ ++ 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 = global.workspace_manager.get_active_workspace_index() + diff; ++ this._activate(newIndex); ++ } ++}); +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 55f0e9aa..5cdb710f 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -18,5 +18,5 @@ extensions/window-list/extension.js + extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml + extensions/window-list/prefs.js + extensions/windowsNavigator/extension.js +-extensions/workspace-indicator/extension.js + extensions/workspace-indicator/prefs.js ++extensions/workspace-indicator/workspaceIndicator.js +-- +2.50.0 + + +From dd928d44f1fbc1536c4ae30bad2130c1fc4432ac Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 19:09:38 +0100 +Subject: [PATCH 04/43] workspace-indicator: Use descendant style selectors + +Add a style class to the indicator itself, and only select +descendant elements. This allows using the briefer class names +from the window-list extension without too much risk of conflicts. +--- + extensions/workspace-indicator/stylesheet.css | 32 +++++++------------ + .../workspace-indicator/workspaceIndicator.js | 6 ++-- + 2 files changed, 16 insertions(+), 22 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css +index 8c101e79..3f89359b 100644 +--- a/extensions/workspace-indicator/stylesheet.css ++++ b/extensions/workspace-indicator/stylesheet.css +@@ -1,29 +1,21 @@ +-.panel-workspace-indicator { +- padding: 0 8px; +- border: 1px solid #cccccc; ++.workspace-indicator .status-label { ++ padding: 0 8px; + } + +-.panel-workspace-indicator-box { +- padding: 2px 0; ++.workspace-indicator .workspaces-box { ++ padding: 4px 0; ++ spacing: 4px; + } + +-.panel-workspace-indicator-box .workspace { +- border: 1px solid #cccccc; +- width: 48px; ++.workspace-indicator .workspace { ++ width: 40px; ++ border: 2px solid #000; ++ border-radius: 2px; ++ background-color: #595959; + } + +-.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; ++.workspace-indicator .workspace.active { ++ border-color: #fff; + } + + .workspace-indicator-window-preview { +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 83a0dffa..2b102117 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -257,6 +257,8 @@ class WorkspaceIndicator extends PanelMenu.Button { + _init() { + super._init(0.0, _('Workspace Indicator')); + ++ this.add_style_class_name('workspace-indicator'); ++ + let container = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + x_expand: true, +@@ -268,7 +270,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + this._currentWorkspace = workspaceManager.get_active_workspace_index(); + this._statusLabel = new St.Label({ +- style_class: 'panel-workspace-indicator', ++ style_class: 'status-label', + y_align: Clutter.ActorAlign.CENTER, + text: this._labelText() + }); +@@ -276,7 +278,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + container.add_actor(this._statusLabel); + + this._thumbnailsBox = new St.BoxLayout({ +- style_class: 'panel-workspace-indicator-box', ++ style_class: 'workspaces-box', + y_expand: true, + reactive: true + }); +-- +2.50.0 + + +From 9a92b49c1835e44b5f3ddf2088f24cf3dd82b8c1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 12:48:43 +0100 +Subject: [PATCH 05/43] window-list: Use consistent style class prefix + +This will eventually allow us to re-use the workspace-indicator +extension without changing anything but the used prefix. +--- + extensions/window-list/classic.css | 4 ++-- + extensions/window-list/stylesheet.css | 11 +++++------ + extensions/window-list/workspaceIndicator.js | 2 +- + 3 files changed, 8 insertions(+), 9 deletions(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index 7079d3ef..02286041 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -57,12 +57,12 @@ + background-color: #ccc; + } + +-.window-list-window-preview { ++.window-list-workspace-indicator-window-preview { + background-color: #ededed; + border: 1px solid #ccc; + } + +-.window-list-window-preview.active { ++.window-list-workspace-indicator-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 2c98aafe..4c9ca671 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -121,14 +121,13 @@ + background-color: rgba(200, 200, 200, .3); + } + +-.window-list-window-preview { +- background-color: #252525; +- border: 1px solid #ccc; ++.window-list-workspace-indicator-window-preview { ++ background-color: #bebebe; ++ border: 1px solid #828282; + } + +-.window-list-window-preview.active { +- background-color: #353535; +- border: 2px solid #ccc; ++.window-list-workspace-indicator-window-preview.active { ++ background-color: #d4d4d4; + } + + .notification { +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 8ae9b288..4e8f1aff 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -18,7 +18,7 @@ let WindowPreview = GObject.registerClass({ + }, class WindowPreview extends St.Button { + _init(window) { + super._init({ +- style_class: 'window-list-window-preview' ++ style_class: 'window-list-workspace-indicator-window-preview', + }); + + this._delegate = this; +-- +2.50.0 + + +From 6db2bc97a0aba60650abff4e82a1b5683db474c5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 23 Feb 2024 01:59:15 +0100 +Subject: [PATCH 06/43] workspace-indicator: Allow overriding base style class + +This will allow reusing the code from the window-list extension +without limiting the ability to specify styling that only applies +to one of the extensions. +--- + .../workspace-indicator/workspaceIndicator.js | 13 ++++++++++--- + 1 file changed, 10 insertions(+), 3 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 2b102117..57a3f7ec 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -15,12 +15,14 @@ const WORKSPACE_KEY = 'workspace-names'; + const TOOLTIP_OFFSET = 6; + const TOOLTIP_ANIMATION_TIME = 150; + ++let baseStyleClassName = ''; ++ + let WindowPreview = GObject.registerClass({ + GTypeName: 'WorkspaceIndicatorWindowPreview' + }, class WindowPreview extends St.Button { + _init(window) { + super._init({ +- style_class: 'workspace-indicator-window-preview' ++ style_class: `${baseStyleClassName}-window-preview`, + }); + + this._delegate = this; +@@ -254,10 +256,15 @@ let WorkspaceThumbnail = GObject.registerClass({ + + let WorkspaceIndicator = GObject.registerClass( + class WorkspaceIndicator extends PanelMenu.Button { +- _init() { ++ _init(params = {}) { + super._init(0.0, _('Workspace Indicator')); + +- this.add_style_class_name('workspace-indicator'); ++ const { ++ baseStyleClass = 'workspace-indicator', ++ } = params; ++ ++ baseStyleClassName = baseStyleClass; ++ this.add_style_class_name(baseStyleClassName); + + let container = new St.Widget({ + layout_manager: new Clutter.BinLayout(), +-- +2.50.0 + + +From 8f4cc8742093a791bf64470ea2e04b18c37a59e1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 11 Jul 2025 12:09:18 +0200 +Subject: [PATCH 07/43] workspace-indicator: Support prefix for GType names + +GTypes have to be unique, so to allow the code to be eventually +shared with the window-list extension, support adding a custom +prefix to type names. +--- + .../workspace-indicator/workspaceIndicator.js | 16 ++++++++++------ + 1 file changed, 10 insertions(+), 6 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 57a3f7ec..2a026c5d 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -17,8 +17,10 @@ const TOOLTIP_ANIMATION_TIME = 150; + + let baseStyleClassName = ''; + ++const TypePrefix = window.TypePrefix || ''; ++ + let WindowPreview = GObject.registerClass({ +- GTypeName: 'WorkspaceIndicatorWindowPreview' ++ GTypeName: `${TypePrefix}WorkspaceIndicatorWindowPreview` + }, class WindowPreview extends St.Button { + _init(window) { + super._init({ +@@ -80,8 +82,9 @@ let WindowPreview = GObject.registerClass({ + } + }); + +-let WorkspaceLayout = GObject.registerClass( +-class WorkspaceLayout extends Clutter.LayoutManager { ++let WorkspaceLayout = GObject.registerClass({ ++ GTypeName: `${TypePrefix}WorkspaceIndicatorWorkspaceLayout` ++}, class WorkspaceLayout extends Clutter.LayoutManager { + vfunc_get_preferred_width() { + return [0, 0]; + } +@@ -111,7 +114,7 @@ class WorkspaceLayout extends Clutter.LayoutManager { + }); + + let WorkspaceThumbnail = GObject.registerClass({ +- GTypeName: 'WorkspaceIndicatorWorkspaceThumbnail' ++ GTypeName: `${TypePrefix}WorkspaceIndicatorWorkspaceThumbnail` + }, class WorkspaceThumbnail extends St.Button { + _init(index) { + super._init({ +@@ -254,8 +257,9 @@ let WorkspaceThumbnail = GObject.registerClass({ + } + }); + +-let WorkspaceIndicator = GObject.registerClass( +-class WorkspaceIndicator extends PanelMenu.Button { ++let WorkspaceIndicator = GObject.registerClass({ ++ GTypeName: `${TypePrefix}WorkspaceIndicator` ++}, class WorkspaceIndicator extends PanelMenu.Button { + _init(params = {}) { + super._init(0.0, _('Workspace Indicator')); + +-- +2.50.0 + + +From 09116b6f8e6477c4347fec0f035e74d508b08288 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 23 Feb 2024 01:58:50 +0100 +Subject: [PATCH 08/43] window-list: Override base style class + +Apply the changes from the last commit to the workspace-indicator +copy, and override the base style class from the extension. + +This will eventually allow us to share the exact same code between +the two extensions, but still use individual styling if necessary. +--- + extensions/window-list/extension.js | 4 +++- + extensions/window-list/workspaceIndicator.js | 15 ++++++++++++--- + 2 files changed, 15 insertions(+), 4 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 11ac393b..e1bbc0de 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -813,7 +813,9 @@ class WindowList { + let indicatorsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.END }); + box.add(indicatorsBox); + +- this._workspaceIndicator = new WorkspaceIndicator(); ++ this._workspaceIndicator = new WorkspaceIndicator({ ++ baseStyleClass: 'window-list-workspace-indicator', ++ }); + indicatorsBox.add(this._workspaceIndicator.container, { expand: false, y_fill: true }); + + this._mutterSettings = new Gio.Settings({ schema_id: 'org.gnome.mutter' }); +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 4e8f1aff..48a8eb22 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -13,12 +13,14 @@ const _ = Gettext.gettext; + const TOOLTIP_OFFSET = 6; + const TOOLTIP_ANIMATION_TIME = 150; + ++let baseStyleClassName = ''; ++ + let WindowPreview = GObject.registerClass({ + GTypeName: 'WindowListWindowPreview' + }, class WindowPreview extends St.Button { + _init(window) { + super._init({ +- style_class: 'window-list-workspace-indicator-window-preview', ++ style_class: `${baseStyleClassName}-window-preview`, + }); + + this._delegate = this; +@@ -248,10 +250,17 @@ let WorkspaceThumbnail = GObject.registerClass({ + var WorkspaceIndicator = GObject.registerClass({ + GTypeName: 'WindowListWorkspaceIndicator' + }, class WorkspaceIndicator extends PanelMenu.Button { +- _init() { ++ _init(params = {}) { + super._init(0.0, _('Workspace Indicator'), true); ++ ++ const { ++ baseStyleClass = 'workspace-indicator', ++ } = params; ++ ++ baseStyleClassName = baseStyleClass; ++ this.add_style_class_name(baseStyleClassName); ++ + 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({ +-- +2.50.0 + + +From 86b017644c4a7cb394558f4b96785957aef480ab Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 11 Jul 2025 12:29:50 +0200 +Subject: [PATCH 09/43] window-list: Set TypePrefix for GType names + +Apply the changes from the last commit to the workspace-indicator +copy, and set the type prefix from the extension. + +This will eventually allow us to share the exact same code between +the two extensions, without creating conflicting GTypes. +--- + extensions/window-list/extension.js | 3 +++ + extensions/window-list/workspaceIndicator.js | 8 +++++--- + 2 files changed, 8 insertions(+), 3 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index e1bbc0de..ed886919 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -10,7 +10,10 @@ const Tweener = imports.ui.tweener; + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); + const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker; ++ ++window.TypePrefix = 'WindowList'; + const { WorkspaceIndicator } = Me.imports.workspaceIndicator; ++delete window.TypePrefix; + + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 48a8eb22..c362539e 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -15,8 +15,10 @@ const TOOLTIP_ANIMATION_TIME = 150; + + let baseStyleClassName = ''; + ++const TypePrefix = window.TypePrefix || ''; ++ + let WindowPreview = GObject.registerClass({ +- GTypeName: 'WindowListWindowPreview' ++ GTypeName: `${TypePrefix}WorkspaceIndicatorWindowPreview` + }, class WindowPreview extends St.Button { + _init(window) { + super._init({ +@@ -104,7 +106,7 @@ let WindowPreview = GObject.registerClass({ + }); + + let WorkspaceThumbnail = GObject.registerClass({ +- GTypeName: 'WindowListWorkspaceThumbnail' ++ GTypeName: `${TypePrefix}WorkspaceIndicatorWorkspaceThumbnail` + }, class WorkspaceThumbnail extends St.Button { + _init(index) { + super._init({ +@@ -248,7 +250,7 @@ let WorkspaceThumbnail = GObject.registerClass({ + }); + + var WorkspaceIndicator = GObject.registerClass({ +- GTypeName: 'WindowListWorkspaceIndicator' ++ GTypeName: `${TypePrefix}WorkspaceIndicator` + }, class WorkspaceIndicator extends PanelMenu.Button { + _init(params = {}) { + super._init(0.0, _('Workspace Indicator'), true); +-- +2.50.0 + + +From e0d18355ae3af843bc35412f4379d26af0a9ddcd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 12:48:43 +0100 +Subject: [PATCH 10/43] window-list: Externally adjust workspace menu + +In order to use a PanelMenu.Button in the bottom bar, we have +to tweak its menu a bit. + +We currently handle this inside the indicator, but that means the +code diverges from the original code in the workspace-indicator +extension. + +Avoid this by using a small subclass that handles the adjustments. +--- + extensions/window-list/extension.js | 23 ++++++++++++++++++-- + extensions/window-list/workspaceIndicator.js | 5 +---- + 2 files changed, 22 insertions(+), 6 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index ed886919..394fec1f 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -1,5 +1,5 @@ + /* exported init */ +-const { Clutter, Gio, GLib, Gtk, Meta, Shell, St } = imports.gi; ++const { Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St } = imports.gi; + + const DND = imports.ui.dnd; + const Main = imports.ui.main; +@@ -816,7 +816,7 @@ class WindowList { + let indicatorsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.END }); + box.add(indicatorsBox); + +- this._workspaceIndicator = new WorkspaceIndicator({ ++ this._workspaceIndicator = new BottomWorkspaceIndicator({ + baseStyleClass: 'window-list-workspace-indicator', + }); + indicatorsBox.add(this._workspaceIndicator.container, { expand: false, y_fill: true }); +@@ -1226,6 +1226,25 @@ class WindowList { + } + } + ++const BottomWorkspaceIndicator = GObject.registerClass( ++class BottomWorkspaceIndicator extends WorkspaceIndicator { ++ _init(params) { ++ super._init(params); ++ ++ this.remove_style_class_name('panel-button'); ++ } ++ ++ setMenu(menu) { ++ super.setMenu(menu); ++ ++ if (!menu) ++ return; ++ ++ this.menu.actor.updateArrowSide(St.Side.BOTTOM); ++ this.menu.actor.remove_style_class_name('panel-menu'); ++ } ++}); ++ + class Extension { + constructor() { + this._windowLists = null; +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index c362539e..574ebdca 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -253,7 +253,7 @@ var WorkspaceIndicator = GObject.registerClass({ + GTypeName: `${TypePrefix}WorkspaceIndicator` + }, class WorkspaceIndicator extends PanelMenu.Button { + _init(params = {}) { +- super._init(0.0, _('Workspace Indicator'), true); ++ super._init(0.0, _('Workspace Indicator')); + + const { + baseStyleClass = 'workspace-indicator', +@@ -262,9 +262,6 @@ var WorkspaceIndicator = GObject.registerClass({ + baseStyleClassName = baseStyleClass; + this.add_style_class_name(baseStyleClassName); + +- this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM)); +- this.menu.actor.remove_style_class_name('panel-menu'); +- + let container = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + x_expand: true, +-- +2.50.0 + + +From e3b79e9d113570a89227cd7cb598e20fe61c9bd0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 21 Mar 2024 16:49:35 +0100 +Subject: [PATCH 11/43] window-list: Handle changes to workspace menu + +For now the menu is always set at construction time, however this +will change in the future. Prepare for that by handling the +`menu-set` signal, similar to the top bar. +--- + extensions/window-list/extension.js | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 394fec1f..aada9525 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -831,7 +831,9 @@ class WindowList { + this._updateWorkspaceIndicatorVisibility(); + + this._menuManager = new PopupMenu.PopupMenuManager(this); +- this._menuManager.addMenu(this._workspaceIndicator.menu); ++ this._workspaceIndicator.connect('menu-set', ++ () => this._onWorkspaceMenuSet()); ++ this._onWorkspaceMenuSet(); + + Main.layoutManager.addChrome(this.actor, { + affectsStruts: true, +@@ -931,6 +933,11 @@ class WindowList { + children[newActive].activate(); + } + ++ _onWorkspaceMenuSet() { ++ if (this._workspaceIndicator.menu) ++ this._menuManager.addMenu(this._workspaceIndicator.menu); ++ } ++ + _updatePosition() { + this.actor.set_position(this._monitor.x, + this._monitor.y + this._monitor.height - this.actor.height); +-- +2.50.0 + + +From c8bf13f4e85c1e90535588234a68e66068d02805 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 15:58:39 +0100 +Subject: [PATCH 12/43] workspace-indicator: Don't use SCHEMA/KEY constants + +Each constant is only used once, so all they do is disconnect +the actual value from the code that uses it. + +The copy in the window-list extension just uses the strings directly, +do the same here. +--- + extensions/workspace-indicator/workspaceIndicator.js | 7 ++----- + 1 file changed, 2 insertions(+), 5 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 2a026c5d..b1dfc970 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -9,9 +9,6 @@ const Tweener = imports.ui.tweener; + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; + +-const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; +-const WORKSPACE_KEY = 'workspace-names'; +- + const TOOLTIP_OFFSET = 6; + const TOOLTIP_ANIMATION_TIME = 150; + +@@ -315,9 +312,9 @@ let WorkspaceIndicator = GObject.registerClass({ + this._updateThumbnails(); + this._onWorkspaceOrientationChanged(); + +- this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); ++ this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); + this._settingsChangedId = this._settings.connect( +- `changed::${WORKSPACE_KEY}`, ++ 'changed::workspace-names', + this._updateMenuLabels.bind(this)); + } + +-- +2.50.0 + + +From dbb82cf4778e324defa564584badc136c4f54448 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 18:59:23 +0100 +Subject: [PATCH 13/43] workspace-indicator: Use existing property + +We already track the current workspace index, use that +instead of getting it from the workspace manager again. +--- + extensions/workspace-indicator/workspaceIndicator.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index b1dfc970..7177a810 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -447,7 +447,7 @@ let WorkspaceIndicator = GObject.registerClass({ + return; + } + +- let newIndex = global.workspace_manager.get_active_workspace_index() + diff; ++ const newIndex = this._currentWorkspace + diff; + this._activate(newIndex); + } + }); +-- +2.50.0 + + +From c929efb231745e4e66182a9ee08c5cef2725e9d9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 16:14:24 +0100 +Subject: [PATCH 14/43] workspace-indicator: Don't use menu section + +We never added anything else to the menu, so we can just operate +on the entire menu instead of an intermediate section. + +This removes another difference with the window-list copy. +--- + extensions/workspace-indicator/workspaceIndicator.js | 12 +++++------- + 1 file changed, 5 insertions(+), 7 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 7177a810..9563bbc1 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -294,8 +294,6 @@ let WorkspaceIndicator = GObject.registerClass({ + container.add_actor(this._thumbnailsBox); + + this._workspacesItems = []; +- this._workspaceSection = new PopupMenu.PopupMenuSection(); +- this.menu.addMenuItem(this._workspaceSection); + + this._workspaceManagerSignals = [ + workspaceManager.connect_after('notify::n-workspaces', +@@ -308,7 +306,7 @@ let WorkspaceIndicator = GObject.registerClass({ + + this.connect('scroll-event', this._onScrollEvent.bind(this)); + this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); +- this._createWorkspacesSection(); ++ this._updateMenu(); + this._updateThumbnails(); + this._onWorkspaceOrientationChanged(); + +@@ -356,7 +354,7 @@ let WorkspaceIndicator = GObject.registerClass({ + } + + _nWorkspacesChanged() { +- this._createWorkspacesSection(); ++ this._updateMenu(); + this._updateThumbnails(); + } + +@@ -391,17 +389,17 @@ let WorkspaceIndicator = GObject.registerClass({ + this._workspacesItems[i].label.text = this._labelText(i); + } + +- _createWorkspacesSection() { ++ _updateMenu() { + let workspaceManager = global.workspace_manager; + +- this._workspaceSection.removeAll(); ++ this.menu.removeAll(); + 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.menu.addMenuItem(this._workspacesItems[i]); + this._workspacesItems[i].workspaceId = i; + this._workspacesItems[i].label_actor = this._statusLabel; + this._workspacesItems[i].connect('activate', (actor, _event) => { +-- +2.50.0 + + +From 8d38e180ac3a6832185820589f5f4afa450f4cf1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 13:05:15 +0100 +Subject: [PATCH 15/43] workspace-indicator: Support showing tooltips above + +The indicator is located in the top bar, so tooltips are always +shown below the previews. However supporting showing tooltips +above previews when space permits allows the same code to be +used in the copy that is included with the window-list extension. +--- + extensions/workspace-indicator/workspaceIndicator.js | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 9563bbc1..b87420ac 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -225,15 +225,16 @@ let WorkspaceThumbnail = GObject.registerClass({ + this._tooltip.show(); + + const [stageX, stageY] = this.get_transformed_position(); +- const thumbWidth = this.allocation.get_width(); +- const thumbHeight = this.allocation.get_height(); +- const tipWidth = this._tooltip.width; ++ const [thumbWidth, thumbHeight] = this.allocation.get_size(); ++ const [tipWidth, tipHeight] = this._tooltip.get_size(); + const xOffset = Math.floor((thumbWidth - tipWidth) / 2); + const monitor = Main.layoutManager.findMonitorForActor(this); + const x = Math.min( + Math.max(stageX + xOffset, monitor.x), + monitor.x + monitor.width - tipWidth); +- const y = stageY + thumbHeight + TOOLTIP_OFFSET; ++ const y = stageY - monitor.y > thumbHeight + TOOLTIP_OFFSET ++ ? stageY - tipHeight - TOOLTIP_OFFSET // show above ++ : stageY + thumbHeight + TOOLTIP_OFFSET; // show below + this._tooltip.set_position(x, y); + } + +-- +2.50.0 + + +From a7d7412b8683e843db154c10fe9ffcd01b38d544 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 17:37:16 +0100 +Subject: [PATCH 16/43] workspace-indicator: Only change top bar redirect when + in top bar + +While this is always the case for the workspace indicator, adding +the check will allow to use the same code in the window list. +--- + .../workspace-indicator/workspaceIndicator.js | 23 +++++++++++++++++-- + 1 file changed, 21 insertions(+), 2 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index b87420ac..b772b66d 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -307,6 +307,16 @@ let WorkspaceIndicator = GObject.registerClass({ + + this.connect('scroll-event', this._onScrollEvent.bind(this)); + this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); ++ ++ this._inTopBar = false; ++ this.connect('notify::realized', () => { ++ if (!this.realized) ++ return; ++ ++ this._inTopBar = Main.panel.contains(this); ++ this._updateTopBarRedirect(); ++ }); ++ + this._updateMenu(); + this._updateThumbnails(); + this._onWorkspaceOrientationChanged(); +@@ -326,7 +336,9 @@ let WorkspaceIndicator = GObject.registerClass({ + this._settingsChangedId = 0; + } + +- Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); ++ if (this._inTopBar) ++ Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); ++ this._inTopBar = false; + + super._onDestroy(); + } +@@ -338,9 +350,16 @@ let WorkspaceIndicator = GObject.registerClass({ + this._statusLabel.visible = vertical; + this._thumbnailsBox.visible = !vertical; + ++ this._updateTopBarRedirect(); ++ } ++ ++ _updateTopBarRedirect() { ++ if (!this._inTopBar) ++ return; ++ + // Disable offscreen-redirect when showing the workspace switcher + // so that clip-to-allocation works +- Main.panel.set_offscreen_redirect(vertical ++ Main.panel.set_offscreen_redirect(this._thumbnailsBox.visible + ? Clutter.OffscreenRedirect.ALWAYS + : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY); + } +-- +2.50.0 + + +From e654fa6be51139099f6eaa13f28130a3da0fd556 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 16:13:00 +0100 +Subject: [PATCH 17/43] workspace-indicator: Small cleanup + +The code to update the menu labels is a bit cleaner in the +window-list extension, so use that. +--- + .../workspace-indicator/workspaceIndicator.js | 22 +++++++++---------- + 1 file changed, 11 insertions(+), 11 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index b772b66d..f707be79 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -416,18 +416,18 @@ let WorkspaceIndicator = GObject.registerClass({ + 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.menu.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); +- }); ++ for (let i = 0; i < workspaceManager.n_workspaces; i++) { ++ const item = new PopupMenu.PopupMenuItem(this._labelText(i)); + +- if (i == this._currentWorkspace) +- this._workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); ++ item.connect('activate', ++ () => this._activate(i)); ++ ++ item.setOrnament(i === this._currentWorkspace ++ ? PopupMenu.Ornament.DOT ++ : PopupMenu.Ornament.NONE); ++ ++ this.menu.addMenuItem(item); ++ this._workspacesItems[i] = item; + } + + this._statusLabel.set_text(this._labelText()); +-- +2.50.0 + + +From 3db67516e6bc00f2b9c85aa59b28d9521028fbc9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 16:13:00 +0100 +Subject: [PATCH 18/43] workspace-indicator: Simplify getting status text + +Currently the same method is used to get the label text for the +indicator itself and for the menu items. + +A method that behaves significantly different depending on whether +a parameter is passed is confusing, so only deal with the indicator +label and directly use the mutter API to get the workspace names +for menu items. +--- + .../workspace-indicator/workspaceIndicator.js | 24 +++++++++---------- + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index f707be79..6783b315 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -281,7 +281,7 @@ let WorkspaceIndicator = GObject.registerClass({ + this._statusLabel = new St.Label({ + style_class: 'status-label', + y_align: Clutter.ActorAlign.CENTER, +- text: this._labelText() ++ text: this._getStatusText(), + }); + + container.add_actor(this._statusLabel); +@@ -370,7 +370,7 @@ let WorkspaceIndicator = GObject.registerClass({ + this._updateMenuOrnament(); + this._updateActiveThumbnail(); + +- this._statusLabel.set_text(this._labelText()); ++ this._statusLabel.set_text(this._getStatusText()); + } + + _nWorkspacesChanged() { +@@ -396,17 +396,16 @@ let WorkspaceIndicator = GObject.registerClass({ + } + } + +- _labelText(workspaceIndex) { +- if (workspaceIndex == undefined) { +- workspaceIndex = this._currentWorkspace; +- return (workspaceIndex + 1).toString(); +- } +- return Meta.prefs_get_workspace_name(workspaceIndex); ++ _getStatusText() { ++ const current = this._currentWorkspace + 1; ++ return `${current}`; + } + + _updateMenuLabels() { +- for (let i = 0; i < this._workspacesItems.length; i++) +- this._workspacesItems[i].label.text = this._labelText(i); ++ for (let i = 0; i < this._workspacesItems.length; i++) { ++ const item = this._workspacesItems[i]; ++ item.label.text = Meta.prefs_get_workspace_name(i); ++ } + } + + _updateMenu() { +@@ -417,7 +416,8 @@ let WorkspaceIndicator = GObject.registerClass({ + this._currentWorkspace = workspaceManager.get_active_workspace_index(); + + for (let i = 0; i < workspaceManager.n_workspaces; i++) { +- const item = new PopupMenu.PopupMenuItem(this._labelText(i)); ++ const name = Meta.prefs_get_workspace_name(i); ++ const item = new PopupMenu.PopupMenuItem(name); + + item.connect('activate', + () => this._activate(i)); +@@ -430,7 +430,7 @@ let WorkspaceIndicator = GObject.registerClass({ + this._workspacesItems[i] = item; + } + +- this._statusLabel.set_text(this._labelText()); ++ this._statusLabel.set_text(this._getStatusText()); + } + + _updateThumbnails() { +-- +2.50.0 + + +From 6ae80e3c952f4fb2e557dbd4dc4ab304f9e82eae Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 Feb 2024 04:45:23 +0100 +Subject: [PATCH 19/43] workspace-indicator: Tweak preview style + +Sync sizes and padding with the window-list previews. + +Tone down the colors a bit, but less then the current window-list +style where workspaces blend too much into the background and +the selection is unclear. +--- + extensions/workspace-indicator/stylesheet.css | 19 ++++++++++--------- + 1 file changed, 10 insertions(+), 9 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css +index 3f89359b..c8c2370f 100644 +--- a/extensions/workspace-indicator/stylesheet.css ++++ b/extensions/workspace-indicator/stylesheet.css +@@ -3,24 +3,25 @@ + } + + .workspace-indicator .workspaces-box { +- padding: 4px 0; +- spacing: 4px; ++ padding: 5px; ++ spacing: 3px; + } + + .workspace-indicator .workspace { +- width: 40px; +- border: 2px solid #000; +- border-radius: 2px; +- background-color: #595959; ++ width: 52px; ++ border: 2px solid transparent; ++ border-radius: 4px; ++ background-color: #3f3f3f; + } + + .workspace-indicator .workspace.active { +- border-color: #fff; ++ border-color: #9f9f9f; + } + + .workspace-indicator-window-preview { +- background-color: #252525; +- border: 1px solid #ccc; ++ background-color: #bebebe; ++ border: 1px solid #828282; ++ border-radius: 1px; + } + + .workspace-indicator-window-preview { +-- +2.50.0 + + +From a034be112b74ea09bfa53840ab3c85ce6593bd88 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 23:22:58 +0100 +Subject: [PATCH 20/43] workspace-indicator: Support light style + +The window-list extension already includes light styling for +its copy of the workspace indicator. Just copy that over to +support the light variant here as well. +--- + extensions/workspace-indicator/meson.build | 6 +++- + .../workspace-indicator/stylesheet-dark.css | 29 +++++++++++++++++ + .../workspace-indicator/stylesheet-light.css | 25 +++++++++++++++ + extensions/workspace-indicator/stylesheet.css | 31 +------------------ + 4 files changed, 60 insertions(+), 31 deletions(-) + create mode 100644 extensions/workspace-indicator/stylesheet-dark.css + create mode 100644 extensions/workspace-indicator/stylesheet-light.css + +diff --git a/extensions/workspace-indicator/meson.build b/extensions/workspace-indicator/meson.build +index eb25b9cc..0bf9f023 100644 +--- a/extensions/workspace-indicator/meson.build ++++ b/extensions/workspace-indicator/meson.build +@@ -3,6 +3,10 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) +-extension_data += files('stylesheet.css') ++extension_data += files( ++ 'stylesheet.css', ++ 'stylesheet-dark.css', ++ 'stylesheet-light.css', ++) + + extension_sources += files('prefs.js', 'workspaceIndicator.js') +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +new file mode 100644 +index 00000000..f74f7e88 +--- /dev/null ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -0,0 +1,29 @@ ++.workspace-indicator .status-label { ++ padding: 0 8px; ++} ++ ++.workspace-indicator .workspaces-box { ++ padding: 5px; ++ spacing: 3px; ++} ++ ++.workspace-indicator .workspace { ++ width: 52px; ++ border: 2px solid transparent; ++ border-radius: 4px; ++ background-color: #3f3f3f; ++} ++ ++.workspace-indicator .workspace.active { ++ border-color: #9f9f9f; ++} ++ ++.workspace-indicator-window-preview { ++ background-color: #bebebe; ++ border: 1px solid #828282; ++ border-radius: 1px; ++} ++ ++.workspace-indicator-window-preview.active { ++ background-color: #d4d4d4; ++} +diff --git a/extensions/workspace-indicator/stylesheet-light.css b/extensions/workspace-indicator/stylesheet-light.css +new file mode 100644 +index 00000000..049b6a38 +--- /dev/null ++++ b/extensions/workspace-indicator/stylesheet-light.css +@@ -0,0 +1,25 @@ ++/* ++ * SPDX-FileCopyrightText: 2013 Florian Müllner ++ * SPDX-FileCopyrightText: 2015 Jakub Steiner ++ * ++ * SPDX-License-Identifier: GPL-2.0-or-later ++ */ ++ ++@import url("stylesheet-dark.css"); ++ ++.workspace-indicator .workspace { ++ background-color: #ccc; ++} ++ ++.workspace-indicator .workspace.active { ++ border-color: #888; ++} ++ ++.workspace-indicator-window-preview { ++ background-color: #ededed; ++ border: 1px solid #ccc; ++} ++ ++.workspace-indicator-window-preview.active { ++ background-color: #f6f5f4; ++} +diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css +index c8c2370f..b0f7d171 100644 +--- a/extensions/workspace-indicator/stylesheet.css ++++ b/extensions/workspace-indicator/stylesheet.css +@@ -1,30 +1 @@ +-.workspace-indicator .status-label { +- padding: 0 8px; +-} +- +-.workspace-indicator .workspaces-box { +- padding: 5px; +- spacing: 3px; +-} +- +-.workspace-indicator .workspace { +- width: 52px; +- border: 2px solid transparent; +- border-radius: 4px; +- background-color: #3f3f3f; +-} +- +-.workspace-indicator .workspace.active { +- border-color: #9f9f9f; +-} +- +-.workspace-indicator-window-preview { +- background-color: #bebebe; +- border: 1px solid #828282; +- border-radius: 1px; +-} +- +-.workspace-indicator-window-preview { +- background-color: #353535; +- border: 2px solid #ccc; +-} ++@import url("stylesheet-dark.css"); +-- +2.50.0 + + +From 7bad2503adfbdcf5dfa67288fd7a5abcdd299fb0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 13:08:52 +0100 +Subject: [PATCH 21/43] window-list: Use actual copy of workspace-indicator + +We are now at a point where the code from the workspace-indicator +extension is usable from the window-list. + +However instead of updating the copy, go one step further and +remove it altogether, and copy the required files at build time. + +This ensures that future changes are picked up by both extensions +without duplicating any work. +--- + extensions/window-list/classic.css | 20 +- + extensions/window-list/meson.build | 29 +- + extensions/window-list/stylesheet.css | 2 + + extensions/window-list/workspaceIndicator.js | 451 ------------------- + 4 files changed, 31 insertions(+), 471 deletions(-) + delete mode 100644 extensions/window-list/workspaceIndicator.js + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index 02286041..d7ceb062 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -1,4 +1,5 @@ + @import url("stylesheet.css"); ++@import url("stylesheet-workspace-switcher-light.css"); + + #panel.bottom-panel { + border-top-width: 1px; +@@ -47,22 +48,3 @@ + color: #888; + box-shadow: none; + } +- +-/* workspace switcher */ +-.window-list-workspace-indicator .workspace { +- background-color: #ddd; +-} +- +-.window-list-workspace-indicator .workspace.active { +- background-color: #ccc; +-} +- +-.window-list-workspace-indicator-window-preview { +- background-color: #ededed; +- border: 1px solid #ccc; +-} +- +-.window-list-workspace-indicator-window-preview.active { +- background-color: #f6f5f4; +- border: 2px solid #888; +-} +diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build +index 599f45e1..12d2b174 100644 +--- a/extensions/window-list/meson.build ++++ b/extensions/window-list/meson.build +@@ -5,7 +5,34 @@ extension_data += configure_file( + ) + extension_data += files('stylesheet.css') + +-extension_sources += files('prefs.js', 'windowPicker.js', 'workspaceIndicator.js') ++transform_stylesheet = [ ++ 'sed', '-E', ++ '-e', 's:^\.(workspace-indicator):.window-list-\\1:', ++ '-e', '/^@import/d', ++ '@INPUT@', ++ ] ++ ++workspaceIndicatorSources = [ ++ configure_file( ++ input: '../workspace-indicator/workspaceIndicator.js', ++ output: '@PLAINNAME@', ++ copy: true, ++ ), ++ configure_file( ++ input: '../workspace-indicator/stylesheet-dark.css', ++ output: 'stylesheet-workspace-switcher-dark.css', ++ command: transform_stylesheet, ++ capture: true, ++ ), ++ configure_file( ++ input: '../workspace-indicator/stylesheet-light.css', ++ output: 'stylesheet-workspace-switcher-light.css', ++ command: transform_stylesheet, ++ capture: true, ++ ), ++] ++ ++extension_sources += files('prefs.js', 'windowPicker.js') + workspaceIndicatorSources + 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 4c9ca671..2aa35922 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -1,3 +1,5 @@ ++@import url("stylesheet-workspace-switcher-dark.css"); ++ + .bottom-panel { + /* .window-button-icon height + + .window-button vertical padding + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +deleted file mode 100644 +index 574ebdca..00000000 +--- a/extensions/window-list/workspaceIndicator.js ++++ /dev/null +@@ -1,451 +0,0 @@ +-/* 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; +-const Tweener = imports.ui.tweener; +- +-const Gettext = imports.gettext.domain('gnome-shell-extensions'); +-const _ = Gettext.gettext; +- +-const TOOLTIP_OFFSET = 6; +-const TOOLTIP_ANIMATION_TIME = 150; +- +-let baseStyleClassName = ''; +- +-const TypePrefix = window.TypePrefix || ''; +- +-let WindowPreview = GObject.registerClass({ +- GTypeName: `${TypePrefix}WorkspaceIndicatorWindowPreview` +-}, class WindowPreview extends St.Button { +- _init(window) { +- super._init({ +- style_class: `${baseStyleClassName}-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: `${TypePrefix}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._tooltip = new St.Label({ +- style_class: 'dash-label', +- visible: false, +- }); +- Main.uiGroup.add_child(this._tooltip); +- +- this.connect('destroy', this._onDestroy.bind(this)); +- this.connect('notify::hover', this._syncTooltip.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 +- on_clicked() { +- let ws = global.workspace_manager.get_workspace_by_index(this._index); +- if (ws) +- ws.activate(global.get_current_time()); +- } +- +- _syncTooltip() { +- if (this.hover) { +- this._tooltip.text = Meta.prefs_get_workspace_name(this._index); +- this._tooltip.opacity = 0; +- this._tooltip.show(); +- +- const [stageX, stageY] = this.get_transformed_position(); +- const thumbWidth = this.allocation.get_width(); +- const tipWidth = this._tooltip.width; +- const tipHeight = this._tooltip.height; +- const xOffset = Math.floor((thumbWidth - tipWidth) / 2); +- const monitor = Main.layoutManager.findMonitorForActor(this); +- const x = Math.min( +- Math.max(stageX + xOffset, monitor.x), +- monitor.x + monitor.width - tipWidth); +- const y = stageY - tipHeight - TOOLTIP_OFFSET; +- this._tooltip.set_position(x, y); +- } +- +- Tweener.addTween(this._tooltip, { +- opacity: this.hover ? 255 : 0, +- time: TOOLTIP_ANIMATION_TIME / 1000, +- transition: 'easeOutQuad', +- onComplete: () => (this._tooltip.visible = this.hover), +- }); +- } +- +- _onDestroy() { +- this._tooltip.destroy(); +- +- this._workspace.disconnect(this._windowAddedId); +- this._workspace.disconnect(this._windowRemovedId); +- global.display.disconnect(this._restackedId); +- } +-}); +- +-var WorkspaceIndicator = GObject.registerClass({ +- GTypeName: `${TypePrefix}WorkspaceIndicator` +-}, class WorkspaceIndicator extends PanelMenu.Button { +- _init(params = {}) { +- super._init(0.0, _('Workspace Indicator')); +- +- const { +- baseStyleClass = 'workspace-indicator', +- } = params; +- +- baseStyleClassName = baseStyleClass; +- this.add_style_class_name(baseStyleClassName); +- +- 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() }); +- +- this._statusBin = new St.Bin({ +- style_class: 'status-label-bin', +- x_expand: true, +- y_expand: true, +- child: this._statusLabel +- }); +- 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)), +- 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( +- 'changed::workspace-names', this._updateMenuLabels.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(); +- } +- +- _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() { +- for (let i = 0; i < this._workspacesItems.length; i++) { +- this._workspacesItems[i].setOrnament(i == this._currentWorkspace +- ? PopupMenu.Ornament.DOT +- : PopupMenu.Ornament.NONE); +- } +- } +- +- _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(); +- let total = workspaceManager.n_workspaces; +- +- 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; +- +- 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()); +- } +- +- _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; +- +- 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.50.0 + + +From 08c0fab926838cb4f3dcf5c3b1b9356c0f0e9f57 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 20 Feb 2024 17:27:57 +0100 +Subject: [PATCH 22/43] workspace-indicator: Split out WorkspacePreviews + +The previews will become a bit more complex soon, so spit them out +into a dedicated class. +--- + .../workspace-indicator/workspaceIndicator.js | 139 +++++++++++------- + 1 file changed, 88 insertions(+), 51 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 6783b315..0a5e7b26 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -255,6 +255,88 @@ let WorkspaceThumbnail = GObject.registerClass({ + } + }); + ++const WorkspacePreviews = GObject.registerClass({ ++ GTypeName: `${TypePrefix}WorkspaceIndicatorWorkspacePreviews` ++}, class WorkspacePreviews extends Clutter.Actor { ++ _init() { ++ super._init({ ++ layout_manager: new Clutter.BinLayout(), ++ reactive: true, ++ y_expand: true, ++ }); ++ ++ this.connect('scroll-event', ++ (a, event) => this.handleScrollEvent(event)); ++ this.connect('destroy', () => this._onDestroy()); ++ ++ const {workspaceManager} = global; ++ ++ this._workspaceManagerSignals = [ ++ workspaceManager.connect_after('notify::n-workspaces', ++ () => this._updateThumbnails()), ++ workspaceManager.connect_after('workspace-switched', ++ () => this._updateActiveThumbnail()), ++ ]; ++ ++ this._thumbnailsBox = new St.BoxLayout({ ++ style_class: 'workspaces-box', ++ y_expand: true, ++ }); ++ this.add_child(this._thumbnailsBox); ++ ++ this._updateThumbnails(); ++ } ++ ++ handleScrollEvent(event) { ++ const 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; ++ ++ const {workspaceManager} = global; ++ const currentIndex = workspaceManager.get_active_workspace_index(); ++ const newIndex = currentIndex + diff; ++ const workspace = workspaceManager.get_workspace_by_index(newIndex); ++ if (workspace) ++ workspace.activate(global.get_current_time()); ++ } ++ ++ _updateThumbnails() { ++ const {nWorkspaces} = global.workspace_manager; ++ ++ this._thumbnailsBox.destroy_all_children(); ++ ++ for (let i = 0; i < nWorkspaces; i++) { ++ const thumb = new WorkspaceThumbnail(i); ++ this._thumbnailsBox.add_child(thumb); ++ } ++ this._updateActiveThumbnail(); ++ } ++ ++ _updateActiveThumbnail() { ++ const {workspaceManager} = global; ++ const activeIndex = workspaceManager.get_active_workspace_index(); ++ let thumbs = this._thumbnailsBox.get_children(); ++ for (let i = 0; i < thumbs.length; i++) { ++ if (i == activeIndex) ++ thumbs[i].add_style_class_name('active'); ++ else ++ thumbs[i].remove_style_class_name('active'); ++ } ++ } ++ ++ _onDestroy() { ++ global.workspace_manager.disconnect(this._nWorkspacesChanged); ++ for (let i = 0; i < this._workspaceManagerSignals.length; i++) ++ global.workspace_manager.disconnect(this._workspaceManagerSignals[i]); ++ this._workspaceManagerSignals = []; ++ } ++}); ++ + let WorkspaceIndicator = GObject.registerClass({ + GTypeName: `${TypePrefix}WorkspaceIndicator` + }, class WorkspaceIndicator extends PanelMenu.Button { +@@ -286,13 +368,8 @@ let WorkspaceIndicator = GObject.registerClass({ + + container.add_actor(this._statusLabel); + +- this._thumbnailsBox = new St.BoxLayout({ +- style_class: 'workspaces-box', +- y_expand: true, +- reactive: true +- }); +- +- container.add_actor(this._thumbnailsBox); ++ this._thumbnails = new WorkspacePreviews(); ++ container.add_child(this._thumbnails); + + this._workspacesItems = []; + +@@ -305,8 +382,8 @@ let WorkspaceIndicator = GObject.registerClass({ + this._onWorkspaceOrientationChanged.bind(this)) + ]; + +- this.connect('scroll-event', this._onScrollEvent.bind(this)); +- this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); ++ this.connect('scroll-event', ++ (o, event) => this._thumbnails.handleScrollEvent(event)); + + this._inTopBar = false; + this.connect('notify::realized', () => { +@@ -318,7 +395,6 @@ let WorkspaceIndicator = GObject.registerClass({ + }); + + this._updateMenu(); +- this._updateThumbnails(); + this._onWorkspaceOrientationChanged(); + + this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); +@@ -348,7 +424,7 @@ let WorkspaceIndicator = GObject.registerClass({ + this.reactive = vertical; + + this._statusLabel.visible = vertical; +- this._thumbnailsBox.visible = !vertical; ++ this._thumbnails.visible = !vertical; + + this._updateTopBarRedirect(); + } +@@ -359,7 +435,7 @@ let WorkspaceIndicator = GObject.registerClass({ + + // Disable offscreen-redirect when showing the workspace switcher + // so that clip-to-allocation works +- Main.panel.set_offscreen_redirect(this._thumbnailsBox.visible ++ Main.panel.set_offscreen_redirect(this._thumbnails.visible + ? Clutter.OffscreenRedirect.ALWAYS + : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY); + } +@@ -368,14 +444,12 @@ let WorkspaceIndicator = GObject.registerClass({ + this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); + + this._updateMenuOrnament(); +- this._updateActiveThumbnail(); + + this._statusLabel.set_text(this._getStatusText()); + } + + _nWorkspacesChanged() { + this._updateMenu(); +- this._updateThumbnails(); + } + + _updateMenuOrnament() { +@@ -386,16 +460,6 @@ let 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() { + const current = this._currentWorkspace + 1; + return `${current}`; +@@ -433,18 +497,6 @@ let 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; + +@@ -453,19 +505,4 @@ let WorkspaceIndicator = GObject.registerClass({ + 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; +- } +- +- const newIndex = this._currentWorkspace + diff; +- this._activate(newIndex); +- } + }); +-- +2.50.0 + + +From 6e8c0b128350d4d8555e2f4750bd551654596917 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 19 Feb 2024 14:42:04 +0100 +Subject: [PATCH 23/43] workspace-indicator: Handle preview overflow + +We currently avoid previews from overflowing in most setups by +artificially limiting them to a maximum of six workspaces. + +Add some proper handling to also cover cases where space is more +limited, and to allow removing the restriction in the future. + +For that, wrap the previews in an auto-scrolling scroll view +and add overflow indicators on each side. +--- + .../workspace-indicator/stylesheet-dark.css | 4 ++ + .../workspace-indicator/workspaceIndicator.js | 68 ++++++++++++++++++- + 2 files changed, 71 insertions(+), 1 deletion(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index f74f7e88..61d1e982 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -2,6 +2,10 @@ + padding: 0 8px; + } + ++.workspace-indicator .workspaces-view.hfade { ++ -st-hfade-offset: 20px; ++} ++ + .workspace-indicator .workspaces-box { + padding: 5px; + spacing: 3px; +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 0a5e7b26..1e3db810 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -12,6 +12,8 @@ const _ = Gettext.gettext; + const TOOLTIP_OFFSET = 6; + const TOOLTIP_ANIMATION_TIME = 150; + ++const SCROLL_TIME = 100; ++ + let baseStyleClassName = ''; + + const TypePrefix = window.TypePrefix || ''; +@@ -276,13 +278,30 @@ const WorkspacePreviews = GObject.registerClass({ + () => this._updateThumbnails()), + workspaceManager.connect_after('workspace-switched', + () => this._updateActiveThumbnail()), ++ workspaceManager.connect('workspace-switched', ++ () => this._updateScrollPosition()), + ]; + ++ this.connect('notify::mapped', () => { ++ if (this.mapped) ++ this._updateScrollPosition(); ++ }); ++ + this._thumbnailsBox = new St.BoxLayout({ + style_class: 'workspaces-box', + y_expand: true, + }); +- this.add_child(this._thumbnailsBox); ++ ++ this._scrollView = new St.ScrollView({ ++ style_class: 'workspaces-view hfade', ++ enable_mouse_scrolling: false, ++ hscrollbar_policy: St.PolicyType.EXTERNAL, ++ vscrollbar_policy: St.PolicyType.NEVER, ++ y_expand: true, ++ }); ++ this._scrollView.add_actor(this._thumbnailsBox); ++ ++ this.add_child(this._scrollView); + + this._updateThumbnails(); + } +@@ -315,6 +334,9 @@ const WorkspacePreviews = GObject.registerClass({ + this._thumbnailsBox.add_child(thumb); + } + this._updateActiveThumbnail(); ++ ++ if (this.mapped) ++ this._updateScrollPosition(); + } + + _updateActiveThumbnail() { +@@ -329,6 +351,50 @@ const WorkspacePreviews = GObject.registerClass({ + } + } + ++ _updateScrollPosition() { ++ const adjustment = this._scrollView.hscroll.adjustment; ++ const {upper, pageSize} = adjustment; ++ let {value} = adjustment; ++ ++ const {workspaceManager} = global; ++ const activeIndex = workspaceManager.get_active_workspace_index(); ++ const activeWorkspace = ++ this._thumbnailsBox.get_child_at_index(activeIndex); ++ ++ if (!activeWorkspace) ++ return; ++ ++ let offset = 0; ++ const hfade = this._scrollView.get_effect('fade'); ++ if (hfade) ++ offset = this._scrollView.get_theme_node().get_length('-st-hfade-offset'); ++ ++ let {x1, x2} = activeWorkspace.get_allocation_box(); ++ let parent = activeWorkspace.get_parent(); ++ while (parent !== this._scrollView) { ++ if (!parent) ++ throw new Error('actor not in scroll view'); ++ ++ const box = parent.get_allocation_box(); ++ x1 += box.x1; ++ x2 += box.x1; ++ parent = parent.get_parent(); ++ } ++ ++ if (x1 < value + offset) ++ value = Math.max(0, x1 - offset); ++ else if (x2 > value + pageSize - offset) ++ value = Math.min(upper, x2 + offset - pageSize); ++ else ++ return; ++ ++ Tweener.addTween(adjustment, { ++ value, ++ time: SCROLL_TIME / 1000, ++ transition: 'easeOutQuad', ++ }); ++ } ++ + _onDestroy() { + global.workspace_manager.disconnect(this._nWorkspacesChanged); + for (let i = 0; i < this._workspaceManagerSignals.length; i++) +-- +2.50.0 + + +From 355a224ca85344c876784bcebc119f6dbf657693 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 20 Feb 2024 22:00:57 +0100 +Subject: [PATCH 24/43] workspace-indicator: Make previews configurable + +Now that previews scroll when there are too many workspaces, +there is no longer a reason for the 6-workspace limit. + +However some users do prefer the menu, so rather than drop it, +turn it into a proper preference. + +Closes +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/336 +--- + ....shell.extensions.classic.gschema.override | 3 ++ + extensions/window-list/extension.js | 1 + + ...e.shell.extensions.window-list.gschema.xml | 4 ++ + extensions/workspace-indicator/extension.js | 4 +- + extensions/workspace-indicator/meson.build | 1 + + extensions/workspace-indicator/prefs.js | 43 ++++++++++++++++++- + ...extensions.workspace-indicator.gschema.xml | 15 +++++++ + .../workspace-indicator/workspaceIndicator.js | 20 +++++---- + po/POTFILES.in | 1 + + 9 files changed, 82 insertions(+), 10 deletions(-) + create mode 100644 extensions/workspace-indicator/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml + +diff --git a/data/00_org.gnome.shell.extensions.classic.gschema.override b/data/00_org.gnome.shell.extensions.classic.gschema.override +index c6701074..04155bfe 100644 +--- a/data/00_org.gnome.shell.extensions.classic.gschema.override ++++ b/data/00_org.gnome.shell.extensions.classic.gschema.override +@@ -7,3 +7,6 @@ button-layout='appmenu:minimize,maximize,close' + [org.gnome.desktop.wm.keybindings:GNOME-Classic] + switch-applications=[] + switch-windows=['Tab','Tab'] ++ ++[org.gnome.shell.extensions.window-list:GNOME-Classic] ++embed-previews=true +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index aada9525..b2357939 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -818,6 +818,7 @@ class WindowList { + + this._workspaceIndicator = new BottomWorkspaceIndicator({ + baseStyleClass: 'window-list-workspace-indicator', ++ settings: ExtensionUtils.getSettings(), + }); + indicatorsBox.add(this._workspaceIndicator.container, { expand: false, y_fill: true }); + +diff --git a/extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml b/extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml +index b7459417..a9174494 100644 +--- a/extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml ++++ b/extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml +@@ -23,5 +23,9 @@ + only on the primary one. + + ++ ++ false ++ Show workspace previews in window list ++ + + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 6f7d07c5..ec991993 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -14,7 +14,9 @@ function init() { + let _indicator; + + function enable() { +- _indicator = new WorkspaceIndicator; ++ _indicator = new WorkspaceIndicator({ ++ settings: ExtensionUtils.getSettings(), ++ }); + Main.panel.addToStatusArea('workspace-indicator', _indicator); + } + +diff --git a/extensions/workspace-indicator/meson.build b/extensions/workspace-indicator/meson.build +index 0bf9f023..a88db78a 100644 +--- a/extensions/workspace-indicator/meson.build ++++ b/extensions/workspace-indicator/meson.build +@@ -8,5 +8,6 @@ extension_data += files( + 'stylesheet-dark.css', + 'stylesheet-light.css', + ) ++extension_schemas += files('schemas/' + metadata_conf.get('gschemaname') + '.gschema.xml') + + extension_sources += files('prefs.js', 'workspaceIndicator.js') +diff --git a/extensions/workspace-indicator/prefs.js b/extensions/workspace-indicator/prefs.js +index d8640559..d787f481 100644 +--- a/extensions/workspace-indicator/prefs.js ++++ b/extensions/workspace-indicator/prefs.js +@@ -12,6 +12,34 @@ const ExtensionUtils = imports.misc.extensionUtils; + const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; + const WORKSPACE_KEY = 'workspace-names'; + ++const GeneralGroup = GObject.registerClass( ++class GeneralGroup extends Gtk.Box { ++ _init() { ++ super._init({ ++ orientation: Gtk.Orientation.VERTICAL, ++ }); ++ ++ const row = new Gtk.Box(); ++ this.add(row); ++ ++ row.add(new Gtk.Label({ ++ label: _('Show Previews In Top Bar'), ++ })); ++ ++ const sw = new Gtk.Switch({ ++ hexpand: true, ++ halign: Gtk.Align.END, ++ }); ++ row.add(sw); ++ ++ const settings = ExtensionUtils.getSettings(); ++ ++ settings.bind('embed-previews', ++ sw, 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ } ++}); ++ + const WorkspaceNameModel = GObject.registerClass( + class WorkspaceNameModel extends Gtk.ListStore { + _init(params) { +@@ -127,7 +155,20 @@ class WorkspaceSettingsWidget extends Gtk.Grid { + this.margin = 12; + this.orientation = Gtk.Orientation.VERTICAL; + +- this.add(new Gtk.Label({ ++ const box = new Gtk.Box({ ++ orientation: Gtk.Orientation.VERTICAL, ++ halign: Gtk.Align.CENTER, ++ spacing: 12, ++ margin_top: 36, ++ margin_bottom: 36, ++ margin_start: 36, ++ margin_end: 36, ++ }); ++ this.add(box); ++ ++ box.add(new GeneralGroup()); ++ ++ box.add(new Gtk.Label({ + label: '%s'.format(_('Workspace Names')), + use_markup: true, + margin_bottom: 6, +diff --git a/extensions/workspace-indicator/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml b/extensions/workspace-indicator/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml +new file mode 100644 +index 00000000..e5059849 +--- /dev/null ++++ b/extensions/workspace-indicator/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml +@@ -0,0 +1,15 @@ ++ ++ ++ ++ ++ ++ false ++ Show workspace previews in top bar ++ ++ ++ +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 1e3db810..fd497dc9 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -411,8 +411,11 @@ let WorkspaceIndicator = GObject.registerClass({ + + const { + baseStyleClass = 'workspace-indicator', ++ settings, + } = params; + ++ this._settings = settings; ++ + baseStyleClassName = baseStyleClass; + this.add_style_class_name(baseStyleClassName); + +@@ -444,8 +447,6 @@ let WorkspaceIndicator = GObject.registerClass({ + this._nWorkspacesChanged.bind(this)), + workspaceManager.connect_after('workspace-switched', + this._onWorkspaceSwitched.bind(this)), +- workspaceManager.connect('notify::layout-rows', +- this._onWorkspaceOrientationChanged.bind(this)) + ]; + + this.connect('scroll-event', +@@ -461,12 +462,15 @@ let WorkspaceIndicator = GObject.registerClass({ + }); + + this._updateMenu(); +- this._onWorkspaceOrientationChanged(); + + this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); + this._settingsChangedId = this._settings.connect( + 'changed::workspace-names', + this._updateMenuLabels.bind(this)); ++ ++ this._settings.connect('changed::embed-previews', ++ () => this._updateThumbnailVisibility()); ++ this._updateThumbnailVisibility(); + } + + _onDestroy() { +@@ -485,12 +489,12 @@ let WorkspaceIndicator = GObject.registerClass({ + super._onDestroy(); + } + +- _onWorkspaceOrientationChanged() { +- let vertical = global.workspace_manager.layout_rows == -1; +- this.reactive = vertical; ++ _updateThumbnailVisibility() { ++ const useMenu = !this._settings.get_boolean('embed-previews'); ++ this.reactive = useMenu; + +- this._statusLabel.visible = vertical; +- this._thumbnails.visible = !vertical; ++ this._statusLabel.visible = useMenu; ++ this._thumbnails.visible = !useMenu; + + this._updateTopBarRedirect(); + } +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 5cdb710f..eeb36fab 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -19,4 +19,5 @@ extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml + extensions/window-list/prefs.js + extensions/windowsNavigator/extension.js + extensions/workspace-indicator/prefs.js ++extensions/workspace-indicator/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml + extensions/workspace-indicator/workspaceIndicator.js +-- +2.50.0 + + +From ecadaca214f2a7d2154d968bcdd7db927cfc8503 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 21 Mar 2024 17:27:09 +0100 +Subject: [PATCH 25/43] window-list: Expose workspace preview option + +Now that we have the option, the window-list should expose it +in its preference window like the workspace-indicator. +--- + extensions/window-list/prefs.js | 6 ++++++ + extensions/workspace-indicator/workspaceIndicator.js | 6 +++--- + 2 files changed, 9 insertions(+), 3 deletions(-) + +diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js +index 17e97990..2c4f9e49 100644 +--- a/extensions/window-list/prefs.js ++++ b/extensions/window-list/prefs.js +@@ -84,6 +84,12 @@ class WindowListPrefsWidget extends Gtk.Grid { + }); + this._settings.bind('show-on-all-monitors', check, 'active', Gio.SettingsBindFlags.DEFAULT); + this.add(check); ++ ++ check = new Gtk.CheckButton({ ++ label: _('Show workspace previews'), ++ }); ++ this._settings.bind('embed-previews', check, 'active', Gio.SettingsBindFlags.DEFAULT); ++ this.add(check); + } + }); + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index fd497dc9..3c1d5724 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -463,8 +463,8 @@ let WorkspaceIndicator = GObject.registerClass({ + + this._updateMenu(); + +- this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); +- this._settingsChangedId = this._settings.connect( ++ this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); ++ this._settingsChangedId = this._desktopSettings.connect( + 'changed::workspace-names', + this._updateMenuLabels.bind(this)); + +@@ -478,7 +478,7 @@ let WorkspaceIndicator = GObject.registerClass({ + global.workspace_manager.disconnect(this._workspaceManagerSignals[i]); + + if (this._settingsChangedId) { +- this._settings.disconnect(this._settingsChangedId); ++ this._desktopSettings.disconnect(this._settingsChangedId); + this._settingsChangedId = 0; + } + +-- +2.50.0 + + +From 1748cb00834f7167d9e642a93240ec5f3518a344 Mon Sep 17 00:00:00 2001 +From: Jakub Steiner +Date: Tue, 16 Jul 2024 09:40:53 +0200 +Subject: [PATCH 26/43] window-list: Update styling + +- Contemporary look. Fewer borders, thinner outlines for workspace indicators +- Lacks the designed unfocused window separators. +- Relies on https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/328 + +Fixes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/421 + +Part-of: +--- + extensions/window-list/classic.css | 68 +++++++----- + extensions/window-list/stylesheet.css | 104 ++++++------------ + .../workspace-indicator/stylesheet-dark.css | 4 +- + 3 files changed, 76 insertions(+), 100 deletions(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index d7ceb062..1d9b82f0 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -1,22 +1,24 @@ ++/* ++ * SPDX-FileCopyrightText: 2013 Florian Müllner ++ * SPDX-FileCopyrightText: 2015 Jakub Steiner ++ * ++ * SPDX-License-Identifier: GPL-2.0-or-later ++ */ ++ + @import url("stylesheet.css"); + @import url("stylesheet-workspace-switcher-light.css"); + + #panel.bottom-panel { + border-top-width: 1px; + border-bottom-width: 0px; +- height: 2.25em ; +- padding: 2px; ++ height: 2.5em; + } + +- .bottom-panel .window-button > StWidget, +- .bottom-panel .window-picker-toggle > StWidget { +- color: #2e3436; +- background-color: #eee; ++ .bottom-panel .window-button > StWidget { + border-radius: 3px; + padding: 3px 6px 1px; + box-shadow: none; + text-shadow: none; +- border: 1px solid rgba(0,0,0,0.2); + } + + .bottom-panel .window-button > StWidget { +@@ -24,27 +26,43 @@ + max-width: 18.75em; + } + +- .bottom-panel .window-button:hover > StWidget, +- .bottom-panel .window-picker-toggle:hover > StWidget { +- background-color: #f9f9f9; +- } ++ .window-button > StWidget { ++ color: #000; ++ background-color: transparent; ++} + +- .bottom-panel .window-button:active > StWidget, +- .bottom-panel .window-button:focus > StWidget { +- box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); +- } ++.window-button > StWidget { ++ -st-natural-width: 18.75em; ++ max-width: 18.75em; ++} + +- .bottom-panel .window-button.focused > StWidget, +- .bottom-panel .window-picker-toggle:checked > StWidget { +- background-color: #ccc; +- box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); +- } ++.window-button:hover > StWidget { ++ background-color: #e1e1e1; ++} + +- .bottom-panel .window-button.focused:hover > StWidget { +- background-color: #e9e9e9; ++.window-button:active > StWidget, ++.window-button:focus > StWidget { ++ background-color: #d4d4d4; ++} ++ ++.window-button.focused > StWidget { ++ background-color: #c7c7c7; ++} ++ ++ .window-button.focused:hover > StWidget { ++ background-color: #bbb; + } + +- .bottom-panel .window-button.minimized > StWidget { +- color: #888; +- box-shadow: none; ++ .window-button.focused:active > StWidget { ++ background-color: #aeaeae; + } ++ ++.window-button.minimized > StWidget { ++ color: #aaa; ++ background-color: #f9f9f9; ++} ++ ++.window-button.minimized:active > StWidget { ++ color: #aaa; ++ background-color: #f9f9f9; ++} +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index 2aa35922..349404c5 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -1,3 +1,9 @@ ++/* ++ * SPDX-FileCopyrightText: 2012 Florian Müllner ++ * SPDX-FileCopyrightText: 2013 Giovanni Campagna ++ * ++ * SPDX-License-Identifier: GPL-2.0-or-later ++ */ + @import url("stylesheet-workspace-switcher-dark.css"); + + .bottom-panel { +@@ -12,8 +18,14 @@ + font-size: 10pt; + } + ++.bottom-panel { ++ background-color: #000000; ++ border-top-width: 0px; ++ height: 2.45em; ++} ++ + .window-button { +- padding: 1px; ++ padding: 4px, 3px; + } + + .window-button:first-child:ltr { +@@ -30,20 +42,11 @@ + + .window-button > StWidget, + .window-picker-toggle > StWidget { +- color: #bbb; +- background-color: black; ++ color: #fff; ++ background-color: transparent; + border-radius: 4px; + padding: 3px 6px 1px; +- box-shadow: inset 1px 1px 4px rgba(255,255,255,0.5); +- text-shadow: 1px 1px 4px rgba(0,0,0,0.8); +-} +- +-.window-picker-toggle { +- padding: 3px; +-} +- +-.window-picker-toggle > StWidet { +- border: 1px solid rgba(255,255,255,0.3); ++ transition: 100ms ease; + } + + .window-button > StWidget { +@@ -51,35 +54,35 @@ + max-width: 18.75em; + } + +-.window-button:hover > StWidget, +-.window-picker-toggle:hover > StWidget { +- color: white; +- background-color: #1f1f1f; ++.window-button:hover > StWidget { ++ background-color: #303030; + } + + .window-button:active > StWidget, + .window-button:focus > StWidget { +- box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5); ++ background-color: #3c3c3c; + } + +-.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 > StWidget { ++ background-color: #5b5b5b; + } + +-.window-button.focused:active > StWidget, +-.window-picker-toggle:checked:active > StWidget { +- box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7); +-} ++ .window-button.focused:hover > StWidget { ++ background-color: #676767; ++ } ++ ++ .window-button.focused:active > StWidget { ++ background-color: #747474; ++ } + + .window-button.minimized > StWidget { + color: #666; +- box-shadow: inset -1px -1px 4px rgba(255,255,255,0.5); ++ background-color: #161616; + } + + .window-button.minimized:active > StWidget { +- box-shadow: inset -2px -2px 4px rgba(255,255,255,0.5); ++ color: #666; ++ background-color: #161616; + } + + .window-button-icon { +@@ -87,51 +90,6 @@ + height: 24px; + } + +-.window-list-workspace-indicator .status-label-bin { +- background-color: rgba(200, 200, 200, .3); +- border: 1px solid #cccccc; +- padding: 0 3px; +- 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); +-} +- +-.window-list-workspace-indicator-window-preview { +- background-color: #bebebe; +- border: 1px solid #828282; +-} +- +-.window-list-workspace-indicator-window-preview.active { +- background-color: #d4d4d4; +-} +- + .notification { + font-weight: normal; + } +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index 61d1e982..5663b422 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -13,13 +13,13 @@ + + .workspace-indicator .workspace { + width: 52px; +- border: 2px solid transparent; ++ border: 1px solid transparent; + border-radius: 4px; + background-color: #3f3f3f; + } + + .workspace-indicator .workspace.active { +- border-color: #9f9f9f; ++ border-color: #fff; + } + + .workspace-indicator-window-preview { +-- +2.50.0 + + +From c98e22e9b33a1a584eef04c25500e65d7423e676 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 03:36:08 +0200 +Subject: [PATCH 27/43] window-list: Small stylesheet cleanup + +The light stylesheet duplicates some declarations, and the +last occurrence matches what we already inherit from the +dark stylesheet. + +Part-of: +--- + extensions/window-list/classic.css | 10 ---------- + 1 file changed, 10 deletions(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index 1d9b82f0..63e5e48c 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -21,21 +21,11 @@ + text-shadow: none; + } + +- .bottom-panel .window-button > StWidget { +- -st-natural-width: 18.7em; +- max-width: 18.75em; +- } +- + .window-button > StWidget { + color: #000; + background-color: transparent; + } + +-.window-button > StWidget { +- -st-natural-width: 18.75em; +- max-width: 18.75em; +-} +- + .window-button:hover > StWidget { + background-color: #e1e1e1; + } +-- +2.50.0 + + +From f2e31221bbd114bedc1d95d3b49afb1af14d5735 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 11 Oct 2024 12:10:36 +0200 +Subject: [PATCH 28/43] workspace-indicator: Split out workspaces prefs page + +The window-list extension already uses the extension code for +its embedded workspace indicator, this will allow it to do the +same for the preference page. + +Part-of: +--- + extensions/workspace-indicator/meson.build | 2 +- + extensions/workspace-indicator/prefs.js | 244 +---------------- + .../workspace-indicator/workspacePrefs.js | 245 ++++++++++++++++++ + po/POTFILES.in | 2 +- + 4 files changed, 250 insertions(+), 243 deletions(-) + create mode 100644 extensions/workspace-indicator/workspacePrefs.js + +diff --git a/extensions/workspace-indicator/meson.build b/extensions/workspace-indicator/meson.build +index a88db78a..52ab5334 100644 +--- a/extensions/workspace-indicator/meson.build ++++ b/extensions/workspace-indicator/meson.build +@@ -10,4 +10,4 @@ extension_data += files( + ) + extension_schemas += files('schemas/' + metadata_conf.get('gschemaname') + '.gschema.xml') + +-extension_sources += files('prefs.js', 'workspaceIndicator.js') ++extension_sources += files('prefs.js', 'workspaceIndicator.js', 'workspacePrefs.js') +diff --git a/extensions/workspace-indicator/prefs.js b/extensions/workspace-indicator/prefs.js +index d787f481..2d90e198 100644 +--- a/extensions/workspace-indicator/prefs.js ++++ b/extensions/workspace-indicator/prefs.js +@@ -1,253 +1,15 @@ + // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- + /* exported init buildPrefsWidget */ + +-const { Gio, GObject, Gtk } = imports.gi; +- +-const Gettext = imports.gettext.domain('gnome-shell-extensions'); +-const _ = Gettext.gettext; +-const N_ = e => e; +- + const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); + +-const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; +-const WORKSPACE_KEY = 'workspace-names'; +- +-const GeneralGroup = GObject.registerClass( +-class GeneralGroup extends Gtk.Box { +- _init() { +- super._init({ +- orientation: Gtk.Orientation.VERTICAL, +- }); +- +- const row = new Gtk.Box(); +- this.add(row); +- +- row.add(new Gtk.Label({ +- label: _('Show Previews In Top Bar'), +- })); +- +- const sw = new Gtk.Switch({ +- hexpand: true, +- halign: Gtk.Align.END, +- }); +- row.add(sw); +- +- const settings = ExtensionUtils.getSettings(); +- +- settings.bind('embed-previews', +- sw, 'active', +- Gio.SettingsBindFlags.DEFAULT); +- } +-}); +- +-const WorkspaceNameModel = GObject.registerClass( +-class WorkspaceNameModel extends Gtk.ListStore { +- _init(params) { +- super._init(params); +- this.set_column_types([GObject.TYPE_STRING]); +- +- this.Columns = { +- LABEL: 0, +- }; +- +- this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); +- //this._settings.connect('changed::workspace-names', this._reloadFromSettings.bind(this)); +- +- this._reloadFromSettings(); +- +- // overriding class closure doesn't work, because GtkTreeModel +- // plays tricks with marshallers and class closures +- this.connect('row-changed', this._onRowChanged.bind(this)); +- this.connect('row-inserted', this._onRowInserted.bind(this)); +- this.connect('row-deleted', this._onRowDeleted.bind(this)); +- } +- +- _reloadFromSettings() { +- if (this._preventChanges) +- return; +- this._preventChanges = true; +- +- let newNames = this._settings.get_strv(WORKSPACE_KEY); +- +- let i = 0; +- let [ok, iter] = this.get_iter_first(); +- while (ok && i < newNames.length) { +- this.set(iter, [this.Columns.LABEL], [newNames[i]]); +- +- ok = this.iter_next(iter); +- i++; +- } +- +- while (ok) +- ok = this.remove(iter); +- +- for ( ; i < newNames.length; i++) { +- iter = this.append(); +- this.set(iter, [this.Columns.LABEL], [newNames[i]]); +- } +- +- this._preventChanges = false; +- } +- +- _onRowChanged(self, path, iter) { +- if (this._preventChanges) +- return; +- this._preventChanges = true; +- +- let index = path.get_indices()[0]; +- let names = this._settings.get_strv(WORKSPACE_KEY); +- +- if (index >= names.length) { +- // fill with blanks +- for (let i = names.length; i <= index; i++) +- names[i] = ''; +- } +- +- names[index] = this.get_value(iter, this.Columns.LABEL); +- +- this._settings.set_strv(WORKSPACE_KEY, names); +- +- this._preventChanges = false; +- } +- +- _onRowInserted(self, path, iter) { +- if (this._preventChanges) +- return; +- this._preventChanges = true; +- +- let index = path.get_indices()[0]; +- let names = this._settings.get_strv(WORKSPACE_KEY); +- let label = this.get_value(iter, this.Columns.LABEL) || ''; +- names.splice(index, 0, label); +- +- this._settings.set_strv(WORKSPACE_KEY, names); +- +- this._preventChanges = false; +- } +- +- _onRowDeleted(self, path) { +- if (this._preventChanges) +- return; +- this._preventChanges = true; +- +- let index = path.get_indices()[0]; +- let names = this._settings.get_strv(WORKSPACE_KEY); +- +- if (index >= names.length) +- return; +- +- names.splice(index, 1); +- +- // compact the array +- for (let i = names.length - 1; i >= 0 && !names[i]; i++) +- names.pop(); +- +- this._settings.set_strv(WORKSPACE_KEY, names); +- +- this._preventChanges = false; +- } +-}); +- +-const WorkspaceSettingsWidget = GObject.registerClass( +-class WorkspaceSettingsWidget extends Gtk.Grid { +- _init(params) { +- super._init(params); +- this.margin = 12; +- this.orientation = Gtk.Orientation.VERTICAL; +- +- const box = new Gtk.Box({ +- orientation: Gtk.Orientation.VERTICAL, +- halign: Gtk.Align.CENTER, +- spacing: 12, +- margin_top: 36, +- margin_bottom: 36, +- margin_start: 36, +- margin_end: 36, +- }); +- this.add(box); +- +- box.add(new GeneralGroup()); +- +- box.add(new Gtk.Label({ +- label: '%s'.format(_('Workspace Names')), +- use_markup: true, +- margin_bottom: 6, +- hexpand: true, +- halign: Gtk.Align.START +- })); +- +- let scrolled = new Gtk.ScrolledWindow({ shadow_type: Gtk.ShadowType.IN }); +- scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); +- this.add(scrolled); +- +- this._store = new WorkspaceNameModel(); +- this._treeView = new Gtk.TreeView({ +- model: this._store, +- headers_visible: false, +- reorderable: true, +- hexpand: true, +- vexpand: true +- }); +- +- let column = new Gtk.TreeViewColumn({ title: _('Name') }); +- let renderer = new Gtk.CellRendererText({ editable: true }); +- renderer.connect('edited', this._cellEdited.bind(this)); +- column.pack_start(renderer, true); +- column.add_attribute(renderer, 'text', this._store.Columns.LABEL); +- this._treeView.append_column(column); +- +- scrolled.add(this._treeView); +- +- let toolbar = new Gtk.Toolbar({ icon_size: Gtk.IconSize.SMALL_TOOLBAR }); +- toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR); +- +- let newButton = new Gtk.ToolButton({ icon_name: 'list-add-symbolic' }); +- newButton.connect('clicked', this._newClicked.bind(this)); +- toolbar.add(newButton); +- +- let delButton = new Gtk.ToolButton({ icon_name: 'list-remove-symbolic' }); +- delButton.connect('clicked', this._delClicked.bind(this)); +- toolbar.add(delButton); +- +- let selection = this._treeView.get_selection(); +- selection.connect('changed', () => { +- delButton.sensitive = selection.count_selected_rows() > 0; +- }); +- delButton.sensitive = selection.count_selected_rows() > 0; +- +- this.add(toolbar); +- } +- +- _cellEdited(renderer, path, newText) { +- let [ok, iter] = this._store.get_iter_from_string(path); +- +- if (ok) +- this._store.set(iter, [this._store.Columns.LABEL], [newText]); +- } +- +- _newClicked() { +- let iter = this._store.append(); +- let index = this._store.get_path(iter).get_indices()[0]; +- +- let label = _('Workspace %d').format(index + 1); +- this._store.set(iter, [this._store.Columns.LABEL], [label]); +- } +- +- _delClicked() { +- let [any, model_, iter] = this._treeView.get_selection().get_selected(); +- +- if (any) +- this._store.remove(iter); +- } +-}); ++const { WorkspacesPage } = Me.imports.workspacePrefs; + + function init() { + ExtensionUtils.initTranslations(); + } + + function buildPrefsWidget() { +- let widget = new WorkspaceSettingsWidget(); +- widget.show_all(); +- +- return widget; ++ return new WorkspacesPage(); + } +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +new file mode 100644 +index 00000000..1c65ff6b +--- /dev/null ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -0,0 +1,245 @@ ++// SPDX-FileCopyrightText: 2012 Giovanni Campagna ++// SPDX-FileCopyrightText: 2014 Florian Müllner ++// ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/* exported WorkspacesPage */ ++const { Gio, GLib, GObject, Gtk, Pango } = imports.gi; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++ ++const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); ++const _ = Gettext.gettext; ++const N_ = e => e; ++ ++const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; ++const WORKSPACE_KEY = 'workspace-names'; ++ ++const GeneralGroup = GObject.registerClass( ++class GeneralGroup extends Gtk.Box { ++ _init() { ++ super._init({ ++ orientation: Gtk.Orientation.VERTICAL, ++ }); ++ ++ const row = new Gtk.Box(); ++ this.add(row); ++ ++ row.add(new Gtk.Label({ ++ label: _('Show Previews In Top Bar'), ++ })); ++ ++ const sw = new Gtk.Switch({ ++ hexpand: true, ++ halign: Gtk.Align.END, ++ }); ++ row.add(sw); ++ ++ const settings = ExtensionUtils.getSettings(); ++ ++ settings.bind('embed-previews', ++ sw, 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ } ++}); ++ ++const WorkspaceNameModel = GObject.registerClass( ++class WorkspaceNameModel extends Gtk.ListStore { ++ _init(params) { ++ super._init(params); ++ this.set_column_types([GObject.TYPE_STRING]); ++ ++ this.Columns = { ++ LABEL: 0, ++ }; ++ ++ this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); ++ //this._settings.connect('changed::workspace-names', this._reloadFromSettings.bind(this)); ++ ++ this._reloadFromSettings(); ++ ++ // overriding class closure doesn't work, because GtkTreeModel ++ // plays tricks with marshallers and class closures ++ this.connect('row-changed', this._onRowChanged.bind(this)); ++ this.connect('row-inserted', this._onRowInserted.bind(this)); ++ this.connect('row-deleted', this._onRowDeleted.bind(this)); ++ } ++ ++ _reloadFromSettings() { ++ if (this._preventChanges) ++ return; ++ this._preventChanges = true; ++ ++ let newNames = this._settings.get_strv(WORKSPACE_KEY); ++ ++ let i = 0; ++ let [ok, iter] = this.get_iter_first(); ++ while (ok && i < newNames.length) { ++ this.set(iter, [this.Columns.LABEL], [newNames[i]]); ++ ++ ok = this.iter_next(iter); ++ i++; ++ } ++ ++ while (ok) ++ ok = this.remove(iter); ++ ++ for ( ; i < newNames.length; i++) { ++ iter = this.append(); ++ this.set(iter, [this.Columns.LABEL], [newNames[i]]); ++ } ++ ++ this._preventChanges = false; ++ } ++ ++ _onRowChanged(self, path, iter) { ++ if (this._preventChanges) ++ return; ++ this._preventChanges = true; ++ ++ let index = path.get_indices()[0]; ++ let names = this._settings.get_strv(WORKSPACE_KEY); ++ ++ if (index >= names.length) { ++ // fill with blanks ++ for (let i = names.length; i <= index; i++) ++ names[i] = ''; ++ } ++ ++ names[index] = this.get_value(iter, this.Columns.LABEL); ++ ++ this._settings.set_strv(WORKSPACE_KEY, names); ++ ++ this._preventChanges = false; ++ } ++ ++ _onRowInserted(self, path, iter) { ++ if (this._preventChanges) ++ return; ++ this._preventChanges = true; ++ ++ let index = path.get_indices()[0]; ++ let names = this._settings.get_strv(WORKSPACE_KEY); ++ let label = this.get_value(iter, this.Columns.LABEL) || ''; ++ names.splice(index, 0, label); ++ ++ this._settings.set_strv(WORKSPACE_KEY, names); ++ ++ this._preventChanges = false; ++ } ++ ++ _onRowDeleted(self, path) { ++ if (this._preventChanges) ++ return; ++ this._preventChanges = true; ++ ++ let index = path.get_indices()[0]; ++ let names = this._settings.get_strv(WORKSPACE_KEY); ++ ++ if (index >= names.length) ++ return; ++ ++ names.splice(index, 1); ++ ++ // compact the array ++ for (let i = names.length - 1; i >= 0 && !names[i]; i++) ++ names.pop(); ++ ++ this._settings.set_strv(WORKSPACE_KEY, names); ++ ++ this._preventChanges = false; ++ } ++}); ++ ++var WorkspacesPage = GObject.registerClass( ++class WorkspacesPage extends Gtk.ScrolledWindow { ++ _init() { ++ super._init({ ++ hscrollbar_policy: Gtk.PolicyType.NEVER, ++ vexpand:true, ++ }); ++ ++ const box = new Gtk.Box({ ++ orientation: Gtk.Orientation.VERTICAL, ++ halign: Gtk.Align.CENTER, ++ spacing: 12, ++ margin_top: 36, ++ margin_bottom: 36, ++ margin_start: 36, ++ margin_end: 36, ++ }); ++ this.add(box); ++ ++ box.add(new GeneralGroup()); ++ ++ box.add(new Gtk.Label({ ++ label: '%s'.format(_('Workspace Names')), ++ use_markup: true, ++ margin_bottom: 6, ++ hexpand: true, ++ halign: Gtk.Align.START ++ })); ++ ++ this._store = new WorkspaceNameModel(); ++ this._treeView = new Gtk.TreeView({ ++ model: this._store, ++ headers_visible: false, ++ reorderable: true, ++ hexpand: true, ++ vexpand: true ++ }); ++ ++ let column = new Gtk.TreeViewColumn({ title: _('Name') }); ++ let renderer = new Gtk.CellRendererText({ editable: true }); ++ renderer.connect('edited', this._cellEdited.bind(this)); ++ column.pack_start(renderer, true); ++ column.add_attribute(renderer, 'text', this._store.Columns.LABEL); ++ this._treeView.append_column(column); ++ ++ box.add(this._treeView); ++ ++ let toolbar = new Gtk.Toolbar({ icon_size: Gtk.IconSize.SMALL_TOOLBAR }); ++ toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR); ++ ++ let newButton = new Gtk.ToolButton({ icon_name: 'list-add-symbolic' }); ++ newButton.connect('clicked', this._newClicked.bind(this)); ++ toolbar.add(newButton); ++ ++ let delButton = new Gtk.ToolButton({ icon_name: 'list-remove-symbolic' }); ++ delButton.connect('clicked', this._delClicked.bind(this)); ++ toolbar.add(delButton); ++ ++ let selection = this._treeView.get_selection(); ++ selection.connect('changed', () => { ++ delButton.sensitive = selection.count_selected_rows() > 0; ++ }); ++ delButton.sensitive = selection.count_selected_rows() > 0; ++ ++ box.add(toolbar); ++ ++ this.show_all(); ++ } ++ ++ _cellEdited(renderer, path, newText) { ++ let [ok, iter] = this._store.get_iter_from_string(path); ++ ++ if (ok) ++ this._store.set(iter, [this._store.Columns.LABEL], [newText]); ++ } ++ ++ _newClicked() { ++ let iter = this._store.append(); ++ let index = this._store.get_path(iter).get_indices()[0]; ++ ++ let label = _('Workspace %d').format(index + 1); ++ this._store.set(iter, [this._store.Columns.LABEL], [label]); ++ } ++ ++ _delClicked() { ++ let [any, model_, iter] = this._treeView.get_selection().get_selected(); ++ ++ if (any) ++ this._store.remove(iter); ++ } ++}); +diff --git a/po/POTFILES.in b/po/POTFILES.in +index eeb36fab..ba4c5a46 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -18,6 +18,6 @@ extensions/window-list/extension.js + extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml + extensions/window-list/prefs.js + extensions/windowsNavigator/extension.js +-extensions/workspace-indicator/prefs.js + extensions/workspace-indicator/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml + extensions/workspace-indicator/workspaceIndicator.js ++extensions/workspace-indicator/workspacePrefs.js +-- +2.50.0 + + +From 24d307c6cfa14b766f44e8bcba09b3b1dc741056 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 11 Oct 2024 12:13:05 +0200 +Subject: [PATCH 29/43] workspace-indicator: Don't mention "top bar" in prefs + +The preferences will be shared with the window-list extension, +so avoid mentioning a specific placement. + +Part-of: +--- + extensions/workspace-indicator/workspacePrefs.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +index 1c65ff6b..0bd4a58b 100644 +--- a/extensions/workspace-indicator/workspacePrefs.js ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -27,7 +27,7 @@ class GeneralGroup extends Gtk.Box { + this.add(row); + + row.add(new Gtk.Label({ +- label: _('Show Previews In Top Bar'), ++ label: _('Show Previews'), + })); + + const sw = new Gtk.Switch({ +-- +2.50.0 + + +From 025580ecd9a522c3d68debbd9e7a9d0464db268e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 11 Oct 2024 12:45:54 +0200 +Subject: [PATCH 30/43] window-list: Remove workspace-previews setting from + prefs + +We are about to include the workspace prefs page from the +workspace-indicator extension, which already includes +the setting. + +Part-of: +--- + extensions/window-list/prefs.js | 6 ------ + 1 file changed, 6 deletions(-) + +diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js +index 2c4f9e49..17e97990 100644 +--- a/extensions/window-list/prefs.js ++++ b/extensions/window-list/prefs.js +@@ -84,12 +84,6 @@ class WindowListPrefsWidget extends Gtk.Grid { + }); + this._settings.bind('show-on-all-monitors', check, 'active', Gio.SettingsBindFlags.DEFAULT); + this.add(check); +- +- check = new Gtk.CheckButton({ +- label: _('Show workspace previews'), +- }); +- this._settings.bind('embed-previews', check, 'active', Gio.SettingsBindFlags.DEFAULT); +- this.add(check); + } + }); + +-- +2.50.0 + + +From 232fa86f0ef5e833d1f7fb83812381afdaf3e1d2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 29 Jun 2025 23:49:15 +0200 +Subject: [PATCH 31/43] window-list: Add workspaces page to prefs + +This brings back the workspace-previews setting, and adds the +ability to change the workspace names. + +Given that those names are used as tooltips or preview titles, +it makes sense to allow editing them from the extension prefs +rather than relying on external tools (like dconf-editor). + +Part-of: +--- + extensions/window-list/meson.build | 1 + + extensions/window-list/prefs.js | 49 +++++++++++++++++++++++++----- + 2 files changed, 42 insertions(+), 8 deletions(-) + +diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build +index 12d2b174..c4485965 100644 +--- a/extensions/window-list/meson.build ++++ b/extensions/window-list/meson.build +@@ -30,6 +30,7 @@ workspaceIndicatorSources = [ + command: transform_stylesheet, + capture: true, + ), ++ files('../workspace-indicator/workspacePrefs.js'), + ] + + extension_sources += files('prefs.js', 'windowPicker.js') + workspaceIndicatorSources +diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js +index 17e97990..936767c8 100644 +--- a/extensions/window-list/prefs.js ++++ b/extensions/window-list/prefs.js +@@ -7,20 +7,26 @@ const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; + + const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); + ++const { WorkspacesPage } = Me.imports.workspacePrefs; + + function init() { + ExtensionUtils.initTranslations(); + } + +-const WindowListPrefsWidget = GObject.registerClass( +-class WindowListPrefsWidget extends Gtk.Grid { +- _init(params) { +- super._init(params); +- +- this.margin = 24; +- this.row_spacing = 6; +- this.orientation = Gtk.Orientation.VERTICAL; ++const WindowListPage = GObject.registerClass( ++class WindowListPage extends Gtk.Box { ++ _init() { ++ super._init({ ++ orientation: Gtk.Orientation.VERTICAL, ++ spacing: 6, ++ margin_top: 36, ++ margin_bottom: 36, ++ margin_start: 36, ++ margin_end: 36, ++ halign: Gtk.Align.CENTER, ++ }); + + let groupingLabel = '%s'.format(_('Window Grouping')); + this.add(new Gtk.Label({ +@@ -84,6 +90,33 @@ class WindowListPrefsWidget extends Gtk.Grid { + }); + this._settings.bind('show-on-all-monitors', check, 'active', Gio.SettingsBindFlags.DEFAULT); + this.add(check); ++ ++ this.show_all(); ++ } ++}); ++ ++const WindowListPrefsWidget = GObject.registerClass( ++class WindowListPrefsWidget extends Gtk.Box { ++ _init() { ++ super._init({ ++ orientation: Gtk.Orientation.VERTICAL, ++ spacing: 6, ++ }); ++ ++ const stack = new Gtk.Stack(); ++ const stackSwitcher = new Gtk.StackSwitcher({ ++ stack, ++ margin_top: 12, ++ halign: Gtk.Align.CENTER, ++ }); ++ ++ this.add(stackSwitcher); ++ this.add(stack); ++ ++ this.show_all(); ++ ++ stack.add_titled(new WindowListPage(), 'window-list', _('Window List')); ++ stack.add_titled(new WorkspacesPage(), 'workspaces', _('Workspaces')); + } + }); + +-- +2.50.0 + + +From 7722bfec6e5f388c2bf0330b603a9728643c0248 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 16:31:57 +0200 +Subject: [PATCH 32/43] workspace-indicator: Remove left-over variable + +Part-of: +--- + extensions/workspace-indicator/workspaceIndicator.js | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 3c1d5724..3207e87c 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -440,8 +440,6 @@ let WorkspaceIndicator = GObject.registerClass({ + this._thumbnails = new WorkspacePreviews(); + container.add_child(this._thumbnails); + +- this._workspacesItems = []; +- + this._workspaceManagerSignals = [ + workspaceManager.connect_after('notify::n-workspaces', + this._nWorkspacesChanged.bind(this)), +-- +2.50.0 + + +From 3d5f527d2215f49670df7d078910a3144da0f95e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 16:27:57 +0200 +Subject: [PATCH 33/43] workspace-indicator: Split out WorkspacesMenu + +The menu currently only contains the previews without any logic +on its own. This is about to change, so split the menu into a +separate class. + +Part-of: +--- + .../workspace-indicator/workspaceIndicator.js | 181 +++++++++++------- + 1 file changed, 110 insertions(+), 71 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 3207e87c..9bb7cb60 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -1,6 +1,7 @@ +-const { Clutter, Gio, GObject, Meta, St } = imports.gi; ++const { Clutter, Gio, GLib, 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; +@@ -403,6 +404,110 @@ const WorkspacePreviews = GObject.registerClass({ + } + }); + ++class WorkspacesMenu extends PopupMenu.PopupMenu { ++ constructor(sourceActor) { ++ super(sourceActor, 0.5, St.Side.TOP); ++ ++ this.actor.add_style_class_name(`${baseStyleClassName}-menu`); ++ this.actor.connect('destroy', () => this._onDestroy()); ++ ++ this._workspacesSection = new PopupMenu.PopupMenuSection(); ++ this.addMenuItem(this._workspacesSection); ++ ++ this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ ++ this.addAction(_('Settings'), () => { ++ const extension = ExtensionUtils.getCurrentExtension(); ++ ++ Gio.DBus.session.call( ++ 'org.gnome.Shell', ++ '/org/gnome/Shell', ++ 'org.gnome.Shell.Extensions', ++ 'LaunchExtensionPrefs', ++ new GLib.Variant('(s)', [extension.uuid]), ++ null, ++ Gio.DBusCallFlags.NONE, ++ -1, ++ null); ++ }); ++ ++ this._desktopSettings = ++ new Gio.Settings({schema_id: 'org.gnome.desktop.wm.preferences'}); ++ this._workspaceNamesChangedId = ++ this._desktopSettings.connect('changed::workspace-names', () => { ++ this._updateWorkspaceLabels(); ++ this.emit('active-name-changed'); ++ }); ++ ++ const {workspaceManager} = global; ++ this._workspaceManagerSignals = [ ++ workspaceManager.connect('notify::n-workspaces', ++ () => this._updateWorkspaceItems()), ++ workspaceManager.connect('workspace-switched', ++ () => this._updateActiveIndicator()), ++ ]; ++ this._updateWorkspaceItems(); ++ } ++ ++ get activeName() { ++ const {workspaceManager} = global; ++ const active = workspaceManager.get_active_workspace_index(); ++ return Meta.prefs_get_workspace_name(active); ++ } ++ ++ _updateWorkspaceItems() { ++ const {workspaceManager} = global; ++ const {nWorkspaces} = workspaceManager; ++ ++ const section = this._workspacesSection.actor; ++ while (section.get_n_children() < nWorkspaces) { ++ const item = new PopupMenu.PopupMenuItem(''); ++ item.connect('activate', (o, event) => { ++ const index = section.get_children().indexOf(item.actor); ++ const workspace = workspaceManager.get_workspace_by_index(index); ++ if (workspace) ++ workspace.activate(event.get_time()); ++ }); ++ this._workspacesSection.addMenuItem(item); ++ } ++ ++ const items = section.get_children(); ++ items.splice(nWorkspaces).forEach(item => item.destroy()); ++ ++ this._updateWorkspaceLabels(); ++ this._updateActiveIndicator(); ++ } ++ ++ _updateWorkspaceLabels() { ++ const items = ++ this._workspacesSection.actor.get_children().map(c => c._delegate); ++ items.forEach( ++ (item, i) => (item.label.text = Meta.prefs_get_workspace_name(i))); ++ } ++ ++ _updateActiveIndicator() { ++ const {workspaceManager} = global; ++ const active = workspaceManager.get_active_workspace_index(); ++ ++ const items = ++ this._workspacesSection.actor.get_children().map(c => c._delegate); ++ items.forEach((item, i) => { ++ item.setOrnament(i === active ++ ? PopupMenu.Ornament.CHECK ++ : PopupMenu.Ornament.NONE); ++ }); ++ this.emit('active-name-changed'); ++ } ++ ++ _onDestroy() { ++ for (const id of this._workspaceManagerSignals) ++ global.workspace_manager.disconnect(id); ++ ++ this._desktopSettings.disconnect(this._workspaceNamesChangedId); ++ this._desktopSettings = null; ++ } ++} ++ + let WorkspaceIndicator = GObject.registerClass({ + GTypeName: `${TypePrefix}WorkspaceIndicator` + }, class WorkspaceIndicator extends PanelMenu.Button { +@@ -441,8 +546,6 @@ let WorkspaceIndicator = GObject.registerClass({ + container.add_child(this._thumbnails); + + this._workspaceManagerSignals = [ +- workspaceManager.connect_after('notify::n-workspaces', +- this._nWorkspacesChanged.bind(this)), + workspaceManager.connect_after('workspace-switched', + this._onWorkspaceSwitched.bind(this)), + ]; +@@ -459,13 +562,6 @@ let WorkspaceIndicator = GObject.registerClass({ + this._updateTopBarRedirect(); + }); + +- this._updateMenu(); +- +- this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); +- this._settingsChangedId = this._desktopSettings.connect( +- 'changed::workspace-names', +- this._updateMenuLabels.bind(this)); +- + this._settings.connect('changed::embed-previews', + () => this._updateThumbnailVisibility()); + this._updateThumbnailVisibility(); +@@ -475,11 +571,6 @@ let WorkspaceIndicator = GObject.registerClass({ + for (let i = 0; i < this._workspaceManagerSignals.length; i++) + global.workspace_manager.disconnect(this._workspaceManagerSignals[i]); + +- if (this._settingsChangedId) { +- this._desktopSettings.disconnect(this._settingsChangedId); +- this._settingsChangedId = 0; +- } +- + if (this._inTopBar) + Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); + this._inTopBar = false; +@@ -494,6 +585,10 @@ let WorkspaceIndicator = GObject.registerClass({ + this._statusLabel.visible = useMenu; + this._thumbnails.visible = !useMenu; + ++ this.setMenu(useMenu ++ ? new WorkspacesMenu(this) ++ : null); ++ + this._updateTopBarRedirect(); + } + +@@ -510,67 +605,11 @@ let WorkspaceIndicator = GObject.registerClass({ + + _onWorkspaceSwitched() { + this._currentWorkspace = global.workspace_manager.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() { + const current = this._currentWorkspace + 1; + return `${current}`; + } +- +- _updateMenuLabels() { +- for (let i = 0; i < this._workspacesItems.length; i++) { +- const item = this._workspacesItems[i]; +- item.label.text = Meta.prefs_get_workspace_name(i); +- } +- } +- +- _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++) { +- const name = Meta.prefs_get_workspace_name(i); +- const item = new PopupMenu.PopupMenuItem(name); +- +- item.connect('activate', +- () => this._activate(i)); +- +- item.setOrnament(i === this._currentWorkspace +- ? PopupMenu.Ornament.DOT +- : PopupMenu.Ornament.NONE); +- +- 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()); +- } +- } + }); +-- +2.50.0 + + +From ce6bbf7ba28a0a24045e460b3eeb47aabbeeebe6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 28 May 2025 02:16:33 +0200 +Subject: [PATCH 34/43] workspace-indicator: Include menu with previews + +The menu is currently only used when previews are disabled. But +as we are going to use the menu for changing workspace names, it +should be always available. So add it unconditionally, and show +it on right-click when using previews. + +Part-of: +--- + .../workspace-indicator/stylesheet-dark.css | 5 ++++ + .../workspace-indicator/workspaceIndicator.js | 25 +++++++++++++------ + 2 files changed, 23 insertions(+), 7 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index 5663b422..6719c05a 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -1,3 +1,8 @@ ++.workspace-indicator.previews:active { ++ background-color: none !important; ++ box-shadow: none !important; ++} ++ + .workspace-indicator .status-label { + padding: 0 8px; + } +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 9bb7cb60..8184efbb 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -524,6 +524,8 @@ let WorkspaceIndicator = GObject.registerClass({ + baseStyleClassName = baseStyleClass; + this.add_style_class_name(baseStyleClassName); + ++ this.setMenu(new WorkspacesMenu(this)); ++ + let container = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + x_expand: true, +@@ -545,6 +547,14 @@ let WorkspaceIndicator = GObject.registerClass({ + this._thumbnails = new WorkspacePreviews(); + container.add_child(this._thumbnails); + ++ this._thumbnails.connect('button-press-event', (a, event) => { ++ if (event.get_button() !== Clutter.BUTTON_SECONDARY) ++ return Clutter.EVENT_PROPAGATE; ++ ++ this.menu.toggle(); ++ return Clutter.EVENT_STOP; ++ }); ++ + this._workspaceManagerSignals = [ + workspaceManager.connect_after('workspace-switched', + this._onWorkspaceSwitched.bind(this)), +@@ -579,15 +589,16 @@ let WorkspaceIndicator = GObject.registerClass({ + } + + _updateThumbnailVisibility() { +- const useMenu = !this._settings.get_boolean('embed-previews'); +- this.reactive = useMenu; ++ const usePreviews = this._settings.get_boolean('embed-previews'); ++ this.reactive = !usePreviews; + +- this._statusLabel.visible = useMenu; +- this._thumbnails.visible = !useMenu; ++ this._thumbnails.visible = usePreviews; ++ this._statusLabel.visible = !usePreviews; + +- this.setMenu(useMenu +- ? new WorkspacesMenu(this) +- : null); ++ if (usePreviews) ++ this.add_style_class_name('previews'); ++ else ++ this.remove_style_class_name('previews'); + + this._updateTopBarRedirect(); + } +-- +2.50.0 + + +From d79cb50745803a7e6e7dc663580f1fb233b9130a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 20:59:58 +0200 +Subject: [PATCH 35/43] workspace-indicator: Show full name when using menu + +With workspace names becoming a more prominent feature, it makes +sense to expose them without opening the menu. + +Part-of: +--- + .../workspace-indicator/stylesheet-dark.css | 8 +++++ + .../workspace-indicator/workspaceIndicator.js | 32 +++++++------------ + 2 files changed, 20 insertions(+), 20 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index 6719c05a..cb0c6e62 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -4,8 +4,16 @@ + } + + .workspace-indicator .status-label { ++ width: 8em; + padding: 0 8px; + } ++.workspace-indicator .status-label:ltr { padding-right: 4px; } ++.workspace-indicator .status-label:rtl { padding-left: 4px; } ++ ++.workspace-indicator .system-status-icon { ++ padding: 0 !important; ++ margin: 0 !important; ++} + + .workspace-indicator .workspaces-view.hfade { + -st-hfade-offset: 20px; +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 8184efbb..2cd3de5f 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -533,16 +533,23 @@ let WorkspaceIndicator = GObject.registerClass({ + }); + this.add_actor(container); + +- let workspaceManager = global.workspace_manager; ++ this._statusBox = new St.BoxLayout(); ++ container.add_child(this._statusBox); + +- this._currentWorkspace = workspaceManager.get_active_workspace_index(); + this._statusLabel = new St.Label({ + style_class: 'status-label', ++ x_expand: true, + y_align: Clutter.ActorAlign.CENTER, +- text: this._getStatusText(), ++ text: this.menu.activeName, + }); ++ this._statusBox.add_child(this._statusLabel); ++ this._statusBox.add_child(new St.Icon({ ++ icon_name: 'pan-down-symbolic', ++ style_class: 'system-status-icon', ++ })); + +- container.add_actor(this._statusLabel); ++ this.menu.connect('active-name-changed', ++ () => this._statusLabel.set_text(this.menu.activeName)); + + this._thumbnails = new WorkspacePreviews(); + container.add_child(this._thumbnails); +@@ -555,11 +562,6 @@ let WorkspaceIndicator = GObject.registerClass({ + return Clutter.EVENT_STOP; + }); + +- this._workspaceManagerSignals = [ +- workspaceManager.connect_after('workspace-switched', +- this._onWorkspaceSwitched.bind(this)), +- ]; +- + this.connect('scroll-event', + (o, event) => this._thumbnails.handleScrollEvent(event)); + +@@ -593,7 +595,7 @@ let WorkspaceIndicator = GObject.registerClass({ + this.reactive = !usePreviews; + + this._thumbnails.visible = usePreviews; +- this._statusLabel.visible = !usePreviews; ++ this._statusBox.visible = !usePreviews; + + if (usePreviews) + this.add_style_class_name('previews'); +@@ -613,14 +615,4 @@ let WorkspaceIndicator = GObject.registerClass({ + ? Clutter.OffscreenRedirect.ALWAYS + : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY); + } +- +- _onWorkspaceSwitched() { +- this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); +- this._statusLabel.set_text(this._getStatusText()); +- } +- +- _getStatusText() { +- const current = this._currentWorkspace + 1; +- return `${current}`; +- } + }); +-- +2.50.0 + + +From bbcbd818726d387a5720686d483c2c79463d84b6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 9 Jun 2025 18:10:14 +0200 +Subject: [PATCH 36/43] workspace-indicator: Add background when using name + label + +Panel buttons are flat, so the name+arrow are not immediately +recognizable as a single control. Address this by adding a +background to the button when using the name label. + +Part-of: +--- + extensions/workspace-indicator/stylesheet-dark.css | 14 ++++++++++++++ + .../workspace-indicator/stylesheet-light.css | 14 ++++++++++++++ + .../workspace-indicator/workspaceIndicator.js | 7 +++++-- + 3 files changed, 33 insertions(+), 2 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index cb0c6e62..7a40664d 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -3,6 +3,20 @@ + box-shadow: none !important; + } + ++.workspace-indicator.name-label { ++ box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.17) !important; ++} ++.workspace-indicator.name-label:hover, ++.workspace-indicator.name-label:focus { ++ box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.28) !important; ++} ++.workspace-indicator.name-label:active { ++ box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.32) !important; ++} ++.workspace-indicator.name-label:active:hover { ++ box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.36) !important; ++} ++ + .workspace-indicator .status-label { + width: 8em; + padding: 0 8px; +diff --git a/extensions/workspace-indicator/stylesheet-light.css b/extensions/workspace-indicator/stylesheet-light.css +index 049b6a38..5191923c 100644 +--- a/extensions/workspace-indicator/stylesheet-light.css ++++ b/extensions/workspace-indicator/stylesheet-light.css +@@ -7,6 +7,20 @@ + + @import url("stylesheet-dark.css"); + ++.workspace-indicator.name-label { ++ box-shadow: inset 0 0 0 100px rgba(34, 34, 38, 0.17) !important; ++} ++.workspace-indicator.name-label:hover, ++.workspace-indicator.name-label:focus { ++ box-shadow: inset 0 0 0 100px rgba(34, 34, 38, 0.28) !important; ++} ++.workspace-indicator.name-label:active { ++ box-shadow: inset 0 0 0 100px rgba(34, 34, 38, 0.32) !important; ++} ++.workspace-indicator.name-label:active:hover { ++ box-shadow: inset 0 0 0 100px rgba(34, 34, 38, 0.36) !important; ++} ++ + .workspace-indicator .workspace { + background-color: #ccc; + } +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 2cd3de5f..0a4f33bf 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -597,10 +597,13 @@ let WorkspaceIndicator = GObject.registerClass({ + this._thumbnails.visible = usePreviews; + this._statusBox.visible = !usePreviews; + +- if (usePreviews) ++ if (usePreviews) { + this.add_style_class_name('previews'); +- else ++ this.remove_style_class_name('name-label'); ++ } else { + this.remove_style_class_name('previews'); ++ this.add_style_class_name('name-label'); ++ } + + this._updateTopBarRedirect(); + } +-- +2.50.0 + + +From 98db3fef2833ced71efe41de14ffd81780b304e6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 21:07:08 +0200 +Subject: [PATCH 37/43] workspace-indicator: Reimplement some libadwaita prefs + widgets + +Upstream now makes more use of libawaita, so reimplement the API we +need to make backporting a bit less painful. +--- + .../workspace-indicator/workspacePrefs.js | 239 ++++++++++++++++++ + 1 file changed, 239 insertions(+) + +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +index 0bd4a58b..11155f8a 100644 +--- a/extensions/workspace-indicator/workspacePrefs.js ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -16,6 +16,245 @@ const N_ = e => e; + const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; + const WORKSPACE_KEY = 'workspace-names'; + ++const PreferencesGroup = GObject.registerClass({ ++ Properties: { ++ 'title': GObject.ParamSpec.string( ++ 'title', '', '', ++ GObject.ParamFlags.READWRITE, ++ null), ++ }, ++}, class PreferencesGroup extends Gtk.Box { ++ _init(params) { ++ super._init({ ++ ...params, ++ orientation: Gtk.Orientation.VERTICAL, ++ }); ++ ++ const titleLabel = new Gtk.Label({ ++ halign: Gtk.Align.START, ++ }); ++ titleLabel.get_style_context().add_class('heading'); ++ ++ this.bind_property('title', ++ titleLabel, 'label', ++ GObject.BindingFlags.SYNC_CREATE); ++ this.add(titleLabel); ++ ++ this._list = new Gtk.ListBox({ ++ selection_mode: Gtk.SelectionMode.NONE, ++ valign: Gtk.Align.START, ++ //show_separators: true, ++ }); ++ this._list.get_style_context().add_class('boxed-list'); ++ this.add(this._list); ++ ++ this._list.connect('row-activated', ++ (l, row) => row.activate()); ++ } ++ ++ get title() { ++ return this._title || ''; ++ } ++ ++ set title(title) { ++ if (this._title === title) ++ return; ++ ++ this._title = title; ++ this.notify('title'); ++ } ++ ++ append(child) { ++ this._list.add(child); ++ } ++ ++ remove(child) { ++ this._list.remove(child); ++ } ++}); ++ ++const ActionRow = GObject.registerClass({ ++ Properties: { ++ 'title': GObject.ParamSpec.string( ++ 'title', '', '', ++ GObject.ParamFlags.READWRITE, ++ null), ++ 'subtitle': GObject.ParamSpec.string( ++ 'subtitle', '', '', ++ GObject.ParamFlags.READWRITE, ++ null), ++ 'activatable-widget': GObject.ParamSpec.object( ++ 'activatable-widget', '', '', ++ GObject.ParamFlags.READWRITE, ++ Gtk.Widget), ++ }, ++}, class ActionRow extends Gtk.ListBoxRow { ++ _init(params) { ++ super._init(params); ++ ++ const provider = new Gtk.CssProvider(); ++ provider.load_from_data(`* { ++ /*border-spacing: 6px 6px;*/ ++ min-height: 40px; ++ margin-left: 12px; ++ margin-right: 12px; ++ }`); ++ ++ const mainBox = new Gtk.Box({ ++ valign: Gtk.Align.CENTER, ++ hexpand: false, ++ }); ++ mainBox.get_style_context().add_provider(provider, ++ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); ++ this.add(mainBox); ++ ++ this._prefixes = new Gtk.Box({ ++ visible: false, ++ }); ++ mainBox.add(this._prefixes); ++ ++ const titleBox = new Gtk.Box({ ++ orientation: Gtk.Orientation.VERTICAL, ++ valign: Gtk.Align.CENTER, ++ hexpand: true, ++ }); ++ mainBox.add(titleBox); ++ ++ this._suffixes = new Gtk.Box({ ++ visible: false, ++ }); ++ mainBox.add(this._suffixes); ++ ++ const titleLabel = new Gtk.Label({ ++ lines: 0, ++ wrap: true, ++ wrap_mode: Pango.WrapMode.WORD_CHAR, ++ xalign: 0, ++ }); ++ ++ this.bind_property('title', ++ titleLabel, 'label', ++ GObject.BindingFlags.SYNC_CREATE); ++ titleBox.add(titleLabel); ++ ++ const subtitleLabel = new Gtk.Label({ ++ ellipsize: Pango.EllipsizeMode.NONE, ++ lines: 0, ++ wrap: true, ++ wrap_mode: Pango.WrapMode.WORD_CHAR, ++ xalign: 0, ++ no_show_all: true, ++ visible: this.subtitle.length > 0, ++ }); ++ subtitleLabel.get_style_context().add_class('dim-label'); ++ ++ this.bind_property('subtitle', ++ subtitleLabel, 'label', ++ GObject.BindingFlags.SYNC_CREATE); ++ titleBox.add(subtitleLabel); ++ } ++ ++ get title() { ++ return this._title || ''; ++ } ++ ++ set title(title) { ++ if (this._title === title) ++ return; ++ ++ this._title = title; ++ this.notify('title'); ++ } ++ ++ get subtitle() { ++ return this._subtitle || ''; ++ } ++ ++ set subtitle(subtitle) { ++ if (this._subtitle === subtitle) ++ return; ++ ++ this._subtitle = subtitle; ++ this.notify('subtitle'); ++ } ++ ++ get activitable_widget() { ++ return this._activatableWidget; ++ } ++ ++ set activatable_widget(widget) { ++ if (this._activatableWidget === widget) ++ return; ++ ++ this._activatableWidget = widget; ++ this.notify('activatable-widget'); ++ } ++ ++ add_prefix(child) { ++ this._prefixes.add(child); ++ this._prefixes.set_visible(true); ++ } ++ ++ add_suffix(child) { ++ this._suffixes.add(child); ++ this._suffixes.set_visible(true); ++ } ++ ++ activate() { ++ if (this._activatableWidget) ++ this._activatableWidget.mnemonic_activate(false); ++ } ++}); ++ ++const SpinRow = GObject.registerClass({ ++ Properties: { ++ 'adjustment': GObject.ParamSpec.object( ++ 'adjustment', '', '', ++ GObject.ParamFlags.READWRITE, ++ Gtk.Adjustment), ++ 'value': GObject.ParamSpec.double( ++ 'value', '', '', ++ GObject.ParamFlags.READWRITE, ++ -Infinity, Infinity, 0), ++ }, ++}, class SpinRow extends ActionRow { ++ _init(params) { ++ this._spinButton = new Gtk.SpinButton({ ++ valign: Gtk.Align.CENTER, ++ hexpand: true, ++ xalign: 1, ++ }); ++ super._init(params); ++ this.add_suffix(this._spinButton); ++ } ++ ++ get adjustment() { ++ return this._spinButton.get_adjustment(); ++ } ++ ++ set adjustment(adj) { ++ if (this.adjustment === adj) ++ return; ++ ++ this._spinButton.set_adjustment(adj); ++ this.notify('adjustment'); ++ } ++ ++ get value() { ++ return this._spinButton.get_value(); ++ } ++ ++ set value(value) { ++ const EPSILON = 0.005; ++ ++ if (Math.abs(this.value, value) < EPSILON) ++ return; ++ ++ this._spinButton.set_value(value); ++ this.notify('value'); ++ } ++}); ++ + const GeneralGroup = GObject.registerClass( + class GeneralGroup extends Gtk.Box { + _init() { +-- +2.50.0 + + +From c3d54e0e0c120ad09f15f9c95d215e14e8f0334d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 29 Jun 2025 21:26:40 +0200 +Subject: [PATCH 38/43] workspace-indicator: Refine preview settings + +Add a group title, and change the single switch row to radio rows +to explicitly choose between "Previews" and "Workspace Name". + +Part-of: +--- + .../workspace-indicator/workspacePrefs.js | 35 ++++++++++++------- + 1 file changed, 22 insertions(+), 13 deletions(-) + +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +index 11155f8a..e33d4716 100644 +--- a/extensions/workspace-indicator/workspacePrefs.js ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -256,29 +256,38 @@ const SpinRow = GObject.registerClass({ + }); + + const GeneralGroup = GObject.registerClass( +-class GeneralGroup extends Gtk.Box { ++class GeneralGroup extends PreferencesGroup { + _init() { + super._init({ +- orientation: Gtk.Orientation.VERTICAL, ++ title: _('Indicator'), + }); + +- const row = new Gtk.Box(); +- this.add(row); +- +- row.add(new Gtk.Label({ +- label: _('Show Previews'), +- })); ++ const previewCheck = new Gtk.RadioButton(); ++ const previewRow = new ActionRow({ ++ title: _('Previews'), ++ activatable_widget: previewCheck, ++ }); ++ previewRow.add_prefix(previewCheck); ++ this.append(previewRow); + +- const sw = new Gtk.Switch({ +- hexpand: true, +- halign: Gtk.Align.END, ++ const nameCheck = new Gtk.RadioButton({ ++ group: previewCheck, ++ }); ++ const nameRow = new ActionRow({ ++ title: _('Workspace Name'), ++ activatable_widget: nameCheck, + }); +- row.add(sw); ++ nameRow.add_prefix(nameCheck); ++ this.append(nameRow); + + const settings = ExtensionUtils.getSettings(); ++ if (settings.get_boolean('embed-previews')) ++ previewCheck.active = true; ++ else ++ nameCheck.active = true; + + settings.bind('embed-previews', +- sw, 'active', ++ previewCheck, 'active', + Gio.SettingsBindFlags.DEFAULT); + } + }); +-- +2.50.0 + + +From 2ce2e4439e13d1fa48502fdda7cff7dd41302482 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 16:20:05 +0200 +Subject: [PATCH 39/43] workspace-indicator: Include workspace settings + +While the "Multitasking" panel in Settings already exposes workspace +settings, it makes sense to expose them in our prefs dialog as well +where they are more in context. + +Part-of: +--- + .../workspace-indicator/workspacePrefs.js | 67 +++++++++++++++++++ + 1 file changed, 67 insertions(+) + +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +index e33d4716..316666ed 100644 +--- a/extensions/workspace-indicator/workspacePrefs.js ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -292,6 +292,72 @@ class GeneralGroup extends PreferencesGroup { + } + }); + ++const BehaviorGroup = GObject.registerClass( ++class BehaviorGroup extends PreferencesGroup { ++ _init() { ++ super._init({ ++ title: _('Behavior'), ++ }); ++ ++ const dynamicCheck = new Gtk.RadioButton(); ++ const dynamicRow = new ActionRow({ ++ title: _('Dynamic'), ++ subtitle: _('Automatically removes empty workspaces.'), ++ activatable_widget: dynamicCheck, ++ }); ++ dynamicRow.add_prefix(dynamicCheck); ++ this.append(dynamicRow); ++ ++ const fixedCheck = new Gtk.RadioButton({ ++ group: dynamicCheck, ++ }); ++ const fixedRow = new ActionRow({ ++ title: _('Fixed Number'), ++ subtitle: _('Specify a number of permanent workspaces.'), ++ activatable_widget: fixedCheck, ++ }); ++ fixedRow.add_prefix(fixedCheck); ++ this.append(fixedRow); ++ ++ const adjustment = new Gtk.Adjustment({ ++ lower: 1, ++ step_increment: 1, ++ value: 4, ++ upper: 36, // hard limit in mutter ++ }); ++ const numRow = new SpinRow({ ++ title: _('Number of Workspaces'), ++ adjustment, ++ }); ++ this.append(numRow); ++ ++ const mutterSettings = new Gio.Settings({ ++ schema_id: 'org.gnome.mutter', ++ }); ++ ++ if (mutterSettings.get_boolean('dynamic-workspaces')) ++ dynamicCheck.active = true; ++ else ++ fixedCheck.active = true; ++ ++ mutterSettings.bind('dynamic-workspaces', ++ dynamicCheck, 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ const desktopSettings = new Gio.Settings({ ++ schema_id: 'org.gnome.desktop.wm.preferences', ++ }); ++ ++ desktopSettings.bind('num-workspaces', ++ numRow, 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ fixedCheck.bind_property('active', ++ numRow, 'sensitive', ++ GObject.BindingFlags.SYNC_CREATE); ++ } ++}); ++ + const WorkspaceNameModel = GObject.registerClass( + class WorkspaceNameModel extends Gtk.ListStore { + _init(params) { +@@ -420,6 +486,7 @@ class WorkspacesPage extends Gtk.ScrolledWindow { + this.add(box); + + box.add(new GeneralGroup()); ++ box.add(new BehaviorGroup()); + + box.add(new Gtk.Label({ + label: '%s'.format(_('Workspace Names')), +-- +2.50.0 + + +From 55746c9d3ef546feed2e473382c42f11245ea82f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 28 May 2025 21:01:08 +0200 +Subject: [PATCH 40/43] workspace-indicator: Allow changing workspace names + from menu + +Instead of requiring the user to open the prefs dialog to change +workspace names, make the menu items themselves editable. + +Part-of: +--- + .../workspace-indicator/stylesheet-dark.css | 48 +++++++ + .../workspace-indicator/stylesheet-light.css | 16 +++ + .../workspace-indicator/workspaceIndicator.js | 130 +++++++++++++++++- + 3 files changed, 192 insertions(+), 2 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index 7a40664d..a74d25b5 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -58,3 +58,51 @@ + .workspace-indicator-window-preview.active { + background-color: #d4d4d4; + } ++ ++.workspace-indicator-menu { ++ min-width: 17em; ++} ++ ++.workspace-indicator-menu .editable-menu-item.popup-menu-item { ++ padding: 3px 12px; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button { ++ border-radius: 99px; ++ padding: 6px; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat { ++ background-color: transparent; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:hover { ++ background-color: rgba(255, 255, 255, 0.1); ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:active { ++ background-color: rgba(255, 255, 255, 0.15); ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:checked { ++ color: white; ++ background-color: #3584e4; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:checked:hover { ++ background-color: #629fea; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:checked:active { ++ background-color: #78aded; ++} ++ ++.workspace-indicator-menu .editable-menu-item .label { ++ padding: 0 11px; ++ width: 6.5em; ++} ++ ++.workspace-indicator-menu .editable-menu-item .entry { ++ padding: 9px 9px; ++ width: 6.5em; ++} +diff --git a/extensions/workspace-indicator/stylesheet-light.css b/extensions/workspace-indicator/stylesheet-light.css +index 5191923c..e4f2b45a 100644 +--- a/extensions/workspace-indicator/stylesheet-light.css ++++ b/extensions/workspace-indicator/stylesheet-light.css +@@ -37,3 +37,19 @@ + .workspace-indicator-window-preview.active { + background-color: #f6f5f4; + } ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:hover { ++ background-color: rgba(0, 0, 0, 0.1); ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:active { ++ background-color: rgba(0, 0, 0, 0.15); ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:checked:hover { ++ background-color: #1b6acb; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:checked:active { ++ background-color: #185fb4; ++} +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 0a4f33bf..5a8a819c 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -1,4 +1,4 @@ +-const { Clutter, Gio, GLib, GObject, Meta, St } = imports.gi; ++const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; + + const DND = imports.ui.dnd; + const ExtensionUtils = imports.misc.extensionUtils; +@@ -7,6 +7,8 @@ const PanelMenu = imports.ui.panelMenu; + const PopupMenu = imports.ui.popupMenu; + const Tweener = imports.ui.tweener; + ++const Signals = imports.signals; ++ + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; + +@@ -404,6 +406,122 @@ const WorkspacePreviews = GObject.registerClass({ + } + }); + ++class EditableMenuItem extends PopupMenu.PopupBaseMenuItem { ++ constructor() { ++ super({ ++ style_class: 'editable-menu-item', ++ }); ++ this.actor.get_accessible().set_description(0, ++ _('Press %s to edit').format('e')); ++ ++ this._ornamentLabel.y_align = Clutter.ActorAlign.CENTER; ++ ++ const stack = new Shell.Stack({ ++ x_expand: true, ++ x_align: Clutter.ActorAlign.START, ++ }); ++ this.actor.add_child(stack); ++ ++ this.label = new St.Label({ ++ style_class: 'label', ++ y_align: Clutter.ActorAlign.CENTER, ++ }); ++ stack.add_child(this.label); ++ this.actor.label_actor = this.label; ++ ++ this._entry = new St.Entry({ ++ style_class: 'entry', ++ opacity: 0, ++ reactive: false, ++ }); ++ stack.add_child(this._entry); ++ ++ this.label.bind_property('text', ++ this._entry, 'text', ++ GObject.BindingFlags.DEFAULT); ++ ++ this._entry.clutter_text.connect('activate', ++ () => this._stopEditing()); ++ ++ this._editButton = new St.Button({ ++ style_class: 'icon-button flat', ++ child: new St.Icon({ ++ icon_name: 'document-edit-symbolic', ++ icon_size: 16, ++ }), ++ button_mask: St.ButtonMask.ONE, ++ toggle_mode: true, ++ }); ++ this._editButton.set_x_align(Clutter.ActorAlign.END); ++ this._editButton.set_y_align(Clutter.ActorAlign.CENTER); ++ this.actor.add_child(this._editButton); ++ ++ this._editButton.connect('notify::checked', () => { ++ if (this._editButton.checked) { ++ this._editButton.child.icon_name = 'object-select-symbolic'; ++ this._startEditing(); ++ } else { ++ this._editButton.child.icon_name = 'document-edit-symbolic'; ++ this._stopEditing(); ++ } ++ }); ++ this.actor.connect('key-release-event', (o, event) => { ++ if (event.get_key_symbol() === Clutter.KEY_e) ++ this._editButton.checked = true; ++ }); ++ ++ this._keyFocusId = global.stage.connect('notify::key-focus', () => { ++ const {keyFocus} = global.stage; ++ if (!keyFocus || !this.actor.contains(keyFocus)) ++ this._stopEditing(); ++ }); ++ ++ this.actor.connect('destroy', () => { ++ global.stage.disconnect(this._keyFocusId); ++ delete this._keyFocusId; ++ }); ++ } ++ ++ _switchActor(from, to) { ++ to.reactive = true; ++ Tweener.addTween(to, { ++ opacity: 255, ++ time: 0.3, ++ transition: 'easeOutQuad', ++ }); ++ ++ Tweener.addTween(from, { ++ opacity: 0, ++ time: 0.3, ++ transition: 'easeOutQuad', ++ onComplete: () => { ++ from.reactive = false; ++ }, ++ }); ++ } ++ ++ _startEditing() { ++ this._switchActor(this.label, this._entry); ++ ++ this._entry.clutter_text.set_selection(0, -1); ++ this._entry.clutter_text.grab_key_focus(); ++ } ++ ++ _stopEditing() { ++ if (this.label.text !== this._entry.text) { ++ this.label.text = this._entry.text; ++ this.emit('edited'); ++ } ++ ++ if (this._editButton.checked) ++ this._editButton.checked = false; ++ ++ this._switchActor(this._entry, this.label); ++ this.actor.navigate_focus(this.actor, St.DirectionType.TAB_FORWARD, false); ++ } ++} ++Signals.addSignalMethods(EditableMenuItem.prototype); ++ + class WorkspacesMenu extends PopupMenu.PopupMenu { + constructor(sourceActor) { + super(sourceActor, 0.5, St.Side.TOP); +@@ -461,13 +579,21 @@ class WorkspacesMenu extends PopupMenu.PopupMenu { + + const section = this._workspacesSection.actor; + while (section.get_n_children() < nWorkspaces) { +- const item = new PopupMenu.PopupMenuItem(''); ++ const item = new EditableMenuItem(); + item.connect('activate', (o, event) => { + const index = section.get_children().indexOf(item.actor); + const workspace = workspaceManager.get_workspace_by_index(index); + if (workspace) + workspace.activate(event.get_time()); + }); ++ item.connect('edited', () => { ++ const nLabels = section.get_n_children(); ++ const oldNames = this._desktopSettings.get_strv('workspace-names'); ++ const newNames = ++ section.get_children().map(c => c._delegate.label.text); ++ this._desktopSettings.set_strv('workspace-names', ++ [...newNames, ...oldNames.slice(nLabels)]); ++ }); + this._workspacesSection.addMenuItem(item); + } + +-- +2.50.0 + + +From 9fd65412c1ebdb6c4c4f1026f95de72e985dcd1d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 28 May 2025 21:04:36 +0200 +Subject: [PATCH 41/43] workspace-indicator: Remove workspace names from prefs + +Now that names can be changed from the extension itself, we no +longer need to expose them in the prefs dialog. + +Part-of: +--- + .../workspace-indicator/workspacePrefs.js | 181 +----------------- + 1 file changed, 1 insertion(+), 180 deletions(-) + +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +index 316666ed..bc4b3d01 100644 +--- a/extensions/workspace-indicator/workspacePrefs.js ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -4,17 +4,13 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + + /* exported WorkspacesPage */ +-const { Gio, GLib, GObject, Gtk, Pango } = imports.gi; ++const { Gio, GObject, Gtk, Pango } = imports.gi; + + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); + + const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); + const _ = Gettext.gettext; +-const N_ = e => e; +- +-const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; +-const WORKSPACE_KEY = 'workspace-names'; + + const PreferencesGroup = GObject.registerClass({ + Properties: { +@@ -358,114 +354,6 @@ class BehaviorGroup extends PreferencesGroup { + } + }); + +-const WorkspaceNameModel = GObject.registerClass( +-class WorkspaceNameModel extends Gtk.ListStore { +- _init(params) { +- super._init(params); +- this.set_column_types([GObject.TYPE_STRING]); +- +- this.Columns = { +- LABEL: 0, +- }; +- +- this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); +- //this._settings.connect('changed::workspace-names', this._reloadFromSettings.bind(this)); +- +- this._reloadFromSettings(); +- +- // overriding class closure doesn't work, because GtkTreeModel +- // plays tricks with marshallers and class closures +- this.connect('row-changed', this._onRowChanged.bind(this)); +- this.connect('row-inserted', this._onRowInserted.bind(this)); +- this.connect('row-deleted', this._onRowDeleted.bind(this)); +- } +- +- _reloadFromSettings() { +- if (this._preventChanges) +- return; +- this._preventChanges = true; +- +- let newNames = this._settings.get_strv(WORKSPACE_KEY); +- +- let i = 0; +- let [ok, iter] = this.get_iter_first(); +- while (ok && i < newNames.length) { +- this.set(iter, [this.Columns.LABEL], [newNames[i]]); +- +- ok = this.iter_next(iter); +- i++; +- } +- +- while (ok) +- ok = this.remove(iter); +- +- for ( ; i < newNames.length; i++) { +- iter = this.append(); +- this.set(iter, [this.Columns.LABEL], [newNames[i]]); +- } +- +- this._preventChanges = false; +- } +- +- _onRowChanged(self, path, iter) { +- if (this._preventChanges) +- return; +- this._preventChanges = true; +- +- let index = path.get_indices()[0]; +- let names = this._settings.get_strv(WORKSPACE_KEY); +- +- if (index >= names.length) { +- // fill with blanks +- for (let i = names.length; i <= index; i++) +- names[i] = ''; +- } +- +- names[index] = this.get_value(iter, this.Columns.LABEL); +- +- this._settings.set_strv(WORKSPACE_KEY, names); +- +- this._preventChanges = false; +- } +- +- _onRowInserted(self, path, iter) { +- if (this._preventChanges) +- return; +- this._preventChanges = true; +- +- let index = path.get_indices()[0]; +- let names = this._settings.get_strv(WORKSPACE_KEY); +- let label = this.get_value(iter, this.Columns.LABEL) || ''; +- names.splice(index, 0, label); +- +- this._settings.set_strv(WORKSPACE_KEY, names); +- +- this._preventChanges = false; +- } +- +- _onRowDeleted(self, path) { +- if (this._preventChanges) +- return; +- this._preventChanges = true; +- +- let index = path.get_indices()[0]; +- let names = this._settings.get_strv(WORKSPACE_KEY); +- +- if (index >= names.length) +- return; +- +- names.splice(index, 1); +- +- // compact the array +- for (let i = names.length - 1; i >= 0 && !names[i]; i++) +- names.pop(); +- +- this._settings.set_strv(WORKSPACE_KEY, names); +- +- this._preventChanges = false; +- } +-}); +- + var WorkspacesPage = GObject.registerClass( + class WorkspacesPage extends Gtk.ScrolledWindow { + _init() { +@@ -488,73 +376,6 @@ class WorkspacesPage extends Gtk.ScrolledWindow { + box.add(new GeneralGroup()); + box.add(new BehaviorGroup()); + +- box.add(new Gtk.Label({ +- label: '%s'.format(_('Workspace Names')), +- use_markup: true, +- margin_bottom: 6, +- hexpand: true, +- halign: Gtk.Align.START +- })); +- +- this._store = new WorkspaceNameModel(); +- this._treeView = new Gtk.TreeView({ +- model: this._store, +- headers_visible: false, +- reorderable: true, +- hexpand: true, +- vexpand: true +- }); +- +- let column = new Gtk.TreeViewColumn({ title: _('Name') }); +- let renderer = new Gtk.CellRendererText({ editable: true }); +- renderer.connect('edited', this._cellEdited.bind(this)); +- column.pack_start(renderer, true); +- column.add_attribute(renderer, 'text', this._store.Columns.LABEL); +- this._treeView.append_column(column); +- +- box.add(this._treeView); +- +- let toolbar = new Gtk.Toolbar({ icon_size: Gtk.IconSize.SMALL_TOOLBAR }); +- toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR); +- +- let newButton = new Gtk.ToolButton({ icon_name: 'list-add-symbolic' }); +- newButton.connect('clicked', this._newClicked.bind(this)); +- toolbar.add(newButton); +- +- let delButton = new Gtk.ToolButton({ icon_name: 'list-remove-symbolic' }); +- delButton.connect('clicked', this._delClicked.bind(this)); +- toolbar.add(delButton); +- +- let selection = this._treeView.get_selection(); +- selection.connect('changed', () => { +- delButton.sensitive = selection.count_selected_rows() > 0; +- }); +- delButton.sensitive = selection.count_selected_rows() > 0; +- +- box.add(toolbar); +- + this.show_all(); + } +- +- _cellEdited(renderer, path, newText) { +- let [ok, iter] = this._store.get_iter_from_string(path); +- +- if (ok) +- this._store.set(iter, [this._store.Columns.LABEL], [newText]); +- } +- +- _newClicked() { +- let iter = this._store.append(); +- let index = this._store.get_path(iter).get_indices()[0]; +- +- let label = _('Workspace %d').format(index + 1); +- this._store.set(iter, [this._store.Columns.LABEL], [label]); +- } +- +- _delClicked() { +- let [any, model_, iter] = this._treeView.get_selection().get_selected(); +- +- if (any) +- this._store.remove(iter); +- } + }); +-- +2.50.0 + + +From f6520c27519ae3bf7a9aa9c52eeaa2a908cbe44f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 30 May 2025 16:39:22 +0200 +Subject: [PATCH 42/43] window-list: Adjust to workspace-indicator changes + +Keep the `.panel-button` class to get the expected hover/focus/active +styling when using a regular menu button, but remove the horizontal +padding when using previews for fittsability. + +Part-of: +--- + extensions/window-list/extension.js | 6 ------ + extensions/window-list/stylesheet.css | 5 +++++ + 2 files changed, 5 insertions(+), 6 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index b2357939..37b5ea09 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -1236,12 +1236,6 @@ class WindowList { + + const BottomWorkspaceIndicator = GObject.registerClass( + class BottomWorkspaceIndicator extends WorkspaceIndicator { +- _init(params) { +- super._init(params); +- +- this.remove_style_class_name('panel-button'); +- } +- + setMenu(menu) { + super.setMenu(menu); + +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index 349404c5..7da8ac7a 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -13,6 +13,11 @@ + height: 2.25em; + } + ++.window-list-workspace-indicator.previews { ++ -natural-hpadding: 0 !important; ++ -minimum-hpadding: 0 !important; ++} ++ + .window-list { + spacing: 2px; + font-size: 10pt; +-- +2.50.0 + + +From cea785875ba9f155031ab94cbb3a61abba546252 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 2 Jul 2024 19:04:10 +0200 +Subject: [PATCH 43/43] workspace-indicator: Re-fittsify workspace previews + +For the window-list extension, it is important that the workspace +previews extend to the bottom edge for easier click targets. + +That broke while merging the code with the workspace-indicator, +fix it again by moving the padding from the parent box into the +thumbnail children. +--- + .../workspace-indicator/stylesheet-dark.css | 16 ++++++++++++- + .../workspace-indicator/workspaceIndicator.js | 23 +++++++++++++++---- + 2 files changed, 33 insertions(+), 6 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index a74d25b5..acf63524 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -34,10 +34,24 @@ + } + + .workspace-indicator .workspaces-box { +- padding: 5px; + spacing: 3px; + } + ++.workspace-indicator .workspace-box { ++ padding-top: 5px; ++ padding-bottom: 5px; ++} ++ ++.workspace-indicator StButton:first-child:ltr > .workspace-box, ++.workspace-indicator StButton:last-child:rtl > .workspace-box { ++ padding-left: 5px; ++} ++ ++.workspace-indicator StButton:last-child:ltr > .workspace-box, ++.workspace-indicator StButton:first-child:rtl > .workspace-box { ++ padding-right: 5px; ++} ++ + .workspace-indicator .workspace { + width: 52px; + border: 1px solid transparent; +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 5a8a819c..89099b31 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -120,14 +120,27 @@ let WorkspaceThumbnail = GObject.registerClass({ + }, class WorkspaceThumbnail extends St.Button { + _init(index) { + super._init({ ++ y_fill: true, ++ }); ++ ++ const box = new St.BoxLayout({ ++ style_class: 'workspace-box', ++ y_expand: true, ++ vertical: true, ++ }); ++ this.set_child(box); ++ ++ this._preview = new St.Bin({ + style_class: 'workspace', + child: new Clutter.Actor({ + layout_manager: new WorkspaceLayout(), + clip_to_allocation: true + }), + x_fill: true, +- y_fill: true ++ y_fill: true, ++ y_expand: true, + }); ++ box.add_child(this._preview); + + this._tooltip = new St.Label({ + style_class: 'dash-label', +@@ -184,7 +197,7 @@ let WorkspaceThumbnail = GObject.registerClass({ + let preview = new WindowPreview(window); + preview.connect('clicked', (a, btn) => this.emit('clicked', btn)); + this._windowPreviews.set(window, preview); +- this.child.add_child(preview); ++ this._preview.child.add_child(preview); + } + + _removeWindow(window) { +@@ -204,7 +217,7 @@ let WorkspaceThumbnail = GObject.registerClass({ + if (!preview) + continue; + +- this.child.set_child_above_sibling(preview, lastPreview); ++ this._preview.child.set_child_above_sibling(preview, lastPreview); + lastPreview = preview; + } + } +@@ -348,9 +361,9 @@ const WorkspacePreviews = GObject.registerClass({ + let thumbs = this._thumbnailsBox.get_children(); + for (let i = 0; i < thumbs.length; i++) { + if (i == activeIndex) +- thumbs[i].add_style_class_name('active'); ++ thumbs[i]._preview.add_style_class_name('active'); + else +- thumbs[i].remove_style_class_name('active'); ++ thumbs[i]._preview.remove_style_class_name('active'); + } + } + +-- +2.50.0 +