diff --git a/SOURCES/more-ws-previews.patch b/SOURCES/more-ws-previews.patch new file mode 100644 index 0000000..40a8b74 --- /dev/null +++ b/SOURCES/more-ws-previews.patch @@ -0,0 +1,4002 @@ +From c4fafbcf01fc3c3846e5fe7d60d9aac623afdd9f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 18 Apr 2024 18:09:40 +0200 +Subject: [PATCH 01/29] prefs: Fix loading custom CSS + +GTK changed the annotation of `gtk_css_provider_load_from_data()`, +and as a result the `length` parameter is no longer interpreted +as an implicit array length, but has to be specified explicitly. +--- + extensions/auto-move-windows/prefs.js | 2 +- + extensions/classification-banner/prefs.js | 2 +- + extensions/heads-up-display/prefs.js | 2 +- + extensions/window-list/prefs.js | 2 +- + extensions/workspace-indicator/prefs.js | 2 +- + 5 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/extensions/auto-move-windows/prefs.js b/extensions/auto-move-windows/prefs.js +index 2c529067..db09c28b 100644 +--- a/extensions/auto-move-windows/prefs.js ++++ b/extensions/auto-move-windows/prefs.js +@@ -48,7 +48,7 @@ class AutoMoveSettingsWidget extends Gtk.ScrolledWindow { + const context = this._list.get_style_context(); + const cssProvider = new Gtk.CssProvider(); + cssProvider.load_from_data( +- 'list { min-width: 30em; }'); ++ 'list { min-width: 30em; }', -1); + + context.add_provider(cssProvider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); +diff --git a/extensions/classification-banner/prefs.js b/extensions/classification-banner/prefs.js +index a5dd8af1..0a91a5da 100644 +--- a/extensions/classification-banner/prefs.js ++++ b/extensions/classification-banner/prefs.js +@@ -137,7 +137,7 @@ class AppearancePrefs extends Adw.PreferencesGroup { + padding: 6px; + color: ${item.color}; + background-color: ${item.background_color}; +- }`); ++ }`, -1); + child.get_style_context().add_provider(provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); + child.label = item.message; +diff --git a/extensions/heads-up-display/prefs.js b/extensions/heads-up-display/prefs.js +index a7106e07..9262c11a 100644 +--- a/extensions/heads-up-display/prefs.js ++++ b/extensions/heads-up-display/prefs.js +@@ -55,7 +55,7 @@ var settings; + function init() { + settings = ExtensionUtils.getSettings("org.gnome.shell.extensions.heads-up-display"); + const cssProvider = new Gtk.CssProvider(); +- cssProvider.load_from_data(cssData); ++ cssProvider.load_from_data(cssData, -1); + + const display = Gdk.Display.get_default(); + Gtk.StyleContext.add_provider_for_display(display, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); +diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js +index aec8cc9d..e35990ff 100644 +--- a/extensions/window-list/prefs.js ++++ b/extensions/window-list/prefs.js +@@ -43,7 +43,7 @@ class WindowListPrefsWidget extends Gtk.Box { + const context = box.get_style_context(); + const cssProvider = new Gtk.CssProvider(); + cssProvider.load_from_data( +- 'box { padding: 12px; }'); ++ 'box { padding: 12px; }', -1); + + context.add_provider(cssProvider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); +diff --git a/extensions/workspace-indicator/prefs.js b/extensions/workspace-indicator/prefs.js +index 567f3e99..d307dcac 100644 +--- a/extensions/workspace-indicator/prefs.js ++++ b/extensions/workspace-indicator/prefs.js +@@ -48,7 +48,7 @@ class WorkspaceSettingsWidget extends Gtk.ScrolledWindow { + const context = this._list.get_style_context(); + const cssProvider = new Gtk.CssProvider(); + cssProvider.load_from_data( +- 'list { min-width: 25em; }'); ++ 'list { min-width: 25em; }', -1); + + context.add_provider(cssProvider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); +-- +2.44.0 + + +From a6e988875f52a49289677ca4d883a98b5515033f 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 02/29] 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/stylesheet.css | 1 - + extensions/panel-favorites/meson.build | 1 + + extensions/places-menu/meson.build | 1 + + extensions/screenshot-window-sizer/meson.build | 1 + + extensions/systemMonitor/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 + + 24 files changed, 16 insertions(+), 9 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/native-window-placement/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 b027381d..20406845 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('adwShim.js', '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 35ba2ecf..5f2ebe7e 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 8e691426..3961141c 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 ca00d01a..6f60b08d 100644 +--- a/extensions/meson.build ++++ b/extensions/meson.build +@@ -15,7 +15,7 @@ foreach e : enabled_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/stylesheet.css b/extensions/native-window-placement/stylesheet.css +deleted file mode 100644 +index 25134b65..00000000 +--- a/extensions/native-window-placement/stylesheet.css ++++ /dev/null +@@ -1 +0,0 @@ +-/* This extensions requires no special styling */ +diff --git a/extensions/panel-favorites/meson.build b/extensions/panel-favorites/meson.build +index 48504f63..6b9bb19c 100644 +--- a/extensions/panel-favorites/meson.build ++++ b/extensions/panel-favorites/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/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/systemMonitor/meson.build b/extensions/systemMonitor/meson.build +index b6548b14..bd3a8484 100644 +--- a/extensions/systemMonitor/meson.build ++++ b/extensions/systemMonitor/meson.build +@@ -3,6 +3,7 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++extension_data += files('stylesheet.css') + + if classic_mode_enabled + extension_data += files('classic.css') +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.44.0 + + +From 071226445e95d1a5551378aaf3c83db625fc2422 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/29] 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 | 440 +---------------- + extensions/workspace-indicator/meson.build | 2 +- + .../workspace-indicator/workspaceIndicator.js | 454 ++++++++++++++++++ + po/POTFILES.in | 2 +- + 4 files changed, 457 insertions(+), 441 deletions(-) + create mode 100644 extensions/workspace-indicator/workspaceIndicator.js + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 6974062b..5e1ed8e5 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -1,449 +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 Me = ExtensionUtils.getCurrentExtension(); +- +-const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); +-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; +- +-const MAX_THUMBNAILS = 6; +- +-let WindowPreview = GObject.registerClass( +-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._updateVisible(); +- this.queue_relayout(); +- }); +- this._minimizedChangedId = this._window.connect('notify::minimized', +- this._updateVisible.bind(this)); +- +- this._focusChangedId = global.display.connect('notify::focus-window', +- this._onFocusChanged.bind(this)); +- this._onFocusChanged(); +- } +- +- // needed for DND +- 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._focusChangedId); +- } +- +- _onFocusChanged() { +- if (global.display.focus_window === this._window) +- this.add_style_class_name('active'); +- else +- this.remove_style_class_name('active'); +- } +- +- _updateVisible() { +- const monitor = Main.layoutManager.findIndexForActor(this); +- const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); +- this.visible = this._window.get_frame_rect().overlap(workArea) && +- this._window.window_type !== Meta.WindowType.DESKTOP && +- 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) { +- 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) { +- const childBox = new Clutter.ActorBox(); +- const frameRect = child.metaWindow.get_frame_rect(); +- childBox.set_size( +- Math.round(Math.min(frameRect.width, workArea.width) * hscale), +- Math.round(Math.min(frameRect.height, workArea.height) * vscale)); +- childBox.set_origin( +- Math.round((frameRect.x - workArea.x) * hscale), +- Math.round((frameRect.y - workArea.y) * vscale)); +- child.allocate(childBox); +- } +- } +-}); +- +-let WorkspaceThumbnail = GObject.registerClass( +-class WorkspaceThumbnail extends St.Button { +- _init(index) { +- super._init({ +- style_class: 'workspace', +- child: new Clutter.Actor({ +- layout_manager: new WorkspaceLayout(), +- clip_to_allocation: 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.metaWindow) +- return false; +- +- this._moveWindow(source.metaWindow); +- return true; +- } +- +- handleDragOver(source) { +- if (source.metaWindow) +- 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); +- } +- +- 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.set({ +- text: Meta.prefs_get_workspace_name(this._index), +- visible: true, +- opacity: 0, +- }); +- +- 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.clamp( +- stageX + xOffset, +- monitor.x, +- monitor.x + monitor.width - tipWidth); +- const y = stageY + thumbHeight + TOOLTIP_OFFSET; +- this._tooltip.set_position(x, y); +- } +- +- this._tooltip.ease({ +- opacity: this.hover ? 255 : 0, +- duration: TOOLTIP_ANIMATION_TIME, +- mode: Clutter.AnimationMode.EASE_OUT_QUAD, +- 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._updateThumbnailVisibility.bind(this)), +- ]; +- +- this.connect('scroll-event', this._onScrollEvent.bind(this)); +- this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); +- this._createWorkspacesSection(); +- this._updateThumbnails(); +- this._updateThumbnailVisibility(); +- +- 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(); +- } +- +- _updateThumbnailVisibility() { +- const { workspaceManager } = global; +- const vertical = workspaceManager.layout_rows === -1; +- const useMenu = +- vertical || workspaceManager.n_workspaces > MAX_THUMBNAILS; +- this.reactive = useMenu; +- +- this._statusLabel.visible = useMenu; +- this._thumbnailsBox.visible = !useMenu; +- +- // Disable offscreen-redirect when showing the workspace switcher +- // so that clip-to-allocation works +- Main.panel.set_offscreen_redirect(useMenu +- ? 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(); +- this._updateThumbnailVisibility(); +- } +- +- _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 { 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..b98de047 +--- /dev/null ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -0,0 +1,454 @@ ++// SPDX-FileCopyrightText: 2011 Erick Pérez Castellanos ++// SPDX-FileCopyrightText: 2011 Giovanni Campagna ++// SPDX-FileCopyrightText: 2017 Florian Müllner ++// ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++const { Clutter, Gio, GObject, Meta, St } = imports.gi; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++ ++const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); ++const _ = Gettext.gettext; ++ ++const DND = imports.ui.dnd; ++const Main = imports.ui.main; ++const PanelMenu = imports.ui.panelMenu; ++const PopupMenu = imports.ui.popupMenu; ++ ++const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; ++const WORKSPACE_KEY = 'workspace-names'; ++ ++const TOOLTIP_OFFSET = 6; ++const TOOLTIP_ANIMATION_TIME = 150; ++ ++const MAX_THUMBNAILS = 6; ++ ++const WindowPreview = GObject.registerClass( ++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._checkRelayout()); ++ this._positionChangedId = this._window.connect('position-changed', ++ () => this._checkRelayout()); ++ this._minimizedChangedId = this._window.connect('notify::minimized', ++ () => this._updateVisible()); ++ this._typeChangedId = this._window.connect('notify::window-type', ++ () => this._updateVisible()); ++ this._updateVisible(); ++ ++ this._focusChangedId = global.display.connect('notify::focus-window', ++ this._onFocusChanged.bind(this)); ++ this._onFocusChanged(); ++ } ++ ++ // needed for DND ++ get metaWindow() { ++ return this._window; ++ } ++ ++ _onDestroy() { ++ this._window.disconnect(this._sizeChangedId); ++ this._window.disconnect(this._positionChangedId); ++ this._window.disconnect(this._minimizedChangedId); ++ this._window.disconnect(this._typeChangedId); ++ 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'); ++ } ++ ++ _checkRelayout() { ++ const monitor = Main.layoutManager.findIndexForActor(this); ++ const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); ++ if (this._window.get_frame_rect().overlap(workArea)) ++ this.queue_relayout(); ++ } ++ ++ _updateVisible() { ++ this.visible = this._window.window_type !== Meta.WindowType.DESKTOP && ++ this._window.showing_on_its_workspace(); ++ } ++}); ++ ++const 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) { ++ 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) { ++ const childBox = new Clutter.ActorBox(); ++ const frameRect = child.metaWindow.get_frame_rect(); ++ childBox.set_size( ++ Math.round(Math.min(frameRect.width, workArea.width) * hscale), ++ Math.round(Math.min(frameRect.height, workArea.height) * vscale)); ++ childBox.set_origin( ++ Math.round((frameRect.x - workArea.x) * hscale), ++ Math.round((frameRect.y - workArea.y) * vscale)); ++ child.allocate(childBox); ++ } ++ } ++}); ++ ++const WorkspaceThumbnail = GObject.registerClass( ++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_expand: true, ++ y_expand: 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.metaWindow) ++ return false; ++ ++ this._moveWindow(source.metaWindow); ++ return true; ++ } ++ ++ handleDragOver(source) { ++ if (source.metaWindow) ++ 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); ++ } ++ ++ 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.set({ ++ text: Meta.prefs_get_workspace_name(this._index), ++ visible: true, ++ opacity: 0, ++ }); ++ ++ 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.clamp( ++ stageX + xOffset, ++ monitor.x, ++ monitor.x + monitor.width - tipWidth); ++ const y = stageY + thumbHeight + TOOLTIP_OFFSET; ++ this._tooltip.set_position(x, y); ++ } ++ ++ this._tooltip.ease({ ++ opacity: this.hover ? 255 : 0, ++ duration: TOOLTIP_ANIMATION_TIME, ++ mode: Clutter.AnimationMode.EASE_OUT_QUAD, ++ 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( ++class WorkspaceIndicator extends PanelMenu.Button { ++ _init() { ++ super._init(0.5, _('Workspace Indicator')); ++ ++ let container = new St.Widget({ ++ layout_manager: new Clutter.BinLayout(), ++ x_expand: true, ++ y_expand: true, ++ }); ++ this.add_child(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_child(this._statusLabel); ++ ++ this._thumbnailsBox = new St.BoxLayout({ ++ style_class: 'panel-workspace-indicator-box', ++ y_expand: true, ++ reactive: true, ++ }); ++ ++ container.add_child(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._updateThumbnailVisibility.bind(this)), ++ ]; ++ ++ this.connect('scroll-event', this._onScrollEvent.bind(this)); ++ this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); ++ this._createWorkspacesSection(); ++ this._updateThumbnails(); ++ this._updateThumbnailVisibility(); ++ ++ 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 = i; 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(); ++ } ++ ++ _updateThumbnailVisibility() { ++ const {workspaceManager} = global; ++ const vertical = workspaceManager.layout_rows === -1; ++ const useMenu = ++ vertical || workspaceManager.n_workspaces > MAX_THUMBNAILS; ++ this.reactive = useMenu; ++ ++ this._statusLabel.visible = useMenu; ++ this._thumbnailsBox.visible = !useMenu; ++ ++ // Disable offscreen-redirect when showing the workspace switcher ++ // so that clip-to-allocation works ++ Main.panel.set_offscreen_redirect(useMenu ++ ? 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(); ++ this._updateThumbnailVisibility(); ++ } ++ ++ _updateMenuOrnament() { ++ for (let i = 0; i < this._workspacesItems.length; i++) { ++ this._workspacesItems[i].setOrnament(i === this._currentWorkspace ++ ? PopupMenu.Ornament.DOT ++ : PopupMenu.Ornament.NO_DOT); ++ } ++ } ++ ++ _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); ++ }); ++ ++ this._workspacesItems[i].setOrnament(i === this._currentWorkspace ++ ? PopupMenu.Ornament.DOT ++ : PopupMenu.Ornament.NO_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_child(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 10b1d517..bd39ab61 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -17,5 +17,5 @@ extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml + extensions/window-list/prefs.js + extensions/window-list/workspaceIndicator.js + extensions/windowsNavigator/extension.js +-extensions/workspace-indicator/extension.js + extensions/workspace-indicator/prefs.js ++extensions/workspace-indicator/workspaceIndicator.js +-- +2.44.0 + + +From 4720bf9f69c91c6fa39897534921eb4f2eceb8eb 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/29] 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 | 8 ++++---- + extensions/workspace-indicator/workspaceIndicator.js | 6 ++++-- + 2 files changed, 8 insertions(+), 6 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css +index 84aaf454..4e12cce4 100644 +--- a/extensions/workspace-indicator/stylesheet.css ++++ b/extensions/workspace-indicator/stylesheet.css +@@ -1,20 +1,20 @@ +-.panel-workspace-indicator { ++.workspace-indicator .status-label { + padding: 0 8px; + } + +-.panel-workspace-indicator-box { ++.workspace-indicator .workspaces-box { + padding: 4px 0; + spacing: 4px; + } + +-.panel-workspace-indicator-box .workspace { ++.workspace-indicator .workspace { + width: 40px; + border: 2px solid #000; + border-radius: 2px; + background-color: #595959; + } + +-.panel-workspace-indicator-box .workspace.active { ++.workspace-indicator .workspace.active { + border-color: #fff; + } + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index b98de047..101c89c6 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -263,6 +263,8 @@ class WorkspaceIndicator extends PanelMenu.Button { + _init() { + super._init(0.5, _('Workspace Indicator')); + ++ this.add_style_class_name('workspace-indicator'); ++ + let container = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + x_expand: true, +@@ -274,7 +276,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(), + }); +@@ -282,7 +284,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + container.add_child(this._statusLabel); + + this._thumbnailsBox = new St.BoxLayout({ +- style_class: 'panel-workspace-indicator-box', ++ style_class: 'workspaces-box', + y_expand: true, + reactive: true, + }); +-- +2.44.0 + + +From 22f44ae9f21337a11a09447763fbd223e37f3d56 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/29] 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 | 4 ++-- + extensions/window-list/workspaceIndicator.js | 2 +- + 3 files changed, 5 insertions(+), 5 deletions(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index 375a33e1..ab982b92 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -58,11 +58,11 @@ + border-color: #888; + } + +-.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; + } +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index 87813a42..a13f72d8 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -101,12 +101,12 @@ + border-color: #fff; + } + +-.window-list-window-preview { ++.window-list-workspace-indicator-window-preview { + background-color: #bebebe; + border: 1px solid #828282; + } + +-.window-list-window-preview.active { ++.window-list-workspace-indicator-window-preview.active { + background-color: #d4d4d4; + } + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index cdfe5b61..c24f159f 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -21,7 +21,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.44.0 + + +From c8bb217d2053cf8d7db5f4866f834b6d06250427 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/29] 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 101c89c6..ba1e05d7 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -25,11 +25,13 @@ const TOOLTIP_ANIMATION_TIME = 150; + + const MAX_THUMBNAILS = 6; + ++let baseStyleClassName = ''; ++ + const WindowPreview = GObject.registerClass( + class WindowPreview extends St.Button { + _init(window) { + super._init({ +- style_class: 'workspace-indicator-window-preview', ++ style_class: `${baseStyleClassName}-window-preview`, + }); + + this._delegate = this; +@@ -260,10 +262,15 @@ class WorkspaceThumbnail extends St.Button { + + var WorkspaceIndicator = GObject.registerClass( + class WorkspaceIndicator extends PanelMenu.Button { +- _init() { ++ _init(params = {}) { + super._init(0.5, _('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.44.0 + + +From fc27a7f0e6da8647380b5bbe901b27ec0e5aa728 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 07/29] 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 | 5 ++++- + extensions/window-list/workspaceIndicator.js | 15 ++++++++++++--- + 2 files changed, 16 insertions(+), 4 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 0c28692d..41a0c143 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -774,7 +774,10 @@ class WindowList extends St.Widget { + 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_child(this._workspaceIndicator.container); + + 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 c24f159f..1a1d15cd 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -17,11 +17,13 @@ const TOOLTIP_ANIMATION_TIME = 150; + + const MAX_THUMBNAILS = 6; + ++let baseStyleClassName = ''; ++ + let WindowPreview = GObject.registerClass( + 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 @@ class WorkspaceThumbnail extends St.Button { + + var WorkspaceIndicator = GObject.registerClass( + 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.remove_style_class_name('panel-button'); + this.menu.actor.remove_style_class_name('panel-menu'); + +-- +2.44.0 + + +From 3838a915d953b910aa2d9ec82cd22dcf2e0f7440 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 08/29] 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 | 22 ++++++++++++++++++-- + extensions/window-list/workspaceIndicator.js | 6 +----- + 2 files changed, 21 insertions(+), 7 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 41a0c143..c58df434 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -774,10 +774,9 @@ class WindowList extends St.Widget { + 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_child(this._workspaceIndicator.container); + + this._mutterSettings = new Gio.Settings({ schema_id: 'org.gnome.mutter' }); +@@ -1140,6 +1139,25 @@ class WindowList extends St.Widget { + } + }); + ++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() { + ExtensionUtils.initTranslations(); +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 1a1d15cd..4290d58a 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -251,7 +251,7 @@ class WorkspaceThumbnail extends St.Button { + var WorkspaceIndicator = GObject.registerClass( + class WorkspaceIndicator extends PanelMenu.Button { + _init(params = {}) { +- super._init(0.0, _('Workspace Indicator'), true); ++ super._init(0.0, _('Workspace Indicator')); + + const { + baseStyleClass = 'workspace-indicator', +@@ -260,10 +260,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + baseStyleClassName = baseStyleClass; + this.add_style_class_name(baseStyleClassName); + +- this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM)); +- this.remove_style_class_name('panel-button'); +- this.menu.actor.remove_style_class_name('panel-menu'); +- + let container = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + x_expand: true, +-- +2.44.0 + + +From 7d2abee5e5de19bba95e4fb66692e08ace67c972 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 09/29] 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 c58df434..a011bc90 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -789,7 +789,9 @@ class WindowList extends St.Widget { + 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, { + affectsStruts: true, +@@ -884,6 +886,11 @@ class WindowList extends St.Widget { + children[newActive].activate(); + } + ++ _onWorkspaceMenuSet() { ++ if (this._workspaceIndicator.menu) ++ this._menuManager.addMenu(this._workspaceIndicator.menu); ++ } ++ + _updatePosition() { + this.set_position( + this._monitor.x, +-- +2.44.0 + + +From bc71e5e7a21249868a481238193e1ca15eba5699 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 10/29] 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 ba1e05d7..60e084e2 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -17,9 +17,6 @@ const Main = imports.ui.main; + const PanelMenu = imports.ui.panelMenu; + const PopupMenu = imports.ui.popupMenu; + +-const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; +-const WORKSPACE_KEY = 'workspace-names'; +- + const TOOLTIP_OFFSET = 6; + const TOOLTIP_ANIMATION_TIME = 150; + +@@ -317,9 +314,9 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._updateThumbnails(); + this._updateThumbnailVisibility(); + +- 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.44.0 + + +From 66cf82e0676179a2565a75bbad1c31bb539c065c 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 11/29] 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 60e084e2..4f2188be 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -454,7 +454,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + return; + + +- let newIndex = global.workspace_manager.get_active_workspace_index() + diff; ++ const newIndex = this._currentWorkspace + diff; + this._activate(newIndex); + } + }); +-- +2.44.0 + + +From c51f0d790c7e4d575d770b63ad7c9632b4af79b1 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 12/29] 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 4f2188be..66b71cd6 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -296,8 +296,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + container.add_child(this._thumbnailsBox); + + this._workspacesItems = []; +- this._workspaceSection = new PopupMenu.PopupMenuSection(); +- this.menu.addMenuItem(this._workspaceSection); + + this._workspaceManagerSignals = [ + workspaceManager.connect_after('notify::n-workspaces', +@@ -310,7 +308,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + this.connect('scroll-event', this._onScrollEvent.bind(this)); + this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); +- this._createWorkspacesSection(); ++ this._updateMenu(); + this._updateThumbnails(); + this._updateThumbnailVisibility(); + +@@ -361,7 +359,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + + _nWorkspacesChanged() { +- this._createWorkspacesSection(); ++ this._updateMenu(); + this._updateThumbnails(); + this._updateThumbnailVisibility(); + } +@@ -397,17 +395,17 @@ class WorkspaceIndicator extends PanelMenu.Button { + 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.44.0 + + +From ef3a5860782e67dffcb63c705422102f68bd68ad 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 13/29] 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 66b71cd6..bc9e222b 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -227,16 +227,17 @@ class WorkspaceThumbnail extends St.Button { + }); + + 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.clamp( + 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.44.0 + + +From 3161d6c59fc8f7dd1b5c5f21082a5fd00cbcf5a9 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 14/29] 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 bc9e222b..ac270d64 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -309,6 +309,16 @@ class WorkspaceIndicator extends PanelMenu.Button { + + 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._updateThumbnailVisibility(); +@@ -328,7 +338,9 @@ class WorkspaceIndicator extends PanelMenu.Button { + 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(); + } +@@ -343,9 +355,16 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._statusLabel.visible = useMenu; + this._thumbnailsBox.visible = !useMenu; + ++ 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(useMenu ++ Main.panel.set_offscreen_redirect(this._thumbnailsBox.visible + ? Clutter.OffscreenRedirect.ALWAYS + : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY); + } +-- +2.44.0 + + +From 3afe55799368e1f899d2949bcc981718c6498623 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 15/29] 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 | 19 +++++++++---------- + 1 file changed, 9 insertions(+), 10 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index ac270d64..9b22102a 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -422,19 +422,18 @@ class WorkspaceIndicator extends PanelMenu.Button { + 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)); + +- this._workspacesItems[i].setOrnament(i === this._currentWorkspace ++ item.connect('activate', ++ () => this._activate(i)); ++ ++ item.setOrnament(i === this._currentWorkspace + ? PopupMenu.Ornament.DOT + : PopupMenu.Ornament.NO_DOT); ++ ++ this.menu.addMenuItem(item); ++ this._workspacesItems[i] = item; + } + + this._statusLabel.set_text(this._labelText()); +-- +2.44.0 + + +From 2eef4f6dd803021303be7c4f15775a41389debb3 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 16/29] 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 9b22102a..d8b29f58 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -283,7 +283,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._statusLabel = new St.Label({ + style_class: 'status-label', + y_align: Clutter.ActorAlign.CENTER, +- text: this._labelText(), ++ text: this._getStatusText(), + }); + + container.add_child(this._statusLabel); +@@ -375,7 +375,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._updateMenuOrnament(); + this._updateActiveThumbnail(); + +- this._statusLabel.set_text(this._labelText()); ++ this._statusLabel.set_text(this._getStatusText()); + } + + _nWorkspacesChanged() { +@@ -402,17 +402,16 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + } + +- _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() { +@@ -423,7 +422,8 @@ class WorkspaceIndicator extends PanelMenu.Button { + 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)); +@@ -436,7 +436,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._workspacesItems[i] = item; + } + +- this._statusLabel.set_text(this._labelText()); ++ this._statusLabel.set_text(this._getStatusText()); + } + + _updateThumbnails() { +-- +2.44.0 + + +From 8785f56bf69272e664c207856bfb7417342550c6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 21 Feb 2024 16:35:09 +0100 +Subject: [PATCH 17/29] workspace-indicator: Include n-workspaces in status + label + +The two extensions currently use a slightly different label +in menu mode: +The workspace indicator uses the plain workspace number ("2"), +while the window list includes the number of workspaces ("2 / 4"). + +The additional information seem useful, as well as the slightly +bigger click/touch target, so copy the window-list behavior. +--- + extensions/workspace-indicator/workspaceIndicator.js | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index d8b29f58..756758b3 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -403,8 +403,9 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + + _getStatusText() { ++ const {nWorkspaces} = global.workspace_manager; + const current = this._currentWorkspace + 1; +- return `${current}`; ++ return `${current} / ${nWorkspaces}`; + } + + _updateMenuLabels() { +-- +2.44.0 + + +From 6e483b472fa4b5a7be8f00b1ef87f28658f815aa 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 18/29] 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 | 15 ++++++++------- + 1 file changed, 8 insertions(+), 7 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css +index 4e12cce4..f74f7e88 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: #bebebe; + border: 1px solid #828282; ++ border-radius: 1px; + } + + .workspace-indicator-window-preview.active { +-- +2.44.0 + + +From cf06ac2f9b7e2412fc555721b9c6d34fea7e0b45 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 19/29] 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 | 30 +------------------ + 4 files changed, 60 insertions(+), 30 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 f74f7e88..b0f7d171 100644 +--- a/extensions/workspace-indicator/stylesheet.css ++++ b/extensions/workspace-indicator/stylesheet.css +@@ -1,29 +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.active { +- background-color: #d4d4d4; +-} ++@import url("stylesheet-dark.css"); +-- +2.44.0 + + +From a5fd131564600f3117f0dbd27a1bb82592ec1132 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 20/29] 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 | 447 ------------------- + 4 files changed, 31 insertions(+), 467 deletions(-) + delete mode 100644 extensions/window-list/workspaceIndicator.js + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index ab982b92..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 { +- border: 2px solid #f6f5f4; +- background-color: #ccc; +-} +- +-.window-list-workspace-indicator .workspace.active { +- border-color: #888; +-} +- +-.window-list-workspace-indicator-window-preview { +- background-color: #ededed; +- border: 1px solid #ccc; +-} +- +-.window-list-workspace-indicator-window-preview.active { +- background-color: #f6f5f4; +-} +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 a13f72d8..4ba47f07 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -1,3 +1,5 @@ ++@import url("stylesheet-workspace-switcher-dark.css"); ++ + .window-list { + spacing: 2px; + font-size: 10pt; +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +deleted file mode 100644 +index 4290d58a..00000000 +--- a/extensions/window-list/workspaceIndicator.js ++++ /dev/null +@@ -1,447 +0,0 @@ +-/* exported WorkspaceIndicator */ +-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 Me = ExtensionUtils.getCurrentExtension(); +- +-const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); +-const _ = Gettext.gettext; +- +-const TOOLTIP_OFFSET = 6; +-const TOOLTIP_ANIMATION_TIME = 150; +- +-const MAX_THUMBNAILS = 6; +- +-let baseStyleClassName = ''; +- +-let WindowPreview = GObject.registerClass( +-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.queue_relayout()); +- this._positionChangedId = this._window.connect('position-changed', +- () => { +- this._updateVisible(); +- this.queue_relayout(); +- }); +- this._minimizedChangedId = this._window.connect('notify::minimized', +- this._updateVisible.bind(this)); +- +- this._focusChangedId = global.display.connect('notify::focus-window', +- this._onFocusChanged.bind(this)); +- this._onFocusChanged(); +- } +- +- // needed for DND +- 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._focusChangedId); +- } +- +- _onFocusChanged() { +- if (global.display.focus_window === this._window) +- this.add_style_class_name('active'); +- else +- this.remove_style_class_name('active'); +- } +- +- _updateVisible() { +- const monitor = Main.layoutManager.findIndexForActor(this); +- const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); +- this.visible = this._window.get_frame_rect().overlap(workArea) && +- this._window.window_type !== Meta.WindowType.DESKTOP && +- 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) { +- 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) { +- const childBox = new Clutter.ActorBox(); +- const frameRect = child.metaWindow.get_frame_rect(); +- childBox.set_size( +- Math.round(Math.min(frameRect.width, workArea.width) * hscale), +- Math.round(Math.min(frameRect.height, workArea.height) * vscale)); +- childBox.set_origin( +- Math.round((frameRect.x - workArea.x) * hscale), +- Math.round((frameRect.y - workArea.y) * vscale)); +- child.allocate(childBox); +- } +- } +-}); +- +-let WorkspaceThumbnail = GObject.registerClass( +-class WorkspaceThumbnail extends St.Button { +- _init(index) { +- super._init({ +- style_class: 'workspace', +- child: new Clutter.Actor({ +- layout_manager: new WorkspaceLayout(), +- clip_to_allocation: 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.metaWindow) +- return false; +- +- this._moveWindow(source.metaWindow); +- return true; +- } +- +- handleDragOver(source) { +- if (source.metaWindow) +- 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); +- } +- +- 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.set({ +- text: Meta.prefs_get_workspace_name(this._index), +- visible: true, +- opacity: 0, +- }); +- +- 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.clamp( +- stageX + xOffset, +- monitor.x, +- monitor.x + monitor.width - tipWidth); +- const y = stageY - tipHeight - TOOLTIP_OFFSET; +- this._tooltip.set_position(x, y); +- } +- +- this._tooltip.ease({ +- opacity: this.hover ? 255 : 0, +- duration: TOOLTIP_ANIMATION_TIME, +- mode: Clutter.AnimationMode.EASE_OUT_QUAD, +- 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( +-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._updateThumbnailVisibility.bind(this)), +- ]; +- +- this.connect('scroll-event', this._onScrollEvent.bind(this)); +- this._updateMenu(); +- this._updateThumbnails(); +- this._updateThumbnailVisibility(); +- +- 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(); +- } +- +- _updateThumbnailVisibility() { +- const { workspaceManager } = global; +- const vertical = workspaceManager.layout_rows === -1; +- const useMenu = +- vertical || workspaceManager.n_workspaces > MAX_THUMBNAILS; +- this.reactive = useMenu; +- +- this._statusBin.visible = useMenu; +- this._thumbnailsBox.visible = !useMenu; +- } +- +- _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(); +- this._updateThumbnailVisibility(); +- } +- +- _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', () => { +- 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); +- } +-}); +- +-- +2.44.0 + + +From fbcf6cb317b58dc32c67952b54cec925adfbad34 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 20 Feb 2024 17:39:49 +0100 +Subject: [PATCH 21/29] workspace-indicator: Simplify scroll handling + +gnome-shell already includes a method for switching workspaces +via scroll events. Use that instead of implementing our own. +--- + .../workspace-indicator/workspaceIndicator.js | 21 ++++--------------- + 1 file changed, 4 insertions(+), 17 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 756758b3..8e3fec56 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -307,8 +307,10 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._updateThumbnailVisibility.bind(this)), + ]; + +- this.connect('scroll-event', this._onScrollEvent.bind(this)); +- this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); ++ this.connect('scroll-event', ++ (a, event) => Main.wm.handleWorkspaceScroll(event)); ++ this._thumbnailsBox.connect('scroll-event', ++ (a, event) => Main.wm.handleWorkspaceScroll(event)); + + this._inTopBar = false; + this.connect('notify::realized', () => { +@@ -460,19 +462,4 @@ class WorkspaceIndicator extends PanelMenu.Button { + 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.44.0 + + +From 346960098322af549c55a6eaf1628f1743585df1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 27 Feb 2024 21:20:45 +0100 +Subject: [PATCH 22/29] workspace-indicator: Handle active indication in + thumbnail + +Meta.Workspace has had an `active` property for a while now, so +we can use a property binding instead of tracking the active +workspace ourselves. +--- + .../workspace-indicator/workspaceIndicator.js | 38 ++++++++++++------- + 1 file changed, 24 insertions(+), 14 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 8e3fec56..01b831f7 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -116,8 +116,14 @@ class WorkspaceLayout extends Clutter.LayoutManager { + } + }); + +-const WorkspaceThumbnail = GObject.registerClass( +-class WorkspaceThumbnail extends St.Button { ++const WorkspaceThumbnail = GObject.registerClass({ ++ Properties: { ++ 'active': GObject.ParamSpec.boolean( ++ 'active', '', '', ++ GObject.ParamFlags.READWRITE, ++ false), ++ }, ++}, class WorkspaceThumbnail extends St.Button { + _init(index) { + super._init({ + style_class: 'workspace', +@@ -146,6 +152,10 @@ class WorkspaceThumbnail extends St.Button { + let workspaceManager = global.workspace_manager; + this._workspace = workspaceManager.get_workspace_by_index(index); + ++ this._workspace.bind_property('active', ++ this, 'active', ++ GObject.BindingFlags.SYNC_CREATE); ++ + this._windowAddedId = this._workspace.connect('window-added', + (ws, window) => this._addWindow(window)); + this._windowRemovedId = this._workspace.connect('window-removed', +@@ -158,6 +168,18 @@ class WorkspaceThumbnail extends St.Button { + this._onRestacked(); + } + ++ get active() { ++ return this.has_style_class_name('active'); ++ } ++ ++ set active(active) { ++ if (active) ++ this.add_style_class_name('active'); ++ else ++ this.remove_style_class_name('active'); ++ this.notify('active'); ++ } ++ + acceptDrop(source) { + if (!source.metaWindow) + return false; +@@ -375,7 +397,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); + + this._updateMenuOrnament(); +- this._updateActiveThumbnail(); + + this._statusLabel.set_text(this._getStatusText()); + } +@@ -394,16 +415,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + } + +- _updateActiveThumbnail() { +- let thumbs = this._thumbnailsBox.get_children(); +- for (let i = 0; i < thumbs.length; i++) { +- if (i === this._currentWorkspace) +- thumbs[i].add_style_class_name('active'); +- else +- thumbs[i].remove_style_class_name('active'); +- } +- } +- + _getStatusText() { + const {nWorkspaces} = global.workspace_manager; + const current = this._currentWorkspace + 1; +@@ -451,7 +462,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + let thumb = new WorkspaceThumbnail(i); + this._thumbnailsBox.add_child(thumb); + } +- this._updateActiveThumbnail(); + } + + _activate(index) { +-- +2.44.0 + + +From 76ec37876c295b9150e98c04e11189e464af7a94 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 23/29] 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 | 74 ++++++++++++------- + 1 file changed, 49 insertions(+), 25 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 01b831f7..a559b8e2 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -280,6 +280,51 @@ const WorkspaceThumbnail = GObject.registerClass({ + } + }); + ++const WorkspacePreviews = GObject.registerClass( ++class WorkspacePreviews extends Clutter.Actor { ++ _init(params) { ++ super._init({ ++ ...params, ++ layout_manager: new Clutter.BinLayout(), ++ reactive: true, ++ y_expand: true, ++ }); ++ ++ this.connect('scroll-event', ++ (a, event) => Main.wm.handleWorkspaceScroll(event)); ++ this.connect('destroy', () => this._onDestroy()); ++ ++ const {workspaceManager} = global; ++ ++ this._nWorkspacesChanged = ++ workspaceManager.connect_after('notify::n-workspaces', ++ () => this._updateThumbnails()); ++ ++ this._thumbnailsBox = new St.BoxLayout({ ++ style_class: 'workspaces-box', ++ y_expand: true, ++ }); ++ this.add_child(this._thumbnailsBox); ++ ++ this._updateThumbnails(); ++ } ++ ++ _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); ++ } ++ } ++ ++ _onDestroy() { ++ global.workspace_manager.disconnect(this._nWorkspacesChanged); ++ } ++}); ++ + var WorkspaceIndicator = GObject.registerClass( + class WorkspaceIndicator extends PanelMenu.Button { + _init(params = {}) { +@@ -307,16 +352,10 @@ class WorkspaceIndicator extends PanelMenu.Button { + y_align: Clutter.ActorAlign.CENTER, + text: this._getStatusText(), + }); +- + container.add_child(this._statusLabel); + +- this._thumbnailsBox = new St.BoxLayout({ +- style_class: 'workspaces-box', +- y_expand: true, +- reactive: true, +- }); +- +- container.add_child(this._thumbnailsBox); ++ this._thumbnails = new WorkspacePreviews(); ++ container.add_child(this._thumbnails); + + this._workspacesItems = []; + +@@ -331,8 +370,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + + this.connect('scroll-event', + (a, event) => Main.wm.handleWorkspaceScroll(event)); +- this._thumbnailsBox.connect('scroll-event', +- (a, event) => Main.wm.handleWorkspaceScroll(event)); + + this._inTopBar = false; + this.connect('notify::realized', () => { +@@ -344,7 +381,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + }); + + this._updateMenu(); +- this._updateThumbnails(); + this._updateThumbnailVisibility(); + + this._settings = new Gio.Settings({schema_id: 'org.gnome.desktop.wm.preferences'}); +@@ -377,7 +413,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + this.reactive = useMenu; + + this._statusLabel.visible = useMenu; +- this._thumbnailsBox.visible = !useMenu; ++ this._thumbnails.visible = !useMenu; + + this._updateTopBarRedirect(); + } +@@ -388,7 +424,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + // 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); + } +@@ -403,7 +439,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + + _nWorkspacesChanged() { + this._updateMenu(); +- this._updateThumbnails(); + this._updateThumbnailVisibility(); + } + +@@ -453,17 +488,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + 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_child(thumb); +- } +- } +- + _activate(index) { + let workspaceManager = global.workspace_manager; + +-- +2.44.0 + + +From 69c5eefbca44f58d10072115dd46ffada862cd48 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 24/29] 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 | 66 ++++++++++++++++++- + 2 files changed, 69 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 a559b8e2..17cf7c89 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -20,6 +20,8 @@ const PopupMenu = imports.ui.popupMenu; + const TOOLTIP_OFFSET = 6; + const TOOLTIP_ANIMATION_TIME = 150; + ++const SCROLL_TIME = 100; ++ + const MAX_THUMBNAILS = 6; + + let baseStyleClassName = ''; +@@ -299,12 +301,30 @@ class WorkspacePreviews extends Clutter.Actor { + this._nWorkspacesChanged = + workspaceManager.connect_after('notify::n-workspaces', + () => this._updateThumbnails()); ++ this._workspaceSwitchedId = ++ 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(); + } +@@ -318,6 +338,50 @@ class WorkspacePreviews extends Clutter.Actor { + const thumb = new WorkspaceThumbnail(i); + this._thumbnailsBox.add_child(thumb); + } ++ ++ if (this.mapped) ++ this._updateScrollPosition(); ++ } ++ ++ _updateScrollPosition() { ++ const adjustment = this._scrollView.hscroll.adjustment; ++ const {upper, pageSize} = adjustment; ++ let {value} = adjustment; ++ ++ const activeWorkspace = ++ [...this._thumbnailsBox].find(a => a.active); ++ ++ if (!activeWorkspace) ++ return; ++ ++ let offset = 0; ++ const hfade = this._scrollView.get_effect('fade'); ++ if (hfade) ++ offset = hfade.fade_margins.left; ++ ++ 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; ++ ++ adjustment.ease(value, { ++ mode: Clutter.AnimationMode.EASE_OUT_QUAD, ++ duration: SCROLL_TIME, ++ }); + } + + _onDestroy() { +-- +2.44.0 + + +From 609674b2763bfd1536e8854b79d3c1bf265f00b9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 3 Mar 2024 15:05:23 +0100 +Subject: [PATCH 25/29] workspace-indicator: Support labels in previews + +The space in the top bar is too limited to include the workspace +names. However we'll soon replace the textual menu with a preview +popover. We can use bigger previews there, so we can include the +names to not lose functionality with regards to the current menu. +--- + .../workspace-indicator/workspaceIndicator.js | 63 ++++++++++++++++--- + 1 file changed, 55 insertions(+), 8 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 17cf7c89..5e6d6300 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -124,10 +124,23 @@ const WorkspaceThumbnail = GObject.registerClass({ + 'active', '', '', + GObject.ParamFlags.READWRITE, + false), ++ 'show-label': GObject.ParamSpec.boolean( ++ 'show-label', '', '', ++ GObject.ParamFlags.READWRITE, ++ false), + }, + }, class WorkspaceThumbnail extends St.Button { + _init(index) { +- super._init({ ++ super._init(); ++ ++ 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(), +@@ -135,7 +148,15 @@ const WorkspaceThumbnail = GObject.registerClass({ + x_expand: true, + y_expand: true, + }), ++ y_expand: true, + }); ++ box.add_child(this._preview); ++ ++ this._label = new St.Label({ ++ x_align: Clutter.ActorAlign.CENTER, ++ text: Meta.prefs_get_workspace_name(index), ++ }); ++ box.add_child(this._label); + + this._tooltip = new St.Label({ + style_class: 'dash-label', +@@ -143,9 +164,20 @@ const WorkspaceThumbnail = GObject.registerClass({ + }); + Main.uiGroup.add_child(this._tooltip); + ++ this.bind_property('show-label', ++ this._label, 'visible', ++ GObject.BindingFlags.SYNC_CREATE); ++ + this.connect('destroy', this._onDestroy.bind(this)); + this.connect('notify::hover', this._syncTooltip.bind(this)); + ++ this._desktopSettings = ++ new Gio.Settings({schema_id: 'org.gnome.desktop.wm.preferences'}); ++ this._namesChangedId = ++ this._desktopSettings.connect('changed::workspace-names', () => { ++ this._label.text = Meta.prefs_get_workspace_name(index); ++ }); ++ + this._index = index; + this._delegate = this; // needed for DND + +@@ -171,14 +203,14 @@ const WorkspaceThumbnail = GObject.registerClass({ + } + + get active() { +- return this.has_style_class_name('active'); ++ return this._preview.has_style_class_name('active'); + } + + set active(active) { + if (active) +- this.add_style_class_name('active'); ++ this._preview.add_style_class_name('active'); + else +- this.remove_style_class_name('active'); ++ this._preview.remove_style_class_name('active'); + this.notify('active'); + } + +@@ -204,7 +236,7 @@ const 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) { +@@ -224,7 +256,7 @@ const 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; + } + } +@@ -243,6 +275,9 @@ const WorkspaceThumbnail = GObject.registerClass({ + } + + _syncTooltip() { ++ if (this.showLabel) ++ return; ++ + if (this.hover) { + this._tooltip.set({ + text: Meta.prefs_get_workspace_name(this._index), +@@ -279,11 +314,20 @@ const WorkspaceThumbnail = GObject.registerClass({ + this._workspace.disconnect(this._windowAddedId); + this._workspace.disconnect(this._windowRemovedId); + global.display.disconnect(this._restackedId); ++ ++ this._desktopSettings.disconnect(this._namesChangedId); ++ this._desktopSettings = null; + } + }); + +-const WorkspacePreviews = GObject.registerClass( +-class WorkspacePreviews extends Clutter.Actor { ++const WorkspacePreviews = GObject.registerClass({ ++ Properties: { ++ 'show-labels': GObject.ParamSpec.boolean( ++ 'show-labels', '', '', ++ GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, ++ false), ++ }, ++}, class WorkspacePreviews extends Clutter.Actor { + _init(params) { + super._init({ + ...params, +@@ -336,6 +380,9 @@ class WorkspacePreviews extends Clutter.Actor { + + for (let i = 0; i < nWorkspaces; i++) { + const thumb = new WorkspaceThumbnail(i); ++ this.bind_property('show-labels', ++ thumb, 'show-label', ++ GObject.BindingFlags.SYNC_CREATE); + this._thumbnailsBox.add_child(thumb); + } + +-- +2.44.0 + + +From 40cfe3a41fa3823fce06824b82e663b8f63f4e6d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 20 Feb 2024 21:43:55 +0100 +Subject: [PATCH 26/29] workspace-indicator: Stop handling vertical layouts + +Both the regular session and GNOME classic use a horizontal layout +nowadays, so it doesn't seem worth to specifically handle vertical +layouts anymore. + +The extension will still work when the layout is changed (by some +other extension), there will simply be a mismatch between horizontal +previews and the actual layout. +--- + extensions/workspace-indicator/workspaceIndicator.js | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 5e6d6300..aad2716a 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -475,8 +475,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._nWorkspacesChanged.bind(this)), + workspaceManager.connect_after('workspace-switched', + this._onWorkspaceSwitched.bind(this)), +- workspaceManager.connect('notify::layout-rows', +- this._updateThumbnailVisibility.bind(this)), + ]; + + this.connect('scroll-event', +@@ -518,9 +516,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + _updateThumbnailVisibility() { + const {workspaceManager} = global; +- const vertical = workspaceManager.layout_rows === -1; +- const useMenu = +- vertical || workspaceManager.n_workspaces > MAX_THUMBNAILS; ++ const useMenu = workspaceManager.n_workspaces > MAX_THUMBNAILS; + this.reactive = useMenu; + + this._statusLabel.visible = useMenu; +-- +2.44.0 + + +From 1cad76230f8f70a08a8ccbe0970641a7b6a717b5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 3 Mar 2024 15:05:23 +0100 +Subject: [PATCH 27/29] workspace-indicator: Also show previews in menu + +Since the regular session also switched to horizontal workspaces, +using a vertical menu has been a bit awkward. + +Now that our previews have become more flexible, we can use them +in the collapsed state as well as when embedded into the top bar. +--- + .../workspace-indicator/stylesheet-dark.css | 25 +++++- + .../workspace-indicator/workspaceIndicator.js | 79 +++---------------- + 2 files changed, 36 insertions(+), 68 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index 61d1e982..fb0e8b1a 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -6,18 +6,41 @@ + -st-hfade-offset: 20px; + } + ++.workspace-indicator-menu .workspaces-view { ++ max-width: 480px; ++} ++ + .workspace-indicator .workspaces-box { + padding: 5px; + spacing: 3px; + } + ++.workspace-indicator-menu .workspaces-box { ++ padding: 5px; ++ spacing: 6px; ++} ++ ++.workspace-indicator-menu .workspace-box { ++ spacing: 6px; ++} ++ ++.workspace-indicator-menu .workspace, + .workspace-indicator .workspace { +- width: 52px; + border: 2px solid transparent; + border-radius: 4px; + background-color: #3f3f3f; + } + ++.workspace-indicator .workspace { ++ width: 52px; ++} ++ ++.workspace-indicator-menu .workspace { ++ height: 80px; ++ width: 160px; ++} ++ ++.workspace-indicator-menu .workspace.active, + .workspace-indicator .workspace.active { + border-color: #9f9f9f; + } +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index aad2716a..7b3c6fbe 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -439,7 +439,7 @@ const WorkspacePreviews = GObject.registerClass({ + var WorkspaceIndicator = GObject.registerClass( + class WorkspaceIndicator extends PanelMenu.Button { + _init(params = {}) { +- super._init(0.5, _('Workspace Indicator')); ++ super(0.5, _('Workspace Indicator'), true); + + const { + baseStyleClass = 'workspace-indicator', +@@ -472,7 +472,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + this._workspaceManagerSignals = [ + workspaceManager.connect_after('notify::n-workspaces', +- this._nWorkspacesChanged.bind(this)), ++ this._updateThumbnailVisibility.bind(this)), + workspaceManager.connect_after('workspace-switched', + this._onWorkspaceSwitched.bind(this)), + ]; +@@ -489,24 +489,13 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._updateTopBarRedirect(); + }); + +- this._updateMenu(); + this._updateThumbnailVisibility(); +- +- 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 = i; i < this._workspaceManagerSignals.length; i++) + global.workspace_manager.disconnect(this._workspaceManagerSignals[i]); + +- if (this._settingsChangedId) { +- this._settings.disconnect(this._settingsChangedId); +- this._settingsChangedId = 0; +- } +- + if (this._inTopBar) + Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); + this._inTopBar = false; +@@ -522,6 +511,10 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._statusLabel.visible = useMenu; + this._thumbnails.visible = !useMenu; + ++ this.setMenu(useMenu ++ ? this._createPreviewMenu() ++ : null); ++ + this._updateTopBarRedirect(); + } + +@@ -538,69 +531,21 @@ class WorkspaceIndicator extends PanelMenu.Button { + + _onWorkspaceSwitched() { + this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); +- +- this._updateMenuOrnament(); +- + this._statusLabel.set_text(this._getStatusText()); + } + +- _nWorkspacesChanged() { +- this._updateMenu(); +- this._updateThumbnailVisibility(); +- } +- +- _updateMenuOrnament() { +- for (let i = 0; i < this._workspacesItems.length; i++) { +- this._workspacesItems[i].setOrnament(i === this._currentWorkspace +- ? PopupMenu.Ornament.DOT +- : PopupMenu.Ornament.NO_DOT); +- } +- } +- + _getStatusText() { + const {nWorkspaces} = global.workspace_manager; + const current = this._currentWorkspace + 1; + return `${current} / ${nWorkspaces}`; + } + +- _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(); ++ _createPreviewMenu() { ++ const menu = new PopupMenu.PopupMenu(this, 0.5, St.Side.TOP); + +- 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.NO_DOT); +- +- this.menu.addMenuItem(item); +- this._workspacesItems[i] = item; +- } +- +- this._statusLabel.set_text(this._getStatusText()); +- } +- +- _activate(index) { +- let workspaceManager = global.workspace_manager; +- +- if (index >= 0 && index < workspaceManager.n_workspaces) { +- let metaWorkspace = workspaceManager.get_workspace_by_index(index); +- metaWorkspace.activate(global.get_current_time()); +- } ++ const previews = new WorkspacePreviews({show_labels: true}); ++ menu.box.add_child(previews); ++ menu.actor.add_style_class_name(`${baseStyleClassName}-menu`); ++ return menu; + } + }); +-- +2.44.0 + + +From 20a2122ab7a435cb1a0840747a5d13be0d838a9e 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 28/29] 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 +--- + 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 | 30 +++++++++++++++++++ + ...extensions.workspace-indicator.gschema.xml | 15 ++++++++++ + .../workspace-indicator/workspaceIndicator.js | 14 ++++----- + po/POTFILES.in | 1 + + 8 files changed, 62 insertions(+), 8 deletions(-) + create mode 100644 extensions/workspace-indicator/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index a011bc90..688ca761 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -776,6 +776,7 @@ class WindowList extends St.Widget { + + this._workspaceIndicator = new BottomWorkspaceIndicator({ + baseStyleClass: 'window-list-workspace-indicator', ++ settings: ExtensionUtils.getSettings(), + }); + indicatorsBox.add_child(this._workspaceIndicator.container); + +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 299864cf..c5ab9fb1 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 +@@ -30,5 +30,9 @@ + only on the primary one. + + ++ ++ true ++ Show workspace previews in window list ++ + + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 5e1ed8e5..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 d307dcac..9809b46a 100644 +--- a/extensions/workspace-indicator/prefs.js ++++ b/extensions/workspace-indicator/prefs.js +@@ -13,6 +13,34 @@ 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.append(row); ++ ++ row.append(new Gtk.Label({ ++ label: _('Show Previews In Top Bar'), ++ })); ++ ++ const sw = new Gtk.Switch({ ++ hexpand: true, ++ halign: Gtk.Align.END, ++ }); ++ row.append(sw); ++ ++ const settings = ExtensionUtils.getSettings(); ++ ++ settings.bind('embed-previews', ++ sw, 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ } ++}); ++ + const WorkspaceSettingsWidget = GObject.registerClass( + class WorkspaceSettingsWidget extends Gtk.ScrolledWindow { + _init() { +@@ -31,6 +59,8 @@ class WorkspaceSettingsWidget extends Gtk.ScrolledWindow { + }); + this.set_child(box); + ++ box.append(new GeneralGroup()); ++ + box.append(new Gtk.Label({ + label: '%s'.format(_('Workspace Names')), + use_markup: true, +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..c7c634ca +--- /dev/null ++++ b/extensions/workspace-indicator/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml +@@ -0,0 +1,15 @@ ++ ++ ++ ++ ++ ++ true ++ Show workspace previews in top bar ++ ++ ++ +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 7b3c6fbe..9d566f41 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -22,8 +22,6 @@ const TOOLTIP_ANIMATION_TIME = 150; + + const SCROLL_TIME = 100; + +-const MAX_THUMBNAILS = 6; +- + let baseStyleClassName = ''; + + const WindowPreview = GObject.registerClass( +@@ -439,12 +437,15 @@ const WorkspacePreviews = GObject.registerClass({ + var WorkspaceIndicator = GObject.registerClass( + class WorkspaceIndicator extends PanelMenu.Button { + _init(params = {}) { +- super(0.5, _('Workspace Indicator'), true); ++ super._init(0.5, _('Workspace Indicator'), true); + + const { + baseStyleClass = 'workspace-indicator', ++ settings, + } = params; + ++ this._settings = settings; ++ + baseStyleClassName = baseStyleClass; + this.add_style_class_name(baseStyleClassName); + +@@ -471,8 +472,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._workspacesItems = []; + + this._workspaceManagerSignals = [ +- workspaceManager.connect_after('notify::n-workspaces', +- this._updateThumbnailVisibility.bind(this)), + workspaceManager.connect_after('workspace-switched', + this._onWorkspaceSwitched.bind(this)), + ]; +@@ -489,6 +488,8 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._updateTopBarRedirect(); + }); + ++ this._settings.connect('changed::embed-previews', ++ () => this._updateThumbnailVisibility()); + this._updateThumbnailVisibility(); + } + +@@ -504,8 +505,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + + _updateThumbnailVisibility() { +- const {workspaceManager} = global; +- const useMenu = workspaceManager.n_workspaces > MAX_THUMBNAILS; ++ const useMenu = !this._settings.get_boolean('embed-previews'); + this.reactive = useMenu; + + this._statusLabel.visible = useMenu; +diff --git a/po/POTFILES.in b/po/POTFILES.in +index bd39ab61..4d551780 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -18,4 +18,5 @@ extensions/window-list/prefs.js + extensions/window-list/workspaceIndicator.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.44.0 + + +From 597ed15f9e4a1fd6eeaaedaae59db30c4d379c3f 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 29/29] 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 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js +index e35990ff..79cd1355 100644 +--- a/extensions/window-list/prefs.js ++++ b/extensions/window-list/prefs.js +@@ -102,6 +102,12 @@ class WindowListPrefsWidget extends Gtk.Box { + }); + this._settings.bind('display-all-workspaces', check, 'active', Gio.SettingsBindFlags.DEFAULT); + this.append(check); ++ ++ check = new Gtk.CheckButton({ ++ label: _('Show workspace previews'), ++ }); ++ this._settings.bind('embed-previews', check, 'active', Gio.SettingsBindFlags.DEFAULT); ++ this.append(check); + } + }); + +-- +2.44.0 + diff --git a/SOURCES/prefer-window-icon.patch b/SOURCES/prefer-window-icon.patch new file mode 100644 index 0000000..1b543e3 --- /dev/null +++ b/SOURCES/prefer-window-icon.patch @@ -0,0 +1,70 @@ +From 34d8854677513b016a08a04cdd9973b165146215 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 19 Mar 2024 13:16:50 +0100 +Subject: [PATCH 1/2] window-list: Use more appropriate fallback icon + +'icon-missing' is not an actual icon name. It somewhat works +because an invalid icon name will fallback to the correct +'image-missing', however for apps the generic app icon is +a better fallback. +--- + extensions/window-list/extension.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 91ee3e6b..1f112548 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -188,7 +188,7 @@ class WindowTitle extends St.BoxLayout { + this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); + } else { + this._icon.child = new St.Icon({ +- icon_name: 'icon-missing', ++ icon_name: 'application-x-executable', + icon_size: ICON_TEXTURE_SIZE, + }); + } +-- +2.44.0 + + +From 032cfb3593651c8344a59828a9c674f148329889 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 19 Mar 2024 14:07:12 +0100 +Subject: [PATCH 2/2] window-list: Override with window icon if available + +--- + extensions/window-list/extension.js | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 1f112548..0c28692d 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -192,6 +192,23 @@ class WindowTitle extends St.BoxLayout { + icon_size: ICON_TEXTURE_SIZE, + }); + } ++ ++ // Override with window icon if available ++ if (this._hasWindowIcon()) { ++ const textureCache = St.TextureCache.get_default(); ++ this._icon.child.gicon = textureCache.bind_cairo_surface_property( ++ this._metaWindow, 'icon'); ++ } ++ } ++ ++ _hasWindowIcon() { ++ // HACK: GI cannot handle CairoSurface, so this ++ // will throw if the icon property is null ++ try { ++ return this._metaWindow.icon !== null; ++ } catch (e) { ++ return true; ++ } + } + + _onDestroy() { +-- +2.44.0 + diff --git a/SPECS/gnome-shell-extensions.spec b/SPECS/gnome-shell-extensions.spec index f6d1191..8cbadfe 100644 --- a/SPECS/gnome-shell-extensions.spec +++ b/SPECS/gnome-shell-extensions.spec @@ -7,7 +7,7 @@ Name: gnome-shell-extensions Version: 40.7 -Release: 12%{?dist}.inferit +Release: 15%{?dist}.inferit Summary: Modify and extend GNOME Shell functionality and behavior License: GPLv2+ @@ -45,6 +45,9 @@ Patch020: 0001-window-list-Explicitly-dispose-settings-on-destroy.patch Patch021: 0001-desktop-icons-Don-t-try-spawn-with-non-existent-work.patch Patch022: 0001-docking-Only-remove-spacer-if-necessary.patch Patch023: 0001-classification-banner-Hide-from-picks.patch +Patch024: prefer-window-icon.patch +Patch025: more-ws-previews.patch + # MSVSphere Patch1000: 0001-Updated-Russian-translation.patch @@ -453,9 +456,22 @@ install -m644 %{SOURCE2} %{buildroot}%{_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 +* Fri Apr 19 2024 Florian Müllner - 40.7-15 +- Fix downstream stylesheets + Resolves: RHEL-31885 + +* Thu Apr 18 2024 Florian Müllner - 40.7-14 +- Improve workspace previews + Resolves: RHEL-31885 + +* Tue Mar 19 2024 Florian Müllner - 40.7-13 +- Prefer window icons in window list + Resolves: RHEL-29659 + * Fri Feb 02 2024 Florian Müllner - 40.7-12 - Hide classification banners from picks Resolves: RHEL-22789