diff --git a/SOURCES/0001-Add-move-notifications-extension.patch b/SOURCES/0001-Add-move-notifications-extension.patch new file mode 100644 index 0000000..38dcc86 --- /dev/null +++ b/SOURCES/0001-Add-move-notifications-extension.patch @@ -0,0 +1,231 @@ +From 50c6c0c2137fded5f89be5bbee2292071e464cd2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 21 May 2024 19:01:30 +0200 +Subject: [PATCH] Add move-notifications extension + +--- + extensions/move-notifications/extension.js | 49 +++++++++++ + extensions/move-notifications/meson.build | 8 ++ + .../move-notifications/metadata.json.in | 10 +++ + ....extensions.move-notifications.gschema.xml | 14 +++ + extensions/move-notifications/prefs.js | 86 +++++++++++++++++++ + meson.build | 1 + + 6 files changed, 168 insertions(+) + create mode 100644 extensions/move-notifications/extension.js + create mode 100644 extensions/move-notifications/meson.build + create mode 100644 extensions/move-notifications/metadata.json.in + create mode 100644 extensions/move-notifications/org.gnome.shell.extensions.move-notifications.gschema.xml + create mode 100644 extensions/move-notifications/prefs.js + +diff --git a/extensions/move-notifications/extension.js b/extensions/move-notifications/extension.js +new file mode 100644 +index 00000000..0211696d +--- /dev/null ++++ b/extensions/move-notifications/extension.js +@@ -0,0 +1,49 @@ ++/* exported init */ ++const Clutter = imports.gi.Clutter; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Main = imports.ui.main; ++ ++class MoveNotificationsExtension { ++ enable() { ++ const updatePanel = Main.panel._updatePanel; ++ this._updatePanelOrig = updatePanel; ++ ++ Main.panel._updatePanel = () => { ++ updatePanel.call(Main.panel); ++ ++ Main.messageTray.bannerAlignment = this._getAlignment(); ++ }; ++ ++ this._settings = ExtensionUtils.getSettings(); ++ this._changedId = this._settings.connect('changed::position', ++ () => Main.panel._updatePanel()); ++ Main.panel._updatePanel(); ++ } ++ ++ disable() { ++ this._settings.disconnect(this._changedId); ++ this._settings = null; ++ ++ Main.panel._updatePanel = this._updatePanelOrig; ++ delete this._updatePanelOrig; ++ ++ Main.panel._updatePanel(); ++ } ++ ++ _getAlignment() { ++ switch (this._settings.get_string('position')) { ++ case 'top-left': ++ return Clutter.ActorAlign.START; ++ case 'top-right': ++ return Clutter.ActorAlign.END; ++ case 'top-center': ++ default: ++ return Clutter.ActorAlign.CENTER; ++ } ++ } ++} ++ ++function init() { ++ return new MoveNotificationsExtension(); ++} +diff --git a/extensions/move-notifications/meson.build b/extensions/move-notifications/meson.build +new file mode 100644 +index 00000000..c55a7830 +--- /dev/null ++++ b/extensions/move-notifications/meson.build +@@ -0,0 +1,8 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_sources += files('prefs.js') ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/move-notifications/metadata.json.in b/extensions/move-notifications/metadata.json.in +new file mode 100644 +index 00000000..cae9352c +--- /dev/null ++++ b/extensions/move-notifications/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++ "uuid": "@uuid@", ++ "extension-id": "@extension_id@", ++ "settings-schema": "@gschemaname@", ++ "gettext-domain": "@gettext_domain@", ++ "name": "Move notifications", ++ "description": "Move notification banners", ++ "shell-version": [ "@shell_current@" ], ++ "url": "@url@" ++} +diff --git a/extensions/move-notifications/org.gnome.shell.extensions.move-notifications.gschema.xml b/extensions/move-notifications/org.gnome.shell.extensions.move-notifications.gschema.xml +new file mode 100644 +index 00000000..a78d72bb +--- /dev/null ++++ b/extensions/move-notifications/org.gnome.shell.extensions.move-notifications.gschema.xml +@@ -0,0 +1,14 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ "top-right" ++ Notification position ++ ++ ++ +diff --git a/extensions/move-notifications/prefs.js b/extensions/move-notifications/prefs.js +new file mode 100644 +index 00000000..a3ecdacf +--- /dev/null ++++ b/extensions/move-notifications/prefs.js +@@ -0,0 +1,86 @@ ++// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- ++/* exported init buildPrefsWidget */ ++ ++const { Gio, GLib, GObject, Gtk } = imports.gi; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++ ++const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); ++const _ = Gettext.gettext; ++ ++/** */ ++function init() { ++ ExtensionUtils.initTranslations(); ++} ++ ++const MoveNotificationsPrefsWidget = GObject.registerClass( ++class MoveNotificationsPrefsWidget extends Gtk.Box { ++ _init() { ++ super._init({ ++ orientation: Gtk.Orientation.VERTICAL, ++ spacing: 6, ++ margin_top: 36, ++ margin_bottom: 36, ++ margin_start: 36, ++ margin_end: 36, ++ halign: Gtk.Align.CENTER, ++ }); ++ ++ this._actionGroup = new Gio.SimpleActionGroup(); ++ this.insert_action_group('move-notifications', this._actionGroup); ++ ++ this._settings = ExtensionUtils.getSettings(); ++ this._actionGroup.add_action( ++ this._settings.create_action('position')); ++ ++ const title = new Gtk.Label({ ++ label: _('Notification Position'), ++ halign: Gtk.Align.START, ++ }); ++ title.add_css_class('heading'); ++ this.append(title); ++ ++ const box = new Gtk.Box({ ++ orientation: Gtk.Orientation.VERTICAL, ++ spacing: 12, ++ margin_bottom: 12, ++ }); ++ this.append(box); ++ ++ const context = box.get_style_context(); ++ const cssProvider = new Gtk.CssProvider(); ++ cssProvider.load_from_data( ++ 'box { padding: 12px; }', -1); ++ ++ context.add_provider(cssProvider, ++ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); ++ context.add_class('boxed-list'); ++ context.add_class('view'); ++ ++ const positions = [ ++ { pos: 'top-right', label: _('Top–Right') }, ++ { pos: 'top-center', label: _('Top–Center') }, ++ { pos: 'top-left', label: _('Top–Left') }, ++ ]; ++ let group = null; ++ for (const { pos, label } of positions) { ++ const check = new Gtk.CheckButton({ ++ action_name: 'move-notifications.position', ++ action_target: new GLib.Variant('s', pos), ++ label, ++ group, ++ margin_end: 12, ++ }); ++ group = check; ++ box.append(check); ++ } ++ } ++}); ++ ++/** ++ * @returns {Gtk.Widget} - the prefs widget ++ */ ++function buildPrefsWidget() { ++ return new MoveNotificationsPrefsWidget(); ++} +diff --git a/meson.build b/meson.build +index 7e6ed3e8..0a31d2f6 100644 +--- a/meson.build ++++ b/meson.build +@@ -53,6 +53,7 @@ all_extensions += [ + 'dash-to-dock', + 'dash-to-panel', + 'gesture-inhibitor', ++ 'move-notifications', + 'native-window-placement', + 'panel-favorites', + 'systemMonitor', +-- +2.46.0 + diff --git a/SOURCES/0001-dash-to-panel-Remove-faulty-version-check.patch b/SOURCES/0001-dash-to-panel-Remove-faulty-version-check.patch new file mode 100644 index 0000000..ffe277e --- /dev/null +++ b/SOURCES/0001-dash-to-panel-Remove-faulty-version-check.patch @@ -0,0 +1,48 @@ +From 7ed5e50cc978b7fda34aaaf56e8bf4d499f4676d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 2 Dec 2024 17:34:58 +0100 +Subject: [PATCH] dash-to-panel: Remove faulty version check + +In a string comparison, '40.10' is *smaller* than '40.3', so the +overview ends up being monkey-patched for an older version. + +Unbreak the app grid by removing the check altogether, as we don't +have to care about older versions. +--- + extensions/dash-to-panel/overview.js | 20 ++++++++------------ + 1 file changed, 8 insertions(+), 12 deletions(-) + +diff --git a/extensions/dash-to-panel/overview.js b/extensions/dash-to-panel/overview.js +index 57600a5c..38f04c75 100644 +--- a/extensions/dash-to-panel/overview.js ++++ b/extensions/dash-to-panel/overview.js +@@ -581,18 +581,14 @@ var dtpOverview = Utils.defineClass({ + const workspaceAppGridBox = + this._cachedWorkspaceBoxes.get(OverviewControls.ControlsState.APP_GRID); + +- if (Config.PACKAGE_VERSION > '40.3') { +- const monitor = Main.layoutManager.findMonitorForActor(this._container); +- const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index); +- const workAreaBox = new Clutter.ActorBox(); +- +- workAreaBox.set_origin(startX, startY); +- workAreaBox.set_size(workArea.width, workArea.height); +- +- params = [workAreaBox, searchHeight, dashHeight, workspaceAppGridBox] +- } else { +- params = [box, startX, searchHeight, dashHeight, workspaceAppGridBox]; +- } ++ const monitor = Main.layoutManager.findMonitorForActor(this._container); ++ const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index); ++ const workAreaBox = new Clutter.ActorBox(); ++ ++ workAreaBox.set_origin(startX, startY); ++ workAreaBox.set_size(workArea.width, workArea.height); ++ ++ params = [workAreaBox, searchHeight, dashHeight, workspaceAppGridBox] + + let appDisplayBox; + if (!transitionParams.transitioning) { +-- +2.47.1 + diff --git a/SOURCES/0001-dash-to-panel-Stop-messing-with-overview-allocation.patch b/SOURCES/0001-dash-to-panel-Stop-messing-with-overview-allocation.patch new file mode 100644 index 0000000..42859ca --- /dev/null +++ b/SOURCES/0001-dash-to-panel-Stop-messing-with-overview-allocation.patch @@ -0,0 +1,261 @@ +From 21c6b13e951e342e4f1e1178afa28f1303b19f2a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 5 May 2025 15:31:12 +0200 +Subject: [PATCH] dash-to-panel: Stop messing with overview allocation + +See upstream commits facbdbd437a and e544d2c7a. +--- + extensions/dash-to-panel/overview.js | 225 --------------------------- + 1 file changed, 225 deletions(-) + +diff --git a/extensions/dash-to-panel/overview.js b/extensions/dash-to-panel/overview.js +index 38f04c75..9b0a8e71 100644 +--- a/extensions/dash-to-panel/overview.js ++++ b/extensions/dash-to-panel/overview.js +@@ -70,7 +70,6 @@ var dtpOverview = Utils.defineClass({ + this._optionalNumberOverlay(); + this._optionalClickToExit(); + this._toggleDash(); +- this._hookupAllocation(); + + this._signalsHandler.add([ + Me.settings, +@@ -81,10 +80,6 @@ var dtpOverview = Utils.defineClass({ + }, + + disable: function () { +- Utils.hookVfunc(Workspace.WorkspaceBackground.prototype, 'allocate', Workspace.WorkspaceBackground.prototype.vfunc_allocate); +- Utils.hookVfunc(OverviewControls.ControlsManagerLayout.prototype, 'allocate', OverviewControls.ControlsManagerLayout.prototype.vfunc_allocate); +- OverviewControls.ControlsManagerLayout.prototype._computeWorkspacesBoxForState = this._oldComputeWorkspacesBoxForState; +- + this._signalsHandler.destroy(); + this._injectionsHandler.destroy(); + +@@ -481,224 +476,4 @@ var dtpOverview = Utils.defineClass({ + ]); + return true; + }, +- +- _hookupAllocation: function() { +- Utils.hookVfunc(OverviewControls.ControlsManagerLayout.prototype, 'allocate', function vfunc_allocate(container, box) { +- const childBox = new Clutter.ActorBox(); +- +- const { spacing } = this; +- +- let startY = 0; +- let startX = 0; +- +- if (Me.settings.get_boolean('stockgs-keep-top-panel') && Main.layoutManager.panelBox.y === Main.layoutManager.primaryMonitor.y) { +- startY = Main.layoutManager.panelBox.height; +- box.y1 += startY; +- } +- +- const panel = global.dashToPanel.panels[0]; +- if(panel) { +- switch (panel.getPosition()) { +- case St.Side.TOP: +- startY = panel.panelBox.height; +- box.y1 += startY; +- break; +- case St.Side.LEFT: +- startX = panel.panelBox.width; +- box.x1 += startX; +- break; +- case St.Side.RIGHT: +- box.x2 -= panel.panelBox.width; +- break; +- +- } +- } +- +- +- const [width, height] = box.get_size(); +- let availableHeight = height; +- +- // Search entry +- let [searchHeight] = this._searchEntry.get_preferred_height(width); +- childBox.set_origin(startX, startY); +- childBox.set_size(width, searchHeight); +- this._searchEntry.allocate(childBox); +- +- availableHeight -= searchHeight + spacing; +- +- // Dash +- const maxDashHeight = Math.round(box.get_height() * DASH_MAX_HEIGHT_RATIO); +- this._dash.setMaxSize(width, maxDashHeight); +- +- let [, dashHeight] = this._dash.get_preferred_height(width); +- if (Me.settings.get_boolean('stockgs-keep-dash')) +- dashHeight = Math.min(dashHeight, maxDashHeight); +- else +- dashHeight = spacing*5; // todo: determine proper spacing for window labels on maximized windows on workspace display +- childBox.set_origin(startX, startY + height - dashHeight); +- childBox.set_size(width, dashHeight); +- this._dash.allocate(childBox); +- +- availableHeight -= dashHeight + spacing; +- +- // Workspace Thumbnails +- let thumbnailsHeight = 0; +- if (this._workspacesThumbnails.visible) { +- const { expandFraction } = this._workspacesThumbnails; +- [thumbnailsHeight] = +- this._workspacesThumbnails.get_preferred_height(width); +- thumbnailsHeight = Math.min( +- thumbnailsHeight * expandFraction, +- height * WorkspaceThumbnail.MAX_THUMBNAIL_SCALE); +- childBox.set_origin(startX, startY + searchHeight + spacing); +- childBox.set_size(width, thumbnailsHeight); +- this._workspacesThumbnails.allocate(childBox); +- } +- +- // Workspaces +- let params = [box, startX, startY, searchHeight, dashHeight, thumbnailsHeight]; +- const transitionParams = this._stateAdjustment.getStateTransitionParams(); +- +- // Update cached boxes +- for (const state of Object.values(OverviewControls.ControlsState)) { +- this._cachedWorkspaceBoxes.set( +- state, this._computeWorkspacesBoxForState(state, ...params)); +- } +- +- let workspacesBox; +- if (!transitionParams.transitioning) { +- workspacesBox = this._cachedWorkspaceBoxes.get(transitionParams.currentState); +- } else { +- const initialBox = this._cachedWorkspaceBoxes.get(transitionParams.initialState); +- const finalBox = this._cachedWorkspaceBoxes.get(transitionParams.finalState); +- workspacesBox = initialBox.interpolate(finalBox, transitionParams.progress); +- } +- +- this._workspacesDisplay.allocate(workspacesBox); +- +- // AppDisplay +- if (this._appDisplay.visible) { +- const workspaceAppGridBox = +- this._cachedWorkspaceBoxes.get(OverviewControls.ControlsState.APP_GRID); +- +- const monitor = Main.layoutManager.findMonitorForActor(this._container); +- const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index); +- const workAreaBox = new Clutter.ActorBox(); +- +- workAreaBox.set_origin(startX, startY); +- workAreaBox.set_size(workArea.width, workArea.height); +- +- params = [workAreaBox, searchHeight, dashHeight, workspaceAppGridBox] +- +- let appDisplayBox; +- if (!transitionParams.transitioning) { +- appDisplayBox = +- this._getAppDisplayBoxForState(transitionParams.currentState, ...params); +- } else { +- const initialBox = +- this._getAppDisplayBoxForState(transitionParams.initialState, ...params); +- const finalBox = +- this._getAppDisplayBoxForState(transitionParams.finalState, ...params); +- +- appDisplayBox = initialBox.interpolate(finalBox, transitionParams.progress); +- } +- +- this._appDisplay.allocate(appDisplayBox); +- } +- +- // Search +- childBox.set_origin(0, startY + searchHeight + spacing); +- childBox.set_size(width, availableHeight); +- +- this._searchController.allocate(childBox); +- +- this._runPostAllocation(); +- }); +- +- this._oldComputeWorkspacesBoxForState = OverviewControls.ControlsManagerLayout.prototype._computeWorkspacesBoxForState; +- OverviewControls.ControlsManagerLayout.prototype._computeWorkspacesBoxForState = function _computeWorkspacesBoxForState(state, box, startX, startY, searchHeight, dashHeight, thumbnailsHeight) { +- const workspaceBox = box.copy(); +- const [width, height] = workspaceBox.get_size(); +- const { spacing } = this; +- const { expandFraction } = this._workspacesThumbnails; +- +- switch (state) { +- case OverviewControls.ControlsState.HIDDEN: +- break; +- case OverviewControls.ControlsState.WINDOW_PICKER: +- workspaceBox.set_origin(startX, +- startY + searchHeight + spacing + +- thumbnailsHeight + spacing * expandFraction); +- workspaceBox.set_size(width, +- height - +- dashHeight - spacing - +- searchHeight - spacing - +- thumbnailsHeight - spacing * expandFraction); +- break; +- case OverviewControls.ControlsState.APP_GRID: +- workspaceBox.set_origin(startX, startY + searchHeight + spacing); +- workspaceBox.set_size( +- width, +- Math.round(height * SMALL_WORKSPACE_RATIO)); +- break; +- } +- +- return workspaceBox; +- } +- +- Utils.hookVfunc(Workspace.WorkspaceBackground.prototype, 'allocate', function vfunc_allocate(box) { +- const [width, height] = box.get_size(); +- const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage); +- const scaledHeight = height - (BACKGROUND_MARGIN * 2 * scaleFactor); +- const scaledWidth = (scaledHeight / height) * width; +- +- const scaledBox = box.copy(); +- scaledBox.set_origin( +- box.x1 + (width - scaledWidth) / 2, +- box.y1 + (height - scaledHeight) / 2); +- scaledBox.set_size(scaledWidth, scaledHeight); +- +- const progress = this._stateAdjustment.value; +- +- if (progress === 1) +- box = scaledBox; +- else if (progress !== 0) +- box = box.interpolate(scaledBox, progress); +- +- this.set_allocation(box); +- +- const themeNode = this.get_theme_node(); +- const contentBox = themeNode.get_content_box(box); +- +- this._bin.allocate(contentBox); +- +- +- const [contentWidth, contentHeight] = contentBox.get_size(); +- const monitor = Main.layoutManager.monitors[this._monitorIndex]; +- let xOff = (contentWidth / this._workarea.width) * +- (this._workarea.x - monitor.x); +- let yOff = (contentHeight / this._workarea.height) * +- (this._workarea.y - monitor.y); +- +- let startX = -xOff; +- let startY = -yOff; +- const panel = Utils.find(global.dashToPanel.panels, p => p.monitor.index == this._monitorIndex); +- switch (panel.getPosition()) { +- case St.Side.TOP: +- yOff += panel.panelBox.height; +- startY -= panel.panelBox.height; +- break; +- case St.Side.BOTTOM: +- yOff += panel.panelBox.height; +- break; +- case St.Side.RIGHT: +- xOff += panel.panelBox.width; +- break; +- } +- contentBox.set_origin(startX, startY); +- contentBox.set_size(xOff + contentWidth, yOff + contentHeight); +- this._backgroundGroup.allocate(contentBox); +- }); +- +- } + }); +-- +2.49.0 + diff --git a/SOURCES/0001-desktop-icons-Handle-touch-events.patch b/SOURCES/0001-desktop-icons-Handle-touch-events.patch new file mode 100644 index 0000000..ce3defc --- /dev/null +++ b/SOURCES/0001-desktop-icons-Handle-touch-events.patch @@ -0,0 +1,242 @@ +From a796215ddce14ebe80774b99e29d0d28109c818b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 6 Mar 2024 20:14:14 +0100 +Subject: [PATCH] desktop-icons: Handle touch events + +File icons currently only deal with button events. Split up the +current handlers and use them to handle touch events as well. +--- + extensions/desktop-icons/fileItem.js | 181 +++++++++++++++++++-------- + 1 file changed, 128 insertions(+), 53 deletions(-) + +diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js +index 37ee54db..26afddb2 100644 +--- a/extensions/desktop-icons/fileItem.js ++++ b/extensions/desktop-icons/fileItem.js +@@ -140,6 +140,7 @@ var FileItem = GObject.registerClass({ + this._container.connect('leave-event', (actor, event) => this._onLeave(actor, event)); + this._container.connect('enter-event', (actor, event) => this._onEnter(actor, event)); + this._container.connect('button-release-event', (actor, event) => this._onReleaseButton(actor, event)); ++ this._container.connect('touch-event', (actor, event) => this._onTouchEvent(actor, event)); + + /* Set the metadata and update relevant UI */ + this._updateMetadataFromFileInfo(fileInfo); +@@ -229,6 +230,10 @@ var FileItem = GObject.registerClass({ + if (this._iconAllocationIdleId) + GLib.source_remove(this._iconAllocationIdleId); + ++ if (this._longPressTimeoutId) ++ GLib.source_remove(this._longPressTimeoutId); ++ delete this._longPressTimeoutId; ++ + /* Menu */ + this._removeMenu(); + } +@@ -731,58 +736,141 @@ var FileItem = GObject.registerClass({ + } + + _updateClickState(event) { ++ const eventType = event.type(); ++ const isButton = ++ eventType === Clutter.EventType.BUTTON_PRESS || ++ eventType === Clutter.EventType.BUTTON_RELEASE; ++ const button = isButton ? event.get_button() : 0; ++ const time = event.get_time(); ++ + let settings = Clutter.Settings.get_default(); +- if ((event.get_button() == this._lastClickButton) && +- ((event.get_time() - this._lastClickTime) < settings.double_click_time)) ++ if (button === this._lastClickButton && ++ (time - this._lastClickTime) < settings.double_click_time) + this._clickCount++; + else + this._clickCount = 1; + +- this._lastClickTime = event.get_time(); +- this._lastClickButton = event.get_button(); ++ this._lastClickTime = time; ++ this._lastClickButton = button; + } + + _getClickCount() { + return this._clickCount; + } + ++ _handlePressEvent(event) { ++ const pressSequence = event.get_event_sequence(); ++ if (this._pressSequence && ++ pressSequence?.get_slot() !== this._pressSequence.get_slot()) ++ return Clutter.EVENT_PROPAGATE; ++ ++ this._primaryButtonPressed = true; ++ this._pressSequence = pressSequence; ++ this._pressDevice = event.get_device(); ++ ++ if (this._getClickCount() !== 1) ++ return Clutter.EVENT_STOP; ++ ++ const [x, y] = event.get_coords(); ++ this._buttonPressInitialX = x; ++ this._buttonPressInitialY = y; ++ ++ const shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK); ++ const controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK); ++ if (controlPressed || shiftPressed) ++ this.emit('selected', true, false, !this._isSelected); ++ else if (!this._isSelected) ++ this.emit('selected', false, false, true); ++ ++ return Clutter.EVENT_STOP; ++ } ++ ++ _handleSecondaryPress() { ++ if (!this.isSelected) ++ this.emit('selected', false, false, true); ++ this._ensureMenu().toggle(); ++ if (this._actionOpenWith) { ++ let allowOpenWith = Extension.desktopManager.getNumberOfSelectedItems() === 1; ++ this._actionOpenWith.setSensitive(allowOpenWith); ++ } ++ const specialFilesSelected = ++ Extension.desktopManager.checkIfSpecialFilesAreSelected(); ++ if (this._actionCut) ++ this._actionCut.setSensitive(!specialFilesSelected); ++ if (this._actionCopy) ++ this._actionCopy.setSensitive(!specialFilesSelected); ++ if (this._actionTrash) ++ this._actionTrash.setSensitive(!specialFilesSelected); ++ return Clutter.EVENT_STOP; ++ } ++ ++ _handleReleaseEvent(event) { ++ if (this._longPressTimeoutId) ++ GLib.source_remove(this._longPressTimeoutId); ++ delete this._longPressTimeoutId; ++ ++ if (!this._primaryButtonPressed || this._pressDevice !== event.get_device()) ++ return Clutter.EVENT_PROPAGATE; ++ ++ const pressSequence = event.get_event_sequence(); ++ if (this._pressSequence && ++ pressSequence?.get_slot() !== this._pressSequence.get_slot()) ++ return Clutter.EVENT_PROPAGATE; ++ ++ // primaryButtonPressed is TRUE only if the user has pressed the button ++ // over an icon, and if (s)he has not started a drag&drop operation ++ this._primaryButtonPressed = false; ++ delete this._pressDevice; ++ delete this._pressSequence; ++ ++ let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK); ++ let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK); ++ if (!controlPressed && !shiftPressed) ++ this.emit('selected', false, false, true); ++ if (this._getClickCount() === 1 && Prefs.CLICK_POLICY_SINGLE && !shiftPressed && !controlPressed) ++ this.doOpen(); ++ if (this._getClickCount() === 2 && !Prefs.CLICK_POLICY_SINGLE) ++ this.doOpen(); ++ return Clutter.EVENT_STOP; ++ } ++ ++ _onTouchEvent(actor, event) { ++ // on X11, let pointer emulation deal with touch ++ if (!Meta.is_wayland_compositor()) ++ return Clutter.EVENT_PROPAGATE; ++ ++ const type = event.type(); ++ if (type === Clutter.EventType.TOUCH_BEGIN) { ++ Extension.desktopManager.endRubberBand(); ++ this._updateClickState(event); ++ ++ if (!this._handlePressEvent(event)) ++ return Clutter.EVENT_PROPAGATE; ++ ++ const { longPressDuration } = Clutter.Settings.get_default(); ++ this._longPressTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ++ longPressDuration, ++ () => { ++ this._handleSecondaryPress(); ++ delete this._longPressTimeoutId; ++ return GLib.SOURCE_REMOVE; ++ }); ++ ++ return Clutter.EVENT_STOP; ++ } else if (type === Clutter.EventType.TOUCH_END) { ++ return this._handleReleaseEvent(event); ++ } ++ return Clutter.EVENT_PROPAGATE; ++ } ++ + _onPressButton(actor, event) { + Extension.desktopManager.endRubberBand(); + this._updateClickState(event); + let button = event.get_button(); +- if (button == 3) { +- if (!this.isSelected) +- this.emit('selected', false, false, true); +- this._ensureMenu().toggle(); +- if (this._actionOpenWith) { +- let allowOpenWith = (Extension.desktopManager.getNumberOfSelectedItems() == 1); +- this._actionOpenWith.setSensitive(allowOpenWith); +- } +- let specialFilesSelected = Extension.desktopManager.checkIfSpecialFilesAreSelected(); +- if (this._actionCut) +- this._actionCut.setSensitive(!specialFilesSelected); +- if (this._actionCopy) +- this._actionCopy.setSensitive(!specialFilesSelected); +- if (this._actionTrash) +- this._actionTrash.setSensitive(!specialFilesSelected); +- return Clutter.EVENT_STOP; +- } else if (button == 1) { +- if (this._getClickCount() == 1) { +- let [x, y] = event.get_coords(); +- this._primaryButtonPressed = true; +- this._buttonPressInitialX = x; +- this._buttonPressInitialY = y; +- let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK); +- let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK); +- if (controlPressed || shiftPressed) { +- this.emit('selected', true, false, !this._isSelected); +- } else { +- if (!this._isSelected) +- this.emit('selected', false, false, true); +- } +- } +- return Clutter.EVENT_STOP; +- } ++ if (button == 3) ++ return this._handleSecondaryPress(); ++ if (button == 1) ++ return this._handlePressEvent(event); + + return Clutter.EVENT_PROPAGATE; + } +@@ -821,22 +909,9 @@ var FileItem = GObject.registerClass({ + + _onReleaseButton(actor, event) { + let button = event.get_button(); +- if (button == 1) { +- // primaryButtonPressed is TRUE only if the user has pressed the button +- // over an icon, and if (s)he has not started a drag&drop operation +- if (this._primaryButtonPressed) { +- this._primaryButtonPressed = false; +- let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK); +- let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK); +- if (!controlPressed && !shiftPressed) +- this.emit('selected', false, false, true); +- if ((this._getClickCount() == 1) && Prefs.CLICK_POLICY_SINGLE && !shiftPressed && !controlPressed) +- this.doOpen(); +- return Clutter.EVENT_STOP; +- } +- if ((this._getClickCount() == 2) && (!Prefs.CLICK_POLICY_SINGLE)) +- this.doOpen(); +- } ++ if (button == 1) ++ return this._handleReleaseEvent(event); ++ + return Clutter.EVENT_PROPAGATE; + } + +-- +2.44.0 + diff --git a/SOURCES/0001-desktop-icons-Notify-icon-drags.patch b/SOURCES/0001-desktop-icons-Notify-icon-drags.patch new file mode 100644 index 0000000..7b3c1a5 --- /dev/null +++ b/SOURCES/0001-desktop-icons-Notify-icon-drags.patch @@ -0,0 +1,40 @@ +From 8389801814c84c797a29f986f15e7ea4dd27bccc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 6 Mar 2024 13:48:49 +0100 +Subject: [PATCH] desktop-icons: Notify icon drags + +Components like the message tray will use the signal to hide +their layout actor from picks, so that it does no interfere +with the DND operation. +--- + extensions/desktop-icons/desktopManager.js | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/extensions/desktop-icons/desktopManager.js b/extensions/desktop-icons/desktopManager.js +index 75b2a22a..c3b3f7e4 100644 +--- a/extensions/desktop-icons/desktopManager.js ++++ b/extensions/desktop-icons/desktopManager.js +@@ -547,17 +547,20 @@ var DesktopManager = GObject.registerClass({ + this._draggableContainer.allocate_preferred_size(0, 0); + + this._draggable.startDrag(x, y, global.get_current_time(), event.get_event_sequence()); ++ Main.overview.beginItemDrag(this._draggableContainer); + } + + _onDragCancelled() { + let event = Clutter.get_current_event(); + let [x, y] = event.get_coords(); + this._dragCancelled = true; ++ Main.overview.cancelledItemDrag(this._draggableContainer); + } + + _onDragEnd() { + this._inDrag = false; + Main.layoutManager.uiGroup.remove_child(this._draggableContainer); ++ Main.overview.endItemDrag(this._draggableContainer); + } + + _dragActorDropped(event) { +-- +2.44.0 + diff --git a/SOURCES/0001-workspace-indicator-Re-fittsify-workspace-previews.patch b/SOURCES/0001-workspace-indicator-Re-fittsify-workspace-previews.patch new file mode 100644 index 0000000..fc74f7c --- /dev/null +++ b/SOURCES/0001-workspace-indicator-Re-fittsify-workspace-previews.patch @@ -0,0 +1,51 @@ +From 64d4841a77293a45e769b868e1109b63811be7d1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 2 Jul 2024 19:04:10 +0200 +Subject: [PATCH] workspace-indicator: Re-fittsify workspace previews + +For the window-list extension, it is important that the workspace +previews extend to the bottom edge for easier click targets. + +That broke while merging the code with the workspace-indicator, +fix it again by moving the padding from the parent box into the +thumbnail children. +--- + .../workspace-indicator/stylesheet-dark.css | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index fb0e8b1a..017d844a 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -11,7 +11,6 @@ + } + + .workspace-indicator .workspaces-box { +- padding: 5px; + spacing: 3px; + } + +@@ -20,6 +19,20 @@ + spacing: 6px; + } + ++.workspace-indicator .workspace-box { ++ padding-top: 5px; ++ padding-bottom: 5px; ++} ++ ++.workspace-indicator StButton:first-child:ltr > .workspace-box, ++.workspace-indicator StButton:last-child:rtl > .workspace-box { ++ padding-left: 5px; ++} ++.workspace-indicator StButton:last-child:ltr > .workspace-box, ++.workspace-indicator StButton:first-child:rtl > .workspace-box { ++ padding-right: 5px; ++} ++ + .workspace-indicator-menu .workspace-box { + spacing: 6px; + } +-- +2.45.2 + diff --git a/SOURCES/improve-workspace-names.patch b/SOURCES/improve-workspace-names.patch new file mode 100644 index 0000000..b945f59 --- /dev/null +++ b/SOURCES/improve-workspace-names.patch @@ -0,0 +1,2402 @@ +From bef9d7ac2f78383208e35c9a54dbacb533053c6b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 17 Jun 2025 22:22:29 +0200 +Subject: [PATCH 01/19] Fix signal leak + +--- + extensions/workspace-indicator/workspaceIndicator.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 14359a0e..f70dcaf7 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -431,6 +431,7 @@ const WorkspacePreviews = GObject.registerClass({ + + _onDestroy() { + global.workspace_manager.disconnect(this._nWorkspacesChanged); ++ global.workspace_manager.disconnect(this._workspaceSwitchedId); + } + }); + +-- +2.51.1 + + +From 9156d8dbea7235e13e23302e5e0c0ae5a388961a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 11 Oct 2024 12:10:36 +0200 +Subject: [PATCH 02/19] workspace-indicator: Split out workspaces prefs page + +The window-list extension already uses the extension code for +its embedded workspace indicator, this will allow it to do the +same for the preference page. + +Part-of: +--- + extensions/workspace-indicator/meson.build | 2 +- + extensions/workspace-indicator/prefs.js | 238 +---------------- + .../workspace-indicator/workspacePrefs.js | 245 ++++++++++++++++++ + po/POTFILES.in | 2 +- + 4 files changed, 249 insertions(+), 238 deletions(-) + create mode 100644 extensions/workspace-indicator/workspacePrefs.js + +diff --git a/extensions/workspace-indicator/meson.build b/extensions/workspace-indicator/meson.build +index a88db78a..52ab5334 100644 +--- a/extensions/workspace-indicator/meson.build ++++ b/extensions/workspace-indicator/meson.build +@@ -10,4 +10,4 @@ extension_data += files( + ) + extension_schemas += files('schemas/' + metadata_conf.get('gschemaname') + '.gschema.xml') + +-extension_sources += files('prefs.js', 'workspaceIndicator.js') ++extension_sources += files('prefs.js', 'workspaceIndicator.js', 'workspacePrefs.js') +diff --git a/extensions/workspace-indicator/prefs.js b/extensions/workspace-indicator/prefs.js +index 9809b46a..2d90e198 100644 +--- a/extensions/workspace-indicator/prefs.js ++++ b/extensions/workspace-indicator/prefs.js +@@ -1,249 +1,15 @@ + // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- + /* exported init buildPrefsWidget */ + +-const { Gio, GLib, GObject, Gtk, Pango } = imports.gi; +- + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); + +-const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); +-const _ = Gettext.gettext; +-const N_ = e => e; +- +-const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; +-const WORKSPACE_KEY = 'workspace-names'; +- +-const GeneralGroup = GObject.registerClass( +-class GeneralGroup extends Gtk.Box { +- _init() { +- super._init({ +- orientation: Gtk.Orientation.VERTICAL, +- }); +- +- const row = new Gtk.Box(); +- this.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() { +- super._init({ +- hscrollbar_policy: Gtk.PolicyType.NEVER, +- }); +- +- const box = new Gtk.Box({ +- orientation: Gtk.Orientation.VERTICAL, +- halign: Gtk.Align.CENTER, +- spacing: 12, +- margin_top: 36, +- margin_bottom: 36, +- margin_start: 36, +- margin_end: 36, +- }); +- this.set_child(box); +- +- box.append(new GeneralGroup()); +- +- box.append(new Gtk.Label({ +- label: '%s'.format(_('Workspace Names')), +- use_markup: true, +- halign: Gtk.Align.START, +- })); +- +- this._list = new Gtk.ListBox({ +- selection_mode: Gtk.SelectionMode.NONE, +- valign: Gtk.Align.START, +- show_separators: true, +- }); +- this._list.connect('row-activated', (l, row) => row.edit()); +- box.append(this._list); +- +- const context = this._list.get_style_context(); +- const cssProvider = new Gtk.CssProvider(); +- cssProvider.load_from_data( +- 'list { min-width: 25em; }', -1); +- +- context.add_provider(cssProvider, +- Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); +- context.add_class('frame'); +- +- this._list.append(new NewWorkspaceRow()); +- +- this._actionGroup = new Gio.SimpleActionGroup(); +- this._list.insert_action_group('workspaces', this._actionGroup); +- +- let action; +- action = new Gio.SimpleAction({ name: 'add' }); +- action.connect('activate', () => { +- const names = this._settings.get_strv(WORKSPACE_KEY); +- this._settings.set_strv(WORKSPACE_KEY, [ +- ...names, +- _('Workspace %d').format(names.length + 1), +- ]); +- }); +- this._actionGroup.add_action(action); +- +- action = new Gio.SimpleAction({ +- name: 'remove', +- parameter_type: new GLib.VariantType('s'), +- }); +- action.connect('activate', (a, param) => { +- const removed = param.deepUnpack(); +- this._settings.set_strv(WORKSPACE_KEY, +- this._settings.get_strv(WORKSPACE_KEY) +- .filter(name => name !== removed)); +- }); +- this._actionGroup.add_action(action); +- +- action = new Gio.SimpleAction({ name: 'update' }); +- action.connect('activate', () => { +- const names = this._getWorkspaceRows().map(row => row.name); +- this._settings.set_strv(WORKSPACE_KEY, names); +- }); +- this._actionGroup.add_action(action); +- +- this._settings = new Gio.Settings({ +- schema_id: WORKSPACE_SCHEMA, +- }); +- this._settings.connect(`changed::${WORKSPACE_KEY}`, +- this._sync.bind(this)); +- this._sync(); +- } +- +- _getWorkspaceRows() { +- return [...this._list].filter(row => row.name); +- } +- +- _sync() { +- const rows = this._getWorkspaceRows(); +- +- const oldNames = rows.map(row => row.name); +- const newNames = this._settings.get_strv(WORKSPACE_KEY); +- +- const removed = oldNames.filter(n => !newNames.includes(n)); +- const added = newNames.filter(n => !oldNames.includes(n)); +- +- removed.forEach(n => this._list.remove(rows.find(r => r.name === n))); +- added.forEach(n => { +- this._list.insert(new WorkspaceRow(n), newNames.indexOf(n)); +- }); +- } +-}); +- +-const WorkspaceRow = GObject.registerClass( +-class WorkspaceRow extends Gtk.ListBoxRow { +- _init(name) { +- super._init({ name }); +- +- const controller = new Gtk.ShortcutController(); +- controller.add_shortcut(new Gtk.Shortcut({ +- trigger: Gtk.ShortcutTrigger.parse_string('Escape'), +- action: Gtk.CallbackAction.new(this._stopEdit.bind(this)), +- })); +- this.add_controller(controller); +- +- const box = new Gtk.Box({ +- spacing: 12, +- margin_top: 6, +- margin_bottom: 6, +- margin_start: 6, +- margin_end: 6, +- }); +- +- const label = new Gtk.Label({ +- hexpand: true, +- xalign: 0, +- max_width_chars: 25, +- ellipsize: Pango.EllipsizeMode.END, +- }); +- this.bind_property('name', label, 'label', +- GObject.BindingFlags.SYNC_CREATE); +- box.append(label); +- +- const button = new Gtk.Button({ +- action_name: 'workspaces.remove', +- action_target: new GLib.Variant('s', name), +- icon_name: 'edit-delete-symbolic', +- }); +- box.append(button); +- +- this._entry = new Gtk.Entry({ +- max_width_chars: 25, +- }); +- +- this._stack = new Gtk.Stack(); +- this._stack.add_named(box, 'display'); +- this._stack.add_named(this._entry, 'edit'); +- this.child = this._stack; +- +- this._entry.connect('activate', () => { +- this.name = this._entry.text; +- this._stopEdit(); +- }); +- this._entry.connect('notify::has-focus', () => { +- if (this._entry.has_focus) +- return; +- this._stopEdit(); +- }); +- +- this.connect('notify::name', () => { +- button.action_target = new GLib.Variant('s', this.name); +- this.activate_action('workspaces.update', null); +- }); +- } +- +- edit() { +- this._entry.text = this.name; +- this._entry.grab_focus(); +- this._stack.visible_child_name = 'edit'; +- } +- +- _stopEdit() { +- this.grab_focus(); +- this._stack.visible_child_name = 'display'; +- } +-}); +- +-const NewWorkspaceRow = GObject.registerClass( +-class NewWorkspaceRow extends Gtk.ListBoxRow { +- _init() { +- super._init({ +- action_name: 'workspaces.add', +- child: new Gtk.Image({ +- icon_name: 'list-add-symbolic', +- pixel_size: 16, +- margin_top: 12, +- margin_bottom: 12, +- margin_start: 12, +- margin_end: 12, +- }), +- }); +- this.update_property( +- [Gtk.AccessibleProperty.LABEL], [_('Add Workspace')]); +- } +-}); ++const { WorkspacesPage } = Me.imports.workspacePrefs; + + function init() { + ExtensionUtils.initTranslations(); + } + + function buildPrefsWidget() { +- return new WorkspaceSettingsWidget(); ++ return new WorkspacesPage(); + } +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +new file mode 100644 +index 00000000..5691996d +--- /dev/null ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -0,0 +1,245 @@ ++// SPDX-FileCopyrightText: 2012 Giovanni Campagna ++// SPDX-FileCopyrightText: 2014 Florian Müllner ++// ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++/* exported WorkspacesPage */ ++const { Gio, GLib, GObject, Gtk, Pango } = imports.gi; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++ ++const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); ++const _ = Gettext.gettext; ++const N_ = e => e; ++ ++const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; ++const WORKSPACE_KEY = 'workspace-names'; ++ ++const GeneralGroup = GObject.registerClass( ++class GeneralGroup extends Gtk.Box { ++ _init() { ++ super._init({ ++ orientation: Gtk.Orientation.VERTICAL, ++ }); ++ ++ const row = new Gtk.Box(); ++ this.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); ++ } ++}); ++ ++var WorkspacesPage = GObject.registerClass( ++class WorkspacesPage extends Gtk.ScrolledWindow { ++ _init() { ++ super._init({ ++ hscrollbar_policy: Gtk.PolicyType.NEVER, ++ vexpand: true, ++ }); ++ ++ const box = new Gtk.Box({ ++ orientation: Gtk.Orientation.VERTICAL, ++ halign: Gtk.Align.CENTER, ++ spacing: 12, ++ margin_top: 36, ++ margin_bottom: 36, ++ margin_start: 36, ++ margin_end: 36, ++ }); ++ this.set_child(box); ++ ++ box.append(new GeneralGroup()); ++ ++ box.append(new Gtk.Label({ ++ label: '%s'.format(_('Workspace Names')), ++ use_markup: true, ++ halign: Gtk.Align.START, ++ })); ++ ++ this._list = new Gtk.ListBox({ ++ selection_mode: Gtk.SelectionMode.NONE, ++ valign: Gtk.Align.START, ++ show_separators: true, ++ }); ++ this._list.connect('row-activated', (l, row) => row.edit()); ++ box.append(this._list); ++ ++ const context = this._list.get_style_context(); ++ const cssProvider = new Gtk.CssProvider(); ++ cssProvider.load_from_data( ++ 'list { min-width: 25em; }', -1); ++ ++ context.add_provider(cssProvider, ++ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); ++ context.add_class('frame'); ++ ++ this._list.append(new NewWorkspaceRow()); ++ ++ this._actionGroup = new Gio.SimpleActionGroup(); ++ this._list.insert_action_group('workspaces', this._actionGroup); ++ ++ let action; ++ action = new Gio.SimpleAction({ name: 'add' }); ++ action.connect('activate', () => { ++ const names = this._settings.get_strv(WORKSPACE_KEY); ++ this._settings.set_strv(WORKSPACE_KEY, [ ++ ...names, ++ _('Workspace %d').format(names.length + 1), ++ ]); ++ }); ++ this._actionGroup.add_action(action); ++ ++ action = new Gio.SimpleAction({ ++ name: 'remove', ++ parameter_type: new GLib.VariantType('s'), ++ }); ++ action.connect('activate', (a, param) => { ++ const removed = param.deepUnpack(); ++ this._settings.set_strv(WORKSPACE_KEY, ++ this._settings.get_strv(WORKSPACE_KEY) ++ .filter(name => name !== removed)); ++ }); ++ this._actionGroup.add_action(action); ++ ++ action = new Gio.SimpleAction({ name: 'update' }); ++ action.connect('activate', () => { ++ const names = this._getWorkspaceRows().map(row => row.name); ++ this._settings.set_strv(WORKSPACE_KEY, names); ++ }); ++ this._actionGroup.add_action(action); ++ ++ this._settings = new Gio.Settings({ ++ schema_id: WORKSPACE_SCHEMA, ++ }); ++ this._settings.connect(`changed::${WORKSPACE_KEY}`, ++ this._sync.bind(this)); ++ this._sync(); ++ } ++ ++ _getWorkspaceRows() { ++ return [...this._list].filter(row => row.name); ++ } ++ ++ _sync() { ++ const rows = this._getWorkspaceRows(); ++ ++ const oldNames = rows.map(row => row.name); ++ const newNames = this._settings.get_strv(WORKSPACE_KEY); ++ ++ const removed = oldNames.filter(n => !newNames.includes(n)); ++ const added = newNames.filter(n => !oldNames.includes(n)); ++ ++ removed.forEach(n => this._list.remove(rows.find(r => r.name === n))); ++ added.forEach(n => { ++ this._list.insert(new WorkspaceRow(n), newNames.indexOf(n)); ++ }); ++ } ++}); ++ ++const WorkspaceRow = GObject.registerClass( ++class WorkspaceRow extends Gtk.ListBoxRow { ++ _init(name) { ++ super._init({ name }); ++ ++ const controller = new Gtk.ShortcutController(); ++ controller.add_shortcut(new Gtk.Shortcut({ ++ trigger: Gtk.ShortcutTrigger.parse_string('Escape'), ++ action: Gtk.CallbackAction.new(this._stopEdit.bind(this)), ++ })); ++ this.add_controller(controller); ++ ++ const box = new Gtk.Box({ ++ spacing: 12, ++ margin_top: 6, ++ margin_bottom: 6, ++ margin_start: 6, ++ margin_end: 6, ++ }); ++ ++ const label = new Gtk.Label({ ++ hexpand: true, ++ xalign: 0, ++ max_width_chars: 25, ++ ellipsize: Pango.EllipsizeMode.END, ++ }); ++ this.bind_property('name', label, 'label', ++ GObject.BindingFlags.SYNC_CREATE); ++ box.append(label); ++ ++ const button = new Gtk.Button({ ++ action_name: 'workspaces.remove', ++ action_target: new GLib.Variant('s', name), ++ icon_name: 'edit-delete-symbolic', ++ }); ++ box.append(button); ++ ++ this._entry = new Gtk.Entry({ ++ max_width_chars: 25, ++ }); ++ ++ this._stack = new Gtk.Stack(); ++ this._stack.add_named(box, 'display'); ++ this._stack.add_named(this._entry, 'edit'); ++ this.child = this._stack; ++ ++ this._entry.connect('activate', () => { ++ this.name = this._entry.text; ++ this._stopEdit(); ++ }); ++ this._entry.connect('notify::has-focus', () => { ++ if (this._entry.has_focus) ++ return; ++ this._stopEdit(); ++ }); ++ ++ this.connect('notify::name', () => { ++ button.action_target = new GLib.Variant('s', this.name); ++ this.activate_action('workspaces.update', null); ++ }); ++ } ++ ++ edit() { ++ this._entry.text = this.name; ++ this._entry.grab_focus(); ++ this._stack.visible_child_name = 'edit'; ++ } ++ ++ _stopEdit() { ++ this.grab_focus(); ++ this._stack.visible_child_name = 'display'; ++ } ++}); ++ ++const NewWorkspaceRow = GObject.registerClass( ++class NewWorkspaceRow extends Gtk.ListBoxRow { ++ _init() { ++ super._init({ ++ action_name: 'workspaces.add', ++ child: new Gtk.Image({ ++ icon_name: 'list-add-symbolic', ++ pixel_size: 16, ++ margin_top: 12, ++ margin_bottom: 12, ++ margin_start: 12, ++ margin_end: 12, ++ }), ++ }); ++ this.update_property( ++ [Gtk.AccessibleProperty.LABEL], [_('Add Workspace')]); ++ } ++}); +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 4d551780..1fb8f17a 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -17,6 +17,6 @@ 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/prefs.js + extensions/workspace-indicator/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml + extensions/workspace-indicator/workspaceIndicator.js ++extensions/workspace-indicator/workspacePrefs.js +-- +2.51.1 + + +From 092dd50137078d9c39b8749866c721b32aa91249 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 11 Oct 2024 12:13:05 +0200 +Subject: [PATCH 03/19] workspace-indicator: Don't mention "top bar" in prefs + +The preferences will be shared with the window-list extension, +so avoid mentioning a specific placement. + +Part-of: +--- + extensions/workspace-indicator/workspacePrefs.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +index 5691996d..77c333f1 100644 +--- a/extensions/workspace-indicator/workspacePrefs.js ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -27,7 +27,7 @@ class GeneralGroup extends Gtk.Box { + this.append(row); + + row.append(new Gtk.Label({ +- label: _('Show Previews In Top Bar'), ++ label: _('Show Previews'), + })); + + const sw = new Gtk.Switch({ +-- +2.51.1 + + +From ef706734487e99ceb59707e7745d393d93d77e0f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 11 Oct 2024 12:45:54 +0200 +Subject: [PATCH 04/19] window-list: Remove workspace-previews setting from + prefs + +We are about to include the workspace prefs page from the +workspace-indicator extension, which already includes +the setting. + +Part-of: +--- + extensions/window-list/prefs.js | 6 ------ + 1 file changed, 6 deletions(-) + +diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js +index 79cd1355..e35990ff 100644 +--- a/extensions/window-list/prefs.js ++++ b/extensions/window-list/prefs.js +@@ -102,12 +102,6 @@ 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.51.1 + + +From 7b9a01c543b2f608d679d355e8bc610ce03b8183 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 29 Jun 2025 23:49:15 +0200 +Subject: [PATCH 05/19] window-list: Add workspaces page to prefs + +This brings back the workspace-previews setting, and adds the +ability to change the workspace names. + +Given that those names are used as tooltips or preview titles, +it makes sense to allow editing them from the extension prefs +rather than relying on external tools (like dconf-editor). + +Part-of: +--- + extensions/window-list/meson.build | 1 + + extensions/window-list/prefs.js | 28 ++++++++++++++++++++++++++-- + 2 files changed, 27 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build +index 12d2b174..c4485965 100644 +--- a/extensions/window-list/meson.build ++++ b/extensions/window-list/meson.build +@@ -30,6 +30,7 @@ workspaceIndicatorSources = [ + command: transform_stylesheet, + capture: true, + ), ++ files('../workspace-indicator/workspacePrefs.js'), + ] + + extension_sources += files('prefs.js', 'windowPicker.js') + workspaceIndicatorSources +diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js +index e35990ff..f1ce4bb7 100644 +--- a/extensions/window-list/prefs.js ++++ b/extensions/window-list/prefs.js +@@ -9,13 +9,14 @@ const Me = ExtensionUtils.getCurrentExtension(); + const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); + const _ = Gettext.gettext; + ++const { WorkspacesPage } = Me.imports.workspacePrefs; + + function init() { + ExtensionUtils.initTranslations(); + } + +-const WindowListPrefsWidget = GObject.registerClass( +-class WindowListPrefsWidget extends Gtk.Box { ++const WindowListPage = GObject.registerClass( ++class WindowListPage extends Gtk.Box { + _init() { + super._init({ + orientation: Gtk.Orientation.VERTICAL, +@@ -105,6 +106,29 @@ class WindowListPrefsWidget extends Gtk.Box { + } + }); + ++const WindowListPrefsWidget = GObject.registerClass( ++class WindowListPrefsWidget extends Gtk.Box { ++ _init() { ++ super._init({ ++ orientation: Gtk.Orientation.VERTICAL, ++ spacing: 6, ++ }); ++ ++ const stack = new Gtk.Stack(); ++ const stackSwitcher = new Gtk.StackSwitcher({ ++ stack, ++ margin_top: 12, ++ halign: Gtk.Align.CENTER, ++ }); ++ ++ this.append(stackSwitcher); ++ this.append(stack); ++ ++ stack.add_titled(new WindowListPage(), 'window-list', _('Window List')); ++ stack.add_titled(new WorkspacesPage(), 'workspaces', _('Workspaces')); ++ } ++}); ++ + function buildPrefsWidget() { + return new WindowListPrefsWidget(); + } +-- +2.51.1 + + +From 7e36fcf60395d6736c42109350bed132cb640bd8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 16:31:57 +0200 +Subject: [PATCH 06/19] workspace-indicator: Remove left-over variable + +Part-of: +--- + extensions/workspace-indicator/workspaceIndicator.js | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index f70dcaf7..5a5dba2f 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -470,8 +470,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._thumbnails = new WorkspacePreviews(); + container.add_child(this._thumbnails); + +- this._workspacesItems = []; +- + this._workspaceManagerSignals = [ + workspaceManager.connect_after('workspace-switched', + this._onWorkspaceSwitched.bind(this)), +-- +2.51.1 + + +From 69d632bcf963084673cff04643382a7181bd1c97 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 16:27:57 +0200 +Subject: [PATCH 07/19] workspace-indicator: Split out WorkspacesMenu + +The menu currently only contains the previews without any logic +on its own. This is about to change, so split the menu into a +separate class. + +Part-of: +--- + .../workspace-indicator/workspaceIndicator.js | 21 ++++++++++--------- + 1 file changed, 11 insertions(+), 10 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 5a5dba2f..7e6b9cce 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -435,6 +435,16 @@ const WorkspacePreviews = GObject.registerClass({ + } + }); + ++class WorkspacesMenu extends PopupMenu.PopupMenu { ++ constructor(sourceActor) { ++ super(sourceActor, 0.5, St.Side.TOP); ++ ++ const previews = new WorkspacePreviews({show_labels: true}); ++ this.box.add_child(previews); ++ this.actor.add_style_class_name(`${baseStyleClassName}-menu`); ++ } ++} ++ + var WorkspaceIndicator = GObject.registerClass( + class WorkspaceIndicator extends PanelMenu.Button { + _init(params = {}) { +@@ -511,7 +521,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._thumbnails.visible = !useMenu; + + this.setMenu(useMenu +- ? this._createPreviewMenu() ++ ? new WorkspacesMenu(this) + : null); + + this._updateTopBarRedirect(); +@@ -538,13 +548,4 @@ class WorkspaceIndicator extends PanelMenu.Button { + const current = this._currentWorkspace + 1; + return `${current} / ${nWorkspaces}`; + } +- +- _createPreviewMenu() { +- const menu = new PopupMenu.PopupMenu(this, 0.5, St.Side.TOP); +- +- const previews = new WorkspacePreviews({show_labels: true}); +- menu.box.add_child(previews); +- menu.actor.add_style_class_name(`${baseStyleClassName}-menu`); +- return menu; +- } + }); +-- +2.51.1 + + +From 8be09699a833df8528f6ec2f80a9792536436b8b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 18:53:20 +0200 +Subject: [PATCH 08/19] workspace-indicator: Add back plain workspaces menu + +Unlike in the top bar, the previews in the menu were not too +successful. Change back to a regular menu with a list of workspace +names. + +Part-of: +--- + .../workspace-indicator/stylesheet-dark.css | 20 ------ + .../workspace-indicator/workspaceIndicator.js | 72 ++++++++++++++++++- + 2 files changed, 70 insertions(+), 22 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index 872c6afc..d9bc65b1 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -6,19 +6,10 @@ + -st-hfade-offset: 20px; + } + +-.workspace-indicator-menu .workspaces-view { +- max-width: 480px; +-} +- + .workspace-indicator .workspaces-box { + spacing: 3px; + } + +-.workspace-indicator-menu .workspaces-box { +- padding: 5px; +- spacing: 6px; +-} +- + .workspace-indicator .workspace-box { + padding-top: 5px; + padding-bottom: 5px; +@@ -33,11 +24,6 @@ + padding-right: 5px; + } + +-.workspace-indicator-menu .workspace-box { +- spacing: 6px; +-} +- +-.workspace-indicator-menu .workspace, + .workspace-indicator .workspace { + border: 1px solid transparent; + border-radius: 4px; +@@ -48,12 +34,6 @@ + width: 52px; + } + +-.workspace-indicator-menu .workspace { +- height: 80px; +- width: 160px; +-} +- +-.workspace-indicator-menu .workspace.active, + .workspace-indicator .workspace.active { + border-color: #fff; + } +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 7e6b9cce..ebe92363 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -439,9 +439,77 @@ class WorkspacesMenu extends PopupMenu.PopupMenu { + constructor(sourceActor) { + super(sourceActor, 0.5, St.Side.TOP); + +- const previews = new WorkspacePreviews({show_labels: true}); +- this.box.add_child(previews); + this.actor.add_style_class_name(`${baseStyleClassName}-menu`); ++ this.actor.connect('destroy', () => this._onDestroy()); ++ ++ this._workspacesSection = new PopupMenu.PopupMenuSection(); ++ this.addMenuItem(this._workspacesSection); ++ ++ this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ ++ this.addAction(_('Settings'), () => ExtensionUtils.openPrefs()); ++ ++ this._desktopSettings = ++ new Gio.Settings({schema_id: 'org.gnome.desktop.wm.preferences'}); ++ this._workspaceNamesChangedId = ++ this._desktopSettings.connect('changed::workspace-names', ++ () => this._updateWorkspaceLabels()); ++ ++ const {workspaceManager} = global; ++ this._workspaceManagerSignals = [ ++ workspaceManager.connect('notify::n-workspaces', ++ () => this._updateWorkspaceItems()), ++ workspaceManager.connect('workspace-switched', ++ () => this._updateActiveIndicator()), ++ ]; ++ this._updateWorkspaceItems(); ++ } ++ ++ _updateWorkspaceItems() { ++ const {workspaceManager} = global; ++ const {nWorkspaces} = workspaceManager; ++ ++ const section = this._workspacesSection.actor; ++ while (section.get_n_children() < nWorkspaces) { ++ const item = new PopupMenu.PopupMenuItem(''); ++ item.connect('activate', (o, event) => { ++ const index = [...section].indexOf(item); ++ const workspace = workspaceManager.get_workspace_by_index(index); ++ workspace?.activate(event.get_time()); ++ }); ++ this._workspacesSection.addMenuItem(item); ++ } ++ ++ [...section].splice(nWorkspaces).forEach(item => item.destroy()); ++ ++ this._updateWorkspaceLabels(); ++ this._updateActiveIndicator(); ++ } ++ ++ _updateWorkspaceLabels() { ++ const items = [...this._workspacesSection.actor]; ++ items.forEach( ++ (item, i) => (item.label.text = Meta.prefs_get_workspace_name(i))); ++ } ++ ++ _updateActiveIndicator() { ++ const {workspaceManager} = global; ++ const active = workspaceManager.get_active_workspace_index(); ++ ++ const items = [...this._workspacesSection.actor]; ++ items.forEach((item, i) => { ++ item.setOrnament(i === active ++ ? PopupMenu.Ornament.CHECK ++ : PopupMenu.Ornament.NONE); ++ }); ++ } ++ ++ _onDestroy() { ++ for (const id of this._workspaceManagerSignals) ++ global.workspace_manager.disconnect(id); ++ ++ this._desktopSettings.disconnect(this._workspaceNamesChangedId); ++ this._desktopSettings = null; + } + } + +-- +2.51.1 + + +From 575bff3521e66f942bf4972edd51a0ad5ec8b43c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 19:00:23 +0200 +Subject: [PATCH 09/19] workspace-indicator: Remove preview labels + +Previews are no longer used in the menu, so they are never +shown with labels. + +Part-of: +--- + .../workspace-indicator/workspaceIndicator.js | 36 ++----------------- + 1 file changed, 2 insertions(+), 34 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index ebe92363..54912746 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -122,10 +122,6 @@ 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) { +@@ -150,32 +146,15 @@ const WorkspaceThumbnail = GObject.registerClass({ + }); + 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', + visible: false, + }); + 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 + +@@ -273,9 +252,6 @@ const WorkspaceThumbnail = GObject.registerClass({ + } + + _syncTooltip() { +- if (this.showLabel) +- return; +- + if (this.hover) { + this._tooltip.set({ + text: Meta.prefs_get_workspace_name(this._index), +@@ -312,9 +288,6 @@ 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; + } + }); + +@@ -376,13 +349,8 @@ const WorkspacePreviews = GObject.registerClass({ + + this._thumbnailsBox.destroy_all_children(); + +- 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); +- } ++ for (let i = 0; i < nWorkspaces; i++) ++ this._thumbnailsBox.add_child(new WorkspaceThumbnail(i)); + + if (this.mapped) + this._updateScrollPosition(); +-- +2.51.1 + + +From 5d61ee235756fd3139bfa31a5be2e5af2329fe6b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 28 May 2025 02:16:33 +0200 +Subject: [PATCH 10/19] workspace-indicator: Include menu with previews + +The menu is currently only used when previews are disabled. But +as we are going to use the menu for changing workspace names, it +should be always available. So add it unconditionally, and show +it on right-click when using previews. + +Part-of: +--- + .../workspace-indicator/stylesheet-dark.css | 5 ++++ + .../workspace-indicator/workspaceIndicator.js | 25 +++++++++++++------ + 2 files changed, 23 insertions(+), 7 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index d9bc65b1..1ca929f9 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -1,3 +1,8 @@ ++.workspace-indicator.previews:active { ++ background-color: none !important; ++ box-shadow: none !important; ++} ++ + .workspace-indicator .status-label { + padding: 0 8px; + } +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 54912746..ca4466cc 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -496,6 +496,8 @@ class WorkspaceIndicator extends PanelMenu.Button { + baseStyleClassName = baseStyleClass; + this.add_style_class_name(baseStyleClassName); + ++ this.setMenu(new WorkspacesMenu(this)); ++ + let container = new St.Widget({ + layout_manager: new Clutter.BinLayout(), + x_expand: true, +@@ -516,6 +518,14 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._thumbnails = new WorkspacePreviews(); + container.add_child(this._thumbnails); + ++ this._thumbnails.connect('button-press-event', (a, event) => { ++ if (event.get_button() !== Clutter.BUTTON_SECONDARY) ++ return Clutter.EVENT_PROPAGATE; ++ ++ this.menu.toggle(); ++ return Clutter.EVENT_STOP; ++ }); ++ + this._workspaceManagerSignals = [ + workspaceManager.connect_after('workspace-switched', + this._onWorkspaceSwitched.bind(this)), +@@ -550,15 +560,16 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + + _updateThumbnailVisibility() { +- const useMenu = !this._settings.get_boolean('embed-previews'); +- this.reactive = useMenu; ++ const usePreviews = this._settings.get_boolean('embed-previews'); ++ this.reactive = !usePreviews; + +- this._statusLabel.visible = useMenu; +- this._thumbnails.visible = !useMenu; ++ this._thumbnails.visible = usePreviews; ++ this._statusLabel.visible = !usePreviews; + +- this.setMenu(useMenu +- ? new WorkspacesMenu(this) +- : null); ++ if (usePreviews) ++ this.add_style_class_name('previews'); ++ else ++ this.remove_style_class_name('previews'); + + this._updateTopBarRedirect(); + } +-- +2.51.1 + + +From 5f9dd0ae1cb5c7f8acb97c4f7e68d21919727ce6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 29 May 2025 14:53:37 +0200 +Subject: [PATCH 11/19] workspace-indicator: Expose active workspace name on + menu + +When not using previews, we currently use a numerical presentation +like "1 / 4" for the top bar button. We will change that to use +the active workspace name instead. + +As the menu already has to track workspace switches and name changes, +expose the active workspace name there, so that the button doesn't +have to duplicate the tracking. + +Part-of: +--- + .../workspace-indicator/workspaceIndicator.js | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index ca4466cc..8e22e6b1 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -420,8 +420,10 @@ class WorkspacesMenu extends PopupMenu.PopupMenu { + this._desktopSettings = + new Gio.Settings({schema_id: 'org.gnome.desktop.wm.preferences'}); + this._workspaceNamesChangedId = +- this._desktopSettings.connect('changed::workspace-names', +- () => this._updateWorkspaceLabels()); ++ this._desktopSettings.connect('changed::workspace-names', () => { ++ this._updateWorkspaceLabels(); ++ this.emit('active-name-changed'); ++ }); + + const {workspaceManager} = global; + this._workspaceManagerSignals = [ +@@ -433,6 +435,12 @@ class WorkspacesMenu extends PopupMenu.PopupMenu { + this._updateWorkspaceItems(); + } + ++ get activeName() { ++ const {workspaceManager} = global; ++ const active = workspaceManager.get_active_workspace_index(); ++ return Meta.prefs_get_workspace_name(active); ++ } ++ + _updateWorkspaceItems() { + const {workspaceManager} = global; + const {nWorkspaces} = workspaceManager; +@@ -470,6 +478,7 @@ class WorkspacesMenu extends PopupMenu.PopupMenu { + ? PopupMenu.Ornament.CHECK + : PopupMenu.Ornament.NONE); + }); ++ this.emit('active-name-changed'); + } + + _onDestroy() { +-- +2.51.1 + + +From 1bd51d790447ac3329c8fcb399f472e2baaf17db Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 20:59:58 +0200 +Subject: [PATCH 12/19] workspace-indicator: Show full name when using menu + +With workspace names becoming a more prominent feature, it makes +sense to expose them without opening the menu. + +Part-of: +--- + .../workspace-indicator/stylesheet-dark.css | 8 ++++ + .../workspace-indicator/workspaceIndicator.js | 37 +++++++------------ + 2 files changed, 21 insertions(+), 24 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index 1ca929f9..d35a5dfc 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -4,8 +4,16 @@ + } + + .workspace-indicator .status-label { ++ width: 8em; + padding: 0 8px; + } ++.workspace-indicator .status-label:ltr { padding-right: 4px; } ++.workspace-indicator .status-label:rtl { padding-left: 4px; } ++ ++.workspace-indicator .system-status-icon { ++ padding: 0 !important; ++ margin: 0 !important; ++} + + .workspace-indicator .workspaces-view.hfade { + -st-hfade-offset: 20px; +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 8e22e6b1..d47fbd13 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -514,15 +514,23 @@ class WorkspaceIndicator extends PanelMenu.Button { + }); + this.add_child(container); + +- let workspaceManager = global.workspace_manager; ++ this._statusBox = new St.BoxLayout(); ++ container.add_child(this._statusBox); + +- this._currentWorkspace = workspaceManager.get_active_workspace_index(); + this._statusLabel = new St.Label({ + style_class: 'status-label', ++ x_expand: true, + y_align: Clutter.ActorAlign.CENTER, +- text: this._getStatusText(), ++ text: this.menu.activeName, + }); +- container.add_child(this._statusLabel); ++ this._statusBox.add_child(this._statusLabel); ++ this._statusBox.add_child(new St.Icon({ ++ icon_name: 'pan-down-symbolic', ++ style_class: 'system-status-icon', ++ })); ++ ++ this.menu.connect('active-name-changed', ++ () => this._statusLabel.set_text(this.menu.activeName)); + + this._thumbnails = new WorkspacePreviews(); + container.add_child(this._thumbnails); +@@ -535,11 +543,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + return Clutter.EVENT_STOP; + }); + +- this._workspaceManagerSignals = [ +- workspaceManager.connect_after('workspace-switched', +- this._onWorkspaceSwitched.bind(this)), +- ]; +- + this.connect('scroll-event', + (a, event) => Main.wm.handleWorkspaceScroll(event)); + +@@ -558,9 +561,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + + _onDestroy() { +- for (let i = 0; i < this._workspaceManagerSignals.length; i++) +- global.workspace_manager.disconnect(this._workspaceManagerSignals[i]); +- + if (this._inTopBar) + Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); + this._inTopBar = false; +@@ -573,7 +573,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + this.reactive = !usePreviews; + + this._thumbnails.visible = usePreviews; +- this._statusLabel.visible = !usePreviews; ++ this._statusBox.visible = !usePreviews; + + if (usePreviews) + this.add_style_class_name('previews'); +@@ -593,15 +593,4 @@ class WorkspaceIndicator extends PanelMenu.Button { + ? Clutter.OffscreenRedirect.ALWAYS + : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY); + } +- +- _onWorkspaceSwitched() { +- this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); +- this._statusLabel.set_text(this._getStatusText()); +- } +- +- _getStatusText() { +- const {nWorkspaces} = global.workspace_manager; +- const current = this._currentWorkspace + 1; +- return `${current} / ${nWorkspaces}`; +- } + }); +-- +2.51.1 + + +From 92cce4da58a748eb65980c604b340cf2b3d930d9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 9 Jun 2025 18:10:14 +0200 +Subject: [PATCH 13/19] workspace-indicator: Add background when using name + label + +Panel buttons are flat, so the name+arrow are not immediately +recognizable as a single control. Address this by adding a +background to the button when using the name label. + +Part-of: +--- + extensions/workspace-indicator/stylesheet-dark.css | 14 ++++++++++++++ + .../workspace-indicator/stylesheet-light.css | 14 ++++++++++++++ + .../workspace-indicator/workspaceIndicator.js | 7 +++++-- + 3 files changed, 33 insertions(+), 2 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index d35a5dfc..f1f9ec8f 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -3,6 +3,20 @@ + box-shadow: none !important; + } + ++.workspace-indicator.name-label { ++ box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.17) !important; ++} ++.workspace-indicator.name-label:hover, ++.workspace-indicator.name-label:focus { ++ box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.28) !important; ++} ++.workspace-indicator.name-label:active { ++ box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.32) !important; ++} ++.workspace-indicator.name-label:active:hover { ++ box-shadow: inset 0 0 0 100px rgba(255, 255, 255, 0.36) !important; ++} ++ + .workspace-indicator .status-label { + width: 8em; + padding: 0 8px; +diff --git a/extensions/workspace-indicator/stylesheet-light.css b/extensions/workspace-indicator/stylesheet-light.css +index 049b6a38..5191923c 100644 +--- a/extensions/workspace-indicator/stylesheet-light.css ++++ b/extensions/workspace-indicator/stylesheet-light.css +@@ -7,6 +7,20 @@ + + @import url("stylesheet-dark.css"); + ++.workspace-indicator.name-label { ++ box-shadow: inset 0 0 0 100px rgba(34, 34, 38, 0.17) !important; ++} ++.workspace-indicator.name-label:hover, ++.workspace-indicator.name-label:focus { ++ box-shadow: inset 0 0 0 100px rgba(34, 34, 38, 0.28) !important; ++} ++.workspace-indicator.name-label:active { ++ box-shadow: inset 0 0 0 100px rgba(34, 34, 38, 0.32) !important; ++} ++.workspace-indicator.name-label:active:hover { ++ box-shadow: inset 0 0 0 100px rgba(34, 34, 38, 0.36) !important; ++} ++ + .workspace-indicator .workspace { + background-color: #ccc; + } +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index d47fbd13..427aef68 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -575,10 +575,13 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._thumbnails.visible = usePreviews; + this._statusBox.visible = !usePreviews; + +- if (usePreviews) ++ if (usePreviews) { + this.add_style_class_name('previews'); +- else ++ this.remove_style_class_name('name-label'); ++ } else { + this.remove_style_class_name('previews'); ++ this.add_style_class_name('name-label'); ++ } + + this._updateTopBarRedirect(); + } +-- +2.51.1 + + +From c16736b3da4ee702ee32c844a3606e364e063dbf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 21:07:08 +0200 +Subject: [PATCH 14/19] workspace-indicator: Reimplement some libadwaita prefs + widgets + +Upstream now makes more use of libawaita, so reimplement the API we +need to make backporting a bit less painful. +--- + .../workspace-indicator/workspacePrefs.js | 241 ++++++++++++++++++ + 1 file changed, 241 insertions(+) + +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +index 77c333f1..9e2f25b9 100644 +--- a/extensions/workspace-indicator/workspacePrefs.js ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -16,6 +16,247 @@ const N_ = e => e; + const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; + const WORKSPACE_KEY = 'workspace-names'; + ++const PreferencesGroup = GObject.registerClass({ ++ Properties: { ++ 'title': GObject.ParamSpec.string( ++ 'title', '', '', ++ GObject.ParamFlags.READWRITE, ++ null), ++ }, ++}, class PreferencesGroup extends Gtk.Box { ++ _init(params) { ++ super._init({ ++ ...params, ++ orientation: Gtk.Orientation.VERTICAL, ++ }); ++ ++ const titleLabel = new Gtk.Label({ ++ halign: Gtk.Align.START, ++ }); ++ titleLabel.add_css_class('heading'); ++ ++ this.bind_property('title', ++ titleLabel, 'label', ++ GObject.BindingFlags.SYNC_CREATE); ++ this.append(titleLabel); ++ ++ this._list = new Gtk.ListBox({ ++ selection_mode: Gtk.SelectionMode.NONE, ++ valign: Gtk.Align.START, ++ show_separators: true, ++ }); ++ this._list.add_css_class('boxed-list'); ++ this.append(this._list); ++ ++ this._list.connect('row-activated', ++ (l, row) => row.activate()); ++ } ++ ++ get title() { ++ return this._title || ''; ++ } ++ ++ set title(title) { ++ if (this._title === title) ++ return; ++ ++ this._title = title; ++ this.notify('title'); ++ } ++ ++ add(child) { ++ this._list.append(child); ++ } ++ ++ remove(child) { ++ this._list.remove(child); ++ } ++}); ++ ++const ActionRow = GObject.registerClass({ ++ Properties: { ++ 'title': GObject.ParamSpec.string( ++ 'title', '', '', ++ GObject.ParamFlags.READWRITE, ++ null), ++ 'subtitle': GObject.ParamSpec.string( ++ 'subtitle', '', '', ++ GObject.ParamFlags.READWRITE, ++ null), ++ 'activatable-widget': GObject.ParamSpec.object( ++ 'activatable-widget', '', '', ++ GObject.ParamFlags.READWRITE, ++ Gtk.Widget), ++ }, ++}, class ActionRow extends Gtk.ListBoxRow { ++ _init(params) { ++ super._init(params); ++ ++ const provider = new Gtk.CssProvider(); ++ provider.load_from_data(`* { ++ border-spacing: 6px 6px; ++ min-height: 40px; ++ margin-left: 12px; ++ margin-right: 12px; ++ }`, -1); ++ ++ const mainBox = new Gtk.Box({ ++ valign: Gtk.Align.CENTER, ++ hexpand: false, ++ }); ++ mainBox.get_style_context().add_provider(provider, ++ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); ++ this.set_child(mainBox); ++ ++ this._prefixes = new Gtk.Box({ ++ visible: false, ++ }); ++ mainBox.append(this._prefixes); ++ ++ const titleBox = new Gtk.Box({ ++ orientation: Gtk.Orientation.VERTICAL, ++ valign: Gtk.Align.CENTER, ++ hexpand: true, ++ }); ++ mainBox.append(titleBox); ++ ++ this._suffixes = new Gtk.Box({ ++ visible: false, ++ }); ++ mainBox.append(this._suffixes); ++ ++ const titleLabel = new Gtk.Label({ ++ lines: 0, ++ wrap: true, ++ wrap_mode: Pango.WrapMode.WORD_CHAR, ++ xalign: 0, ++ }); ++ ++ this.bind_property('title', ++ titleLabel, 'label', ++ GObject.BindingFlags.SYNC_CREATE); ++ titleBox.append(titleLabel); ++ ++ const subtitleLabel = new Gtk.Label({ ++ ellipsize: Pango.EllipsizeMode.NONE, ++ lines: 0, ++ wrap: true, ++ wrap_mode: Pango.WrapMode.WORD_CHAR, ++ xalign: 0, ++ visible: this.subtitle.length > 0, ++ }); ++ subtitleLabel.add_css_class('dim-label'); ++ ++ this.bind_property('subtitle', ++ subtitleLabel, 'label', ++ GObject.BindingFlags.SYNC_CREATE); ++ titleBox.append(subtitleLabel); ++ } ++ ++ get title() { ++ return this._title || ''; ++ } ++ ++ set title(title) { ++ if (this._title === title) ++ return; ++ ++ this._title = title; ++ this.notify('title'); ++ } ++ ++ get subtitle() { ++ return this._subtitle || ''; ++ } ++ ++ set subtitle(subtitle) { ++ if (this._subtitle === subtitle) ++ return; ++ ++ this._subtitle = subtitle; ++ this.notify('subtitle'); ++ } ++ ++ get activitable_widget() { ++ return this._activatableWidget; ++ } ++ ++ set activatable_widget(widget) { ++ if (this._activatableWidget === widget) ++ return; ++ ++ this._activatableWidget = widget; ++ this.notify('activatable-widget'); ++ } ++ ++ add_prefix(child) { ++ this._prefixes.append(child); ++ this._prefixes.set_visible(true); ++ } ++ ++ add_suffix(child) { ++ this._suffixes.append(child); ++ this._suffixes.set_visible(true); ++ } ++ ++ activate() { ++ if (this._activatableWidget) ++ this._activatableWidget.mnemonic_activate(false); ++ } ++}); ++ ++const SpinRow = GObject.registerClass({ ++ Properties: { ++ 'adjustment': GObject.ParamSpec.object( ++ 'adjustment', '', '', ++ GObject.ParamFlags.READWRITE, ++ Gtk.Adjustment), ++ 'value': GObject.ParamSpec.double( ++ 'value', '', '', ++ GObject.ParamFlags.READWRITE, ++ -Infinity, Infinity, 0), ++ }, ++}, class SpinRow extends ActionRow { ++ _init(params) { ++ this._spinButton = new Gtk.SpinButton({ ++ valign: Gtk.Align.CENTER, ++ hexpand: true, ++ xalign: 1, ++ }); ++ super._init(params); ++ this.add_suffix(this._spinButton); ++ ++ this._spinButton.connect('notify::value', ++ () => this.notify('value')); ++ } ++ ++ get adjustment() { ++ return this._spinButton.get_adjustment(); ++ } ++ ++ set adjustment(adj) { ++ if (this.adjustment === adj) ++ return; ++ ++ this._spinButton.set_adjustment(adj); ++ this.notify('adjustment'); ++ } ++ ++ get value() { ++ return this._spinButton.get_value(); ++ } ++ ++ set value(value) { ++ const EPSILON = 0.005; ++ ++ if (Math.abs(this.value, value) < EPSILON) ++ return; ++ ++ this._spinButton.set_value(value); ++ this.notify('value'); ++ } ++}); ++ + const GeneralGroup = GObject.registerClass( + class GeneralGroup extends Gtk.Box { + _init() { +-- +2.51.1 + + +From 6b1f02d8c38e5a649ac886a26fcc57be6c2b0f08 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 29 Jun 2025 21:26:40 +0200 +Subject: [PATCH 15/19] workspace-indicator: Refine preview settings + +Add a group title, and change the single switch row to radio rows +to explicitly choose between "Previews" and "Workspace Name". + +Part-of: +--- + .../workspace-indicator/workspacePrefs.js | 35 ++++++++++++------- + 1 file changed, 22 insertions(+), 13 deletions(-) + +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +index 9e2f25b9..d8ad902f 100644 +--- a/extensions/workspace-indicator/workspacePrefs.js ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -258,29 +258,38 @@ const SpinRow = GObject.registerClass({ + }); + + const GeneralGroup = GObject.registerClass( +-class GeneralGroup extends Gtk.Box { ++class GeneralGroup extends PreferencesGroup { + _init() { + super._init({ +- orientation: Gtk.Orientation.VERTICAL, ++ title: _('Indicator'), + }); + +- const row = new Gtk.Box(); +- this.append(row); +- +- row.append(new Gtk.Label({ +- label: _('Show Previews'), +- })); ++ const previewCheck = new Gtk.CheckButton(); ++ const previewRow = new ActionRow({ ++ title: _('Previews'), ++ activatable_widget: previewCheck, ++ }); ++ previewRow.add_prefix(previewCheck); ++ this.add(previewRow); + +- const sw = new Gtk.Switch({ +- hexpand: true, +- halign: Gtk.Align.END, ++ const nameCheck = new Gtk.CheckButton({ ++ group: previewCheck, ++ }); ++ const nameRow = new ActionRow({ ++ title: _('Workspace Name'), ++ activatable_widget: nameCheck, + }); +- row.append(sw); ++ nameRow.add_prefix(nameCheck); ++ this.add(nameRow); + + const settings = ExtensionUtils.getSettings(); ++ if (settings.get_boolean('embed-previews')) ++ previewCheck.active = true; ++ else ++ nameCheck.active = true; + + settings.bind('embed-previews', +- sw, 'active', ++ previewCheck, 'active', + Gio.SettingsBindFlags.DEFAULT); + } + }); +-- +2.51.1 + + +From ed92b9fe869dc8a6b11e04278192024e88146c7e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 May 2025 16:20:05 +0200 +Subject: [PATCH 16/19] workspace-indicator: Include workspace settings + +While the "Multitasking" panel in Settings already exposes workspace +settings, it makes sense to expose them in our prefs dialog as well +where they are more in context. + +Part-of: +--- + .../workspace-indicator/workspacePrefs.js | 67 +++++++++++++++++++ + 1 file changed, 67 insertions(+) + +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +index d8ad902f..d037ef80 100644 +--- a/extensions/workspace-indicator/workspacePrefs.js ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -294,6 +294,72 @@ class GeneralGroup extends PreferencesGroup { + } + }); + ++const BehaviorGroup = GObject.registerClass( ++class BehaviorGroup extends PreferencesGroup { ++ _init() { ++ super._init({ ++ title: _('Behavior'), ++ }); ++ ++ const dynamicCheck = new Gtk.CheckButton(); ++ const dynamicRow = new ActionRow({ ++ title: _('Dynamic'), ++ subtitle: _('Automatically removes empty workspaces.'), ++ activatable_widget: dynamicCheck, ++ }); ++ dynamicRow.add_prefix(dynamicCheck); ++ this.add(dynamicRow); ++ ++ const fixedCheck = new Gtk.CheckButton({ ++ group: dynamicCheck, ++ }); ++ const fixedRow = new ActionRow({ ++ title: _('Fixed Number'), ++ subtitle: _('Specify a number of permanent workspaces.'), ++ activatable_widget: fixedCheck, ++ }); ++ fixedRow.add_prefix(fixedCheck); ++ this.add(fixedRow); ++ ++ const adjustment = new Gtk.Adjustment({ ++ lower: 1, ++ step_increment: 1, ++ value: 4, ++ upper: 36, // hard limit in mutter ++ }); ++ const numRow = new SpinRow({ ++ title: _('Number of Workspaces'), ++ adjustment, ++ }); ++ this.add(numRow); ++ ++ const mutterSettings = new Gio.Settings({ ++ schema_id: 'org.gnome.mutter', ++ }); ++ ++ if (mutterSettings.get_boolean('dynamic-workspaces')) ++ dynamicCheck.active = true; ++ else ++ fixedCheck.active = true; ++ ++ mutterSettings.bind('dynamic-workspaces', ++ dynamicCheck, 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ const desktopSettings = new Gio.Settings({ ++ schema_id: 'org.gnome.desktop.wm.preferences', ++ }); ++ ++ desktopSettings.bind('num-workspaces', ++ numRow, 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ fixedCheck.bind_property('active', ++ numRow, 'sensitive', ++ GObject.BindingFlags.SYNC_CREATE); ++ } ++}); ++ + var WorkspacesPage = GObject.registerClass( + class WorkspacesPage extends Gtk.ScrolledWindow { + _init() { +@@ -314,6 +380,7 @@ class WorkspacesPage extends Gtk.ScrolledWindow { + this.set_child(box); + + box.append(new GeneralGroup()); ++ box.append(new BehaviorGroup()); + + box.append(new Gtk.Label({ + label: '%s'.format(_('Workspace Names')), +-- +2.51.1 + + +From 3fabfb8e852d932129bbaf60646fcd73683e1966 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 28 May 2025 21:01:08 +0200 +Subject: [PATCH 17/19] workspace-indicator: Allow changing workspace names + from menu + +Instead of requiring the user to open the prefs dialog to change +workspace names, make the menu items themselves editable. + +Part-of: +--- + .../workspace-indicator/stylesheet-dark.css | 48 +++++++ + .../workspace-indicator/stylesheet-light.css | 16 +++ + .../workspace-indicator/workspaceIndicator.js | 130 +++++++++++++++++- + 3 files changed, 192 insertions(+), 2 deletions(-) + +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index f1f9ec8f..446c78de 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -74,3 +74,51 @@ + .workspace-indicator-window-preview.active { + background-color: #d4d4d4; + } ++ ++.workspace-indicator-menu { ++ min-width: 17em; ++} ++ ++.workspace-indicator-menu .editable-menu-item.popup-menu-item { ++ padding: 3px 12px; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button { ++ border-radius: 99px; ++ padding: 6px; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat { ++ background-color: transparent; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:hover { ++ background-color: rgba(255, 255, 255, 0.1); ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:active { ++ background-color: rgba(255, 255, 255, 0.15); ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:checked { ++ color: white; ++ background-color: #3584e4; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:checked:hover { ++ background-color: #629fea; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:checked:active { ++ background-color: #78aded; ++} ++ ++.workspace-indicator-menu .editable-menu-item .label { ++ padding: 0 11px; ++ width: 6.5em; ++} ++ ++.workspace-indicator-menu .editable-menu-item .entry { ++ padding: 9px 9px; ++ width: 6.5em; ++} +diff --git a/extensions/workspace-indicator/stylesheet-light.css b/extensions/workspace-indicator/stylesheet-light.css +index 5191923c..e4f2b45a 100644 +--- a/extensions/workspace-indicator/stylesheet-light.css ++++ b/extensions/workspace-indicator/stylesheet-light.css +@@ -37,3 +37,19 @@ + .workspace-indicator-window-preview.active { + background-color: #f6f5f4; + } ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:hover { ++ background-color: rgba(0, 0, 0, 0.1); ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:active { ++ background-color: rgba(0, 0, 0, 0.15); ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:checked:hover { ++ background-color: #1b6acb; ++} ++ ++.workspace-indicator-menu .editable-menu-item .icon-button.flat:checked:active { ++ background-color: #185fb4; ++} +diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js +index 427aef68..43315280 100644 +--- a/extensions/workspace-indicator/workspaceIndicator.js ++++ b/extensions/workspace-indicator/workspaceIndicator.js +@@ -4,7 +4,7 @@ + // + // SPDX-License-Identifier: GPL-2.0-or-later + +-const { Clutter, Gio, GObject, Meta, St } = imports.gi; ++const { Clutter, Gio, GObject, Meta, Shell, St } = imports.gi; + + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); +@@ -403,6 +403,125 @@ const WorkspacePreviews = GObject.registerClass({ + } + }); + ++const EditableMenuItem = GObject.registerClass({ ++ Signals: { ++ 'edited': {}, ++ }, ++}, class EditableMenuItem extends PopupMenu.PopupBaseMenuItem { ++ _init() { ++ super._init({ ++ style_class: 'editable-menu-item', ++ }); ++ this.get_accessible()?.set_description(0, ++ _('Press %s to edit').format('e')); ++ ++ this._ornamentLabel.y_align = Clutter.ActorAlign.CENTER; ++ ++ const stack = new Shell.Stack({ ++ x_expand: true, ++ x_align: Clutter.ActorAlign.START, ++ }); ++ this.add_child(stack); ++ ++ this.label = new St.Label({ ++ style_class: 'label', ++ y_align: Clutter.ActorAlign.CENTER, ++ }); ++ stack.add_child(this.label); ++ this.label_actor = this.label; ++ ++ this._entry = new St.Entry({ ++ style_class: 'entry', ++ opacity: 0, ++ reactive: false, ++ }); ++ stack.add_child(this._entry); ++ ++ this.label.bind_property('text', ++ this._entry, 'text', ++ GObject.BindingFlags.DEFAULT); ++ ++ this._entry.clutter_text.connect('activate', ++ () => this._stopEditing()); ++ ++ this._editButton = new St.Button({ ++ style_class: 'icon-button flat', ++ child: new St.Icon({ ++ icon_name: 'document-edit-symbolic', ++ icon_size: 16, ++ }), ++ button_mask: St.ButtonMask.ONE, ++ toggle_mode: true, ++ x_align: Clutter.ActorAlign.END, ++ y_align: Clutter.ActorAlign.CENTER, ++ }); ++ this.add_child(this._editButton); ++ ++ this._editButton.connect('notify::checked', () => { ++ if (this._editButton.checked) { ++ this._editButton.child.icon_name = 'object-select-symbolic'; ++ this._startEditing(); ++ } else { ++ this._editButton.child.icon_name = 'document-edit-symbolic'; ++ this._stopEditing(); ++ } ++ }); ++ this.connect('key-release-event', (o, event) => { ++ if (event.get_key_symbol() === Clutter.KEY_e) ++ this._editButton.checked = true; ++ }); ++ ++ this._keyFocusId = global.stage.connect('notify::key-focus', () => { ++ const {keyFocus} = global.stage; ++ if (!keyFocus || !this.contains(keyFocus)) ++ this._stopEditing(); ++ }); ++ ++ this.connect('destroy', () => { ++ global.stage.disconnect(this._keyFocusId); ++ delete this._keyFocusId; ++ }); ++ } ++ ++ _switchActor(from, to) { ++ to.reactive = true; ++ to.ease({ ++ opacity: 255, ++ duration: 300, ++ mode: Clutter.AnimationMode.EASE_OUT_QUAD, ++ }); ++ ++ from.ease({ ++ opacity: 0, ++ duration: 300, ++ mode: Clutter.AnimationMode.EASE_OUT_QUAD, ++ onComplete: () => { ++ from.reactive = false; ++ }, ++ }); ++ } ++ ++ _startEditing() { ++ this._switchActor(this.label, this._entry); ++ ++ this._entry.clutter_text.set_selection(0, -1); ++ this._entry.clutter_text.grab_key_focus(); ++ } ++ ++ _stopEditing() { ++ if (this.label.text !== this._entry.text) { ++ this.label.text = this._entry.text; ++ this.emit('edited'); ++ } ++ ++ if (this._editButton.checked) ++ this._editButton.checked = false; ++ ++ this._switchActor(this._entry, this.label); ++ this.navigate_focus(this, St.DirectionType.TAB_FORWARD, false); ++ } ++}); ++ + class WorkspacesMenu extends PopupMenu.PopupMenu { + constructor(sourceActor) { + super(sourceActor, 0.5, St.Side.TOP); +@@ -447,12 +566,19 @@ class WorkspacesMenu extends PopupMenu.PopupMenu { + + const section = this._workspacesSection.actor; + while (section.get_n_children() < nWorkspaces) { +- const item = new PopupMenu.PopupMenuItem(''); ++ const item = new EditableMenuItem(); + item.connect('activate', (o, event) => { + const index = [...section].indexOf(item); + const workspace = workspaceManager.get_workspace_by_index(index); + workspace?.activate(event.get_time()); + }); ++ item.connect('edited', () => { ++ const nLabels = section.get_n_children(); ++ const oldNames = this._desktopSettings.get_strv('workspace-names'); ++ const newNames = [...section].map(c => c.label.text); ++ this._desktopSettings.set_strv('workspace-names', ++ [...newNames, ...oldNames.slice(nLabels)]); ++ }); + this._workspacesSection.addMenuItem(item); + } + +-- +2.51.1 + + +From 976529e158e43599c35de4ac18832fef332b33b7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 28 May 2025 21:04:36 +0200 +Subject: [PATCH 18/19] workspace-indicator: Remove workspace names from prefs + +Now that names can be changed from the extension itself, we no +longer need to expose them in the prefs dialog. + +Part-of: +--- + .../workspace-indicator/workspacePrefs.js | 183 +----------------- + 1 file changed, 1 insertion(+), 182 deletions(-) + +diff --git a/extensions/workspace-indicator/workspacePrefs.js b/extensions/workspace-indicator/workspacePrefs.js +index d037ef80..3fd932c0 100644 +--- a/extensions/workspace-indicator/workspacePrefs.js ++++ b/extensions/workspace-indicator/workspacePrefs.js +@@ -4,17 +4,13 @@ + // SPDX-License-Identifier: GPL-2.0-or-later + + /* exported WorkspacesPage */ +-const { Gio, GLib, GObject, Gtk, Pango } = imports.gi; ++const { Gio, GObject, Gtk, Pango } = imports.gi; + + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); + + const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); + const _ = Gettext.gettext; +-const N_ = e => e; +- +-const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; +-const WORKSPACE_KEY = 'workspace-names'; + + const PreferencesGroup = GObject.registerClass({ + Properties: { +@@ -381,182 +377,5 @@ class WorkspacesPage extends Gtk.ScrolledWindow { + + box.append(new GeneralGroup()); + box.append(new BehaviorGroup()); +- +- box.append(new Gtk.Label({ +- label: '%s'.format(_('Workspace Names')), +- use_markup: true, +- halign: Gtk.Align.START, +- })); +- +- this._list = new Gtk.ListBox({ +- selection_mode: Gtk.SelectionMode.NONE, +- valign: Gtk.Align.START, +- show_separators: true, +- }); +- this._list.connect('row-activated', (l, row) => row.edit()); +- box.append(this._list); +- +- const context = this._list.get_style_context(); +- const cssProvider = new Gtk.CssProvider(); +- cssProvider.load_from_data( +- 'list { min-width: 25em; }', -1); +- +- context.add_provider(cssProvider, +- Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); +- context.add_class('frame'); +- +- this._list.append(new NewWorkspaceRow()); +- +- this._actionGroup = new Gio.SimpleActionGroup(); +- this._list.insert_action_group('workspaces', this._actionGroup); +- +- let action; +- action = new Gio.SimpleAction({ name: 'add' }); +- action.connect('activate', () => { +- const names = this._settings.get_strv(WORKSPACE_KEY); +- this._settings.set_strv(WORKSPACE_KEY, [ +- ...names, +- _('Workspace %d').format(names.length + 1), +- ]); +- }); +- this._actionGroup.add_action(action); +- +- action = new Gio.SimpleAction({ +- name: 'remove', +- parameter_type: new GLib.VariantType('s'), +- }); +- action.connect('activate', (a, param) => { +- const removed = param.deepUnpack(); +- this._settings.set_strv(WORKSPACE_KEY, +- this._settings.get_strv(WORKSPACE_KEY) +- .filter(name => name !== removed)); +- }); +- this._actionGroup.add_action(action); +- +- action = new Gio.SimpleAction({ name: 'update' }); +- action.connect('activate', () => { +- const names = this._getWorkspaceRows().map(row => row.name); +- this._settings.set_strv(WORKSPACE_KEY, names); +- }); +- this._actionGroup.add_action(action); +- +- this._settings = new Gio.Settings({ +- schema_id: WORKSPACE_SCHEMA, +- }); +- this._settings.connect(`changed::${WORKSPACE_KEY}`, +- this._sync.bind(this)); +- this._sync(); +- } +- +- _getWorkspaceRows() { +- return [...this._list].filter(row => row.name); +- } +- +- _sync() { +- const rows = this._getWorkspaceRows(); +- +- const oldNames = rows.map(row => row.name); +- const newNames = this._settings.get_strv(WORKSPACE_KEY); +- +- const removed = oldNames.filter(n => !newNames.includes(n)); +- const added = newNames.filter(n => !oldNames.includes(n)); +- +- removed.forEach(n => this._list.remove(rows.find(r => r.name === n))); +- added.forEach(n => { +- this._list.insert(new WorkspaceRow(n), newNames.indexOf(n)); +- }); +- } +-}); +- +-const WorkspaceRow = GObject.registerClass( +-class WorkspaceRow extends Gtk.ListBoxRow { +- _init(name) { +- super._init({ name }); +- +- const controller = new Gtk.ShortcutController(); +- controller.add_shortcut(new Gtk.Shortcut({ +- trigger: Gtk.ShortcutTrigger.parse_string('Escape'), +- action: Gtk.CallbackAction.new(this._stopEdit.bind(this)), +- })); +- this.add_controller(controller); +- +- const box = new Gtk.Box({ +- spacing: 12, +- margin_top: 6, +- margin_bottom: 6, +- margin_start: 6, +- margin_end: 6, +- }); +- +- const label = new Gtk.Label({ +- hexpand: true, +- xalign: 0, +- max_width_chars: 25, +- ellipsize: Pango.EllipsizeMode.END, +- }); +- this.bind_property('name', label, 'label', +- GObject.BindingFlags.SYNC_CREATE); +- box.append(label); +- +- const button = new Gtk.Button({ +- action_name: 'workspaces.remove', +- action_target: new GLib.Variant('s', name), +- icon_name: 'edit-delete-symbolic', +- }); +- box.append(button); +- +- this._entry = new Gtk.Entry({ +- max_width_chars: 25, +- }); +- +- this._stack = new Gtk.Stack(); +- this._stack.add_named(box, 'display'); +- this._stack.add_named(this._entry, 'edit'); +- this.child = this._stack; +- +- this._entry.connect('activate', () => { +- this.name = this._entry.text; +- this._stopEdit(); +- }); +- this._entry.connect('notify::has-focus', () => { +- if (this._entry.has_focus) +- return; +- this._stopEdit(); +- }); +- +- this.connect('notify::name', () => { +- button.action_target = new GLib.Variant('s', this.name); +- this.activate_action('workspaces.update', null); +- }); +- } +- +- edit() { +- this._entry.text = this.name; +- this._entry.grab_focus(); +- this._stack.visible_child_name = 'edit'; +- } +- +- _stopEdit() { +- this.grab_focus(); +- this._stack.visible_child_name = 'display'; +- } +-}); +- +-const NewWorkspaceRow = GObject.registerClass( +-class NewWorkspaceRow extends Gtk.ListBoxRow { +- _init() { +- super._init({ +- action_name: 'workspaces.add', +- child: new Gtk.Image({ +- icon_name: 'list-add-symbolic', +- pixel_size: 16, +- margin_top: 12, +- margin_bottom: 12, +- margin_start: 12, +- margin_end: 12, +- }), +- }); +- this.update_property( +- [Gtk.AccessibleProperty.LABEL], [_('Add Workspace')]); + } + }); +-- +2.51.1 + + +From bed8bb3da1a813d6b203e4ea6e3bbb3393fbe7e0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 30 May 2025 16:39:22 +0200 +Subject: [PATCH 19/19] window-list: Adjust to workspace-indicator changes + +Keep the `.panel-button` class to get the expected hover/focus/active +styling when using a regular menu button, but remove the horizontal +padding when using previews for fittsability. + +Part-of: +--- + extensions/window-list/extension.js | 6 ------ + extensions/window-list/stylesheet.css | 5 +++++ + 2 files changed, 5 insertions(+), 6 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 477ed8fe..f0d7564f 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -1531,12 +1531,6 @@ 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); + +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index 88ef0a25..ef9df230 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -6,6 +6,11 @@ + */ + @import url("stylesheet-workspace-switcher-dark.css"); + ++.window-list-workspace-indicator.previews { ++ -natural-hpadding: 0 !important; ++ -minimum-hpadding: 0 !important; ++} ++ + .window-list { + spacing: 2px; + font-size: 10pt; +-- +2.51.1 + diff --git a/SOURCES/more-ws-previews.patch b/SOURCES/more-ws-previews.patch index 40a8b74..39e1f5b 100644 --- a/SOURCES/more-ws-previews.patch +++ b/SOURCES/more-ws-previews.patch @@ -1,4 +1,4 @@ -From c4fafbcf01fc3c3846e5fe7d60d9aac623afdd9f Mon Sep 17 00:00:00 2001 +From 2bc8aa48edae1465a5c51be9d864a159b1009bc7 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 @@ -80,10 +80,10 @@ index 567f3e99..d307dcac 100644 context.add_provider(cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); -- -2.44.0 +2.47.0 -From a6e988875f52a49289677ca4d883a98b5515033f Mon Sep 17 00:00:00 2001 +From d1380931b47adb23c36e5499cbd931fba4d63bd0 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 @@ -363,10 +363,10 @@ index 71efa039..19858a39 100644 extension_sources += files('prefs.js') -- -2.44.0 +2.47.0 -From 071226445e95d1a5551378aaf3c83db625fc2422 Mon Sep 17 00:00:00 2001 +From c97c668cf178968d70a9f2de3308e37e0b931acb 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 @@ -859,7 +859,7 @@ index 19858a39..eb25b9cc 100644 +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 +index 00000000..c88ffc9c --- /dev/null +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -0,0 +1,454 @@ @@ -1180,7 +1180,7 @@ index 00000000..b98de047 + } + + _onDestroy() { -+ for (let i = i; i < this._workspaceManagerSignals.length; i++) ++ for (let i = 0; i < this._workspaceManagerSignals.length; i++) + global.workspace_manager.disconnect(this._workspaceManagerSignals[i]); + + if (this._settingsChangedId) { @@ -1329,10 +1329,10 @@ index 10b1d517..bd39ab61 100644 extensions/workspace-indicator/prefs.js +extensions/workspace-indicator/workspaceIndicator.js -- -2.44.0 +2.47.0 -From 4720bf9f69c91c6fa39897534921eb4f2eceb8eb Mon Sep 17 00:00:00 2001 +From 34ba767aa622ac9122463f4649f7a8854a56f25d 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 @@ -1375,7 +1375,7 @@ index 84aaf454..4e12cce4 100644 } diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js -index b98de047..101c89c6 100644 +index c88ffc9c..28fc3ea8 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -263,6 +263,8 @@ class WorkspaceIndicator extends PanelMenu.Button { @@ -1406,10 +1406,10 @@ index b98de047..101c89c6 100644 reactive: true, }); -- -2.44.0 +2.47.0 -From 22f44ae9f21337a11a09447763fbd223e37f3d56 Mon Sep 17 00:00:00 2001 +From c5843b7870e0ce0cbf7b2f587193b93c63062105 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 @@ -1473,10 +1473,10 @@ index cdfe5b61..c24f159f 100644 this._delegate = this; -- -2.44.0 +2.47.0 -From c8bb217d2053cf8d7db5f4866f834b6d06250427 Mon Sep 17 00:00:00 2001 +From a81dbea8250c85b950c83048f178c700635a8c38 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 @@ -1489,7 +1489,7 @@ to one of the extensions. 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 +index 28fc3ea8..01604b91 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -25,11 +25,13 @@ const TOOLTIP_ANIMATION_TIME = 150; @@ -1526,10 +1526,10 @@ index 101c89c6..ba1e05d7 100644 let container = new St.Widget({ layout_manager: new Clutter.BinLayout(), -- -2.44.0 +2.47.0 -From fc27a7f0e6da8647380b5bbe901b27ec0e5aa728 Mon Sep 17 00:00:00 2001 +From e1c5b589fca9021b960e19f043ca26735c8b02de 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 @@ -1600,10 +1600,10 @@ index c24f159f..1a1d15cd 100644 this.menu.actor.remove_style_class_name('panel-menu'); -- -2.44.0 +2.47.0 -From 3838a915d953b910aa2d9ec82cd22dcf2e0f7440 Mon Sep 17 00:00:00 2001 +From c673a9d7169cc1a2f6df9c3eeea4b11f8968dc19 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 @@ -1688,10 +1688,10 @@ index 1a1d15cd..4290d58a 100644 layout_manager: new Clutter.BinLayout(), x_expand: true, -- -2.44.0 +2.47.0 -From 7d2abee5e5de19bba95e4fb66692e08ace67c972 Mon Sep 17 00:00:00 2001 +From 3271e93695782d06ada9752144c650605ceed76a 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 @@ -1731,10 +1731,10 @@ index c58df434..a011bc90 100644 this.set_position( this._monitor.x, -- -2.44.0 +2.47.0 -From bc71e5e7a21249868a481238193e1ca15eba5699 Mon Sep 17 00:00:00 2001 +From 314072792bba618a85897dc085bb72debe3ec6b1 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 @@ -1749,7 +1749,7 @@ do the same here. 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 +index 01604b91..6e3ad7b5 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -17,9 +17,6 @@ const Main = imports.ui.main; @@ -1775,10 +1775,10 @@ index ba1e05d7..60e084e2 100644 } -- -2.44.0 +2.47.0 -From 66cf82e0676179a2565a75bbad1c31bb539c065c Mon Sep 17 00:00:00 2001 +From 002a0bb8036a997a70acda017e4016fd9fd51806 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 @@ -1790,7 +1790,7 @@ instead of getting it from the workspace manager again. 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 +index 6e3ad7b5..60356d74 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -454,7 +454,7 @@ class WorkspaceIndicator extends PanelMenu.Button { @@ -1803,10 +1803,10 @@ index 60e084e2..4f2188be 100644 } }); -- -2.44.0 +2.47.0 -From c51f0d790c7e4d575d770b63ad7c9632b4af79b1 Mon Sep 17 00:00:00 2001 +From 4e2785910ceccc079305da1b9d0e2c810b1f982a 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 @@ -1820,7 +1820,7 @@ This removes another difference with the window-list copy. 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 +index 60356d74..39d4e296 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -296,8 +296,6 @@ class WorkspaceIndicator extends PanelMenu.Button { @@ -1872,10 +1872,10 @@ index 4f2188be..66b71cd6 100644 this._workspacesItems[i].label_actor = this._statusLabel; this._workspacesItems[i].connect('activate', (actor, _event) => { -- -2.44.0 +2.47.0 -From ef3a5860782e67dffcb63c705422102f68bd68ad Mon Sep 17 00:00:00 2001 +From 5f33bc574aee51700c92b940b23df0b7966450d6 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 @@ -1889,7 +1889,7 @@ used in the copy that is included with the window-list extension. 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 +index 39d4e296..83713b6f 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -227,16 +227,17 @@ class WorkspaceThumbnail extends St.Button { @@ -1915,10 +1915,10 @@ index 66b71cd6..bc9e222b 100644 } -- -2.44.0 +2.47.0 -From 3161d6c59fc8f7dd1b5c5f21082a5fd00cbcf5a9 Mon Sep 17 00:00:00 2001 +From a65fd9e61cf3f3a83116815482843b1b20eef20f 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 @@ -1931,7 +1931,7 @@ the check will allow to use the same code in the window list. 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 +index 83713b6f..fa05a54c 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -309,6 +309,16 @@ class WorkspaceIndicator extends PanelMenu.Button { @@ -1981,10 +1981,10 @@ index bc9e222b..ac270d64 100644 : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY); } -- -2.44.0 +2.47.0 -From 3afe55799368e1f899d2949bcc981718c6498623 Mon Sep 17 00:00:00 2001 +From 521b224b17cb15df49a32d5c2dffe5ce65285df4 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 @@ -1996,7 +1996,7 @@ window-list extension, so use that. 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 +index fa05a54c..bbb51c41 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -422,19 +422,18 @@ class WorkspaceIndicator extends PanelMenu.Button { @@ -2029,10 +2029,10 @@ index ac270d64..9b22102a 100644 this._statusLabel.set_text(this._labelText()); -- -2.44.0 +2.47.0 -From 2eef4f6dd803021303be7c4f15775a41389debb3 Mon Sep 17 00:00:00 2001 +From 3145e45a2d897b177e7395cec71a1293b6857a36 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 @@ -2049,7 +2049,7 @@ for menu items. 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 +index bbb51c41..d401b6ab 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -283,7 +283,7 @@ class WorkspaceIndicator extends PanelMenu.Button { @@ -2115,10 +2115,10 @@ index 9b22102a..d8b29f58 100644 _updateThumbnails() { -- -2.44.0 +2.47.0 -From 8785f56bf69272e664c207856bfb7417342550c6 Mon Sep 17 00:00:00 2001 +From 30adbc349b382c69c7592cd674d7837f6c9185c5 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 @@ -2136,7 +2136,7 @@ bigger click/touch target, so copy the window-list behavior. 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 +index d401b6ab..29b8a671 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -403,8 +403,9 @@ class WorkspaceIndicator extends PanelMenu.Button { @@ -2151,10 +2151,10 @@ index d8b29f58..756758b3 100644 _updateMenuLabels() { -- -2.44.0 +2.47.0 -From 6e483b472fa4b5a7be8f00b1ef87f28658f815aa Mon Sep 17 00:00:00 2001 +From 675e91b7b521b4c8d7f53a9f3d028ab7300281dd 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 @@ -2206,10 +2206,10 @@ index 4e12cce4..f74f7e88 100644 .workspace-indicator-window-preview.active { -- -2.44.0 +2.47.0 -From cf06ac2f9b7e2412fc555721b9c6d34fea7e0b45 Mon Sep 17 00:00:00 2001 +From 011af97ab27a4e7be9dab937754470e890bc131c 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 @@ -2344,10 +2344,10 @@ index f74f7e88..b0f7d171 100644 -} +@import url("stylesheet-dark.css"); -- -2.44.0 +2.47.0 -From a5fd131564600f3117f0dbd27a1bb82592ec1132 Mon Sep 17 00:00:00 2001 +From bcb34a3a91a5140b5ecc6f9582dd9f80272c7e32 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 @@ -2905,10 +2905,10 @@ index 4290d58a..00000000 -}); - -- -2.44.0 +2.47.0 -From fbcf6cb317b58dc32c67952b54cec925adfbad34 Mon Sep 17 00:00:00 2001 +From 36e4714c31398b74eb1727197f40bbd81ccfd6f7 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 @@ -2920,7 +2920,7 @@ via scroll events. Use that instead of implementing our own. 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 +index 29b8a671..1dd3ed6b 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -307,8 +307,10 @@ class WorkspaceIndicator extends PanelMenu.Button { @@ -2957,10 +2957,10 @@ index 756758b3..8e3fec56 100644 - } }); -- -2.44.0 +2.47.0 -From 346960098322af549c55a6eaf1628f1743585df1 Mon Sep 17 00:00:00 2001 +From f3154702594a814e5131252ab84b9daf0553ada3 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 @@ -2974,7 +2974,7 @@ workspace ourselves. 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 +index 1dd3ed6b..ae526929 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -116,8 +116,14 @@ class WorkspaceLayout extends Clutter.LayoutManager { @@ -3058,10 +3058,10 @@ index 8e3fec56..01b831f7 100644 _activate(index) { -- -2.44.0 +2.47.0 -From 76ec37876c295b9150e98c04e11189e464af7a94 Mon Sep 17 00:00:00 2001 +From e854ad2e483489952691d5fbede37e3fec63737c 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 @@ -3073,7 +3073,7 @@ into a dedicated class. 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 +index ae526929..e5be8081 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -280,6 +280,51 @@ const WorkspaceThumbnail = GObject.registerClass({ @@ -3209,10 +3209,10 @@ index 01b831f7..a559b8e2 100644 let workspaceManager = global.workspace_manager; -- -2.44.0 +2.47.0 -From 69c5eefbca44f58d10072115dd46ffada862cd48 Mon Sep 17 00:00:00 2001 +From 18b40e7f586d4546bd64745a0b2d617655b87af3 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 @@ -3246,7 +3246,7 @@ index f74f7e88..61d1e982 100644 padding: 5px; spacing: 3px; diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js -index a559b8e2..17cf7c89 100644 +index e5be8081..d496d22d 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -20,6 +20,8 @@ const PopupMenu = imports.ui.popupMenu; @@ -3342,10 +3342,10 @@ index a559b8e2..17cf7c89 100644 _onDestroy() { -- -2.44.0 +2.47.0 -From 609674b2763bfd1536e8854b79d3c1bf265f00b9 Mon Sep 17 00:00:00 2001 +From 422a5e6f67b23c3b9b0764ec313ad0ad864249db 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 @@ -3359,7 +3359,7 @@ names to not lose functionality with regards to the current menu. 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 +index d496d22d..f5ffdbb7 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -124,10 +124,23 @@ const WorkspaceThumbnail = GObject.registerClass({ @@ -3504,10 +3504,10 @@ index 17cf7c89..5e6d6300 100644 } -- -2.44.0 +2.47.0 -From 40cfe3a41fa3823fce06824b82e663b8f63f4e6d Mon Sep 17 00:00:00 2001 +From a0985eb24eafee329b6af6c2c2f7db4de7b103e3 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 @@ -3524,7 +3524,7 @@ previews and the actual layout. 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 +index f5ffdbb7..362c6372 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -475,8 +475,6 @@ class WorkspaceIndicator extends PanelMenu.Button { @@ -3548,10 +3548,10 @@ index 5e6d6300..aad2716a 100644 this._statusLabel.visible = useMenu; -- -2.44.0 +2.47.0 -From 1cad76230f8f70a08a8ccbe0970641a7b6a717b5 Mon Sep 17 00:00:00 2001 +From 74b64e860757d56f0d500165ba2e49024ead48d2 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 @@ -3614,7 +3614,7 @@ index 61d1e982..fb0e8b1a 100644 border-color: #9f9f9f; } diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js -index aad2716a..7b3c6fbe 100644 +index 362c6372..ed5645db 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -439,7 +439,7 @@ const WorkspacePreviews = GObject.registerClass({ @@ -3649,7 +3649,7 @@ index aad2716a..7b3c6fbe 100644 } _onDestroy() { - for (let i = i; i < this._workspaceManagerSignals.length; i++) + for (let i = 0; i < this._workspaceManagerSignals.length; i++) global.workspace_manager.disconnect(this._workspaceManagerSignals[i]); - if (this._settingsChangedId) { @@ -3748,10 +3748,10 @@ index aad2716a..7b3c6fbe 100644 } }); -- -2.44.0 +2.47.0 -From 20a2122ab7a435cb1a0840747a5d13be0d838a9e Mon Sep 17 00:00:00 2001 +From f810340e1e7ffd7c1964ec950be8b0585afe9e7f 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 @@ -3898,7 +3898,7 @@ index 00000000..c7c634ca + + diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js -index 7b3c6fbe..9d566f41 100644 +index ed5645db..14359a0e 100644 --- a/extensions/workspace-indicator/workspaceIndicator.js +++ b/extensions/workspace-indicator/workspaceIndicator.js @@ -22,8 +22,6 @@ const TOOLTIP_ANIMATION_TIME = 150; @@ -3966,10 +3966,10 @@ index bd39ab61..4d551780 100644 +extensions/workspace-indicator/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml extensions/workspace-indicator/workspaceIndicator.js -- -2.44.0 +2.47.0 -From 597ed15f9e4a1fd6eeaaedaae59db30c4d379c3f Mon Sep 17 00:00:00 2001 +From 952d2ca4e3e5b35b3a25336506b06ef0dca734a9 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 @@ -3998,5 +3998,5 @@ index e35990ff..79cd1355 100644 }); -- -2.44.0 +2.47.0 diff --git a/SOURCES/window-list-attention-indicator.patch b/SOURCES/window-list-attention-indicator.patch new file mode 100644 index 0000000..b7bc6ff --- /dev/null +++ b/SOURCES/window-list-attention-indicator.patch @@ -0,0 +1,206 @@ +From 984a2672b4c4c41d9dab85c068f76efa98f81a56 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 17 Dec 2024 01:09:34 +0100 +Subject: [PATCH] window-list: Add attention indicator + +Some X11 clients still rely on the traditional urgent/demand-attention +hints instead of notifications to request the user's attention. + +Support these by adding a visual indication to the corresponding +buttons, based on the visual indicator in libadwaita's tabs. + +Closes: https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/543 +--- + extensions/window-list/extension.js | 90 +++++++++++++++++++++++++-- + extensions/window-list/stylesheet.css | 9 +++ + 2 files changed, 95 insertions(+), 4 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index bb9ca80f..477ed8fe 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -160,23 +160,30 @@ const TitleWidget = GObject.registerClass({ + GObject.ParamFlags.READWRITE, + false), + }, +-}, class TitleWidget extends St.BoxLayout { ++}, class TitleWidget extends St.Widget { + _init() { + super._init({ ++ layout_manager: new Clutter.BinLayout(), ++ x_expand: true, ++ y_expand: true, ++ }); ++ ++ const hbox = new St.BoxLayout({ + style_class: 'window-button-box', + x_expand: true, + y_expand: true, + }); ++ this.add_child(hbox); + + this._icon = new St.Bin({ + style_class: 'window-button-icon', + }); +- this.add_child(this._icon); ++ hbox.add_child(this._icon); + + this._label = new St.Label({ + y_align: Clutter.ActorAlign.CENTER, + }); +- this.add_child(this._label); ++ hbox.add_child(this._label); + this.label_actor = this._label; + + this.bind_property('abstract-label', +@@ -189,11 +196,28 @@ const TitleWidget = GObject.registerClass({ + x_expand: true, + y_expand: true, + }); +- this.add_child(this._abstractLabel); ++ hbox.add_child(this._abstractLabel); + + this.bind_property('abstract-label', + this._abstractLabel, 'visible', + GObject.BindingFlags.SYNC_CREATE); ++ ++ this._attentionIndicator = new St.Widget({ ++ style_class: 'window-button-attention-indicator', ++ x_expand: true, ++ y_expand: true, ++ y_align: Clutter.ActorAlign.END, ++ scale_x: 0, ++ }); ++ this._attentionIndicator.set_pivot_point(0.5, 0.5); ++ this.add_child(this._attentionIndicator); ++ } ++ ++ setNeedsAttention(enable) { ++ this._attentionIndicator.ease({ ++ scaleX: enable ? 0.4 : 0, ++ duration: 300, ++ }); + } + }); + +@@ -219,7 +243,12 @@ class WindowTitle extends TitleWidget { + 'notify::title', this._updateTitle.bind(this)); + this._notifyMinimizedId = this._metaWindow.connect( + 'notify::minimized', this._minimizedChanged.bind(this)); ++ this._notifyDemandsAttentionId = this._metaWindow.connect( ++ 'notify::demands-attention', this._updateNeedsAttention.bind(this)); ++ this._notifyUrgentId = this._metaWindow.connect( ++ 'notify::urgent', this._updateNeedsAttention.bind(this)); + this._minimizedChanged(); ++ this._updateNeedsAttention(); + } + + _minimizedChanged() { +@@ -227,6 +256,11 @@ class WindowTitle extends TitleWidget { + this._updateTitle(); + } + ++ _updateNeedsAttention() { ++ const { urgent, demandsAttention } = this._metaWindow; ++ this.setNeedsAttention(urgent || demandsAttention); ++ } ++ + _updateTitle() { + if (!this._metaWindow.title) + return; +@@ -272,6 +306,8 @@ class WindowTitle extends TitleWidget { + this._metaWindow.disconnect(this._notifyMinimizedId); + this._metaWindow.disconnect(this._notifyWmClass); + this._metaWindow.disconnect(this._notifyAppId); ++ this._metaWindow.disconnect(this._notifyDemandsAttentionId); ++ this._metaWindow.disconnect(this._notifyUrgentId); + } + }); + +@@ -281,6 +317,7 @@ class AppTitle extends TitleWidget { + super._init(); + + this._app = app; ++ this._windows = new Map(); + + this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); + this._label.text = app.get_name(); +@@ -291,6 +328,10 @@ class AppTitle extends TitleWidget { + this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); + }); + ++ this._windowsChangedId = this._app.connect( ++ 'windows-changed', this._onWindowsChanged.bind(this)); ++ this._onWindowsChanged(); ++ + this.connect('destroy', this._onDestroy.bind(this)); + } + +@@ -299,6 +340,47 @@ class AppTitle extends TitleWidget { + this._textureCache.disconnect(this._iconThemeChangedId); + this._iconThemeChangedId = 0; + this._textureCache = null; ++ ++ for (const [window, ids] of this._windows) ++ ids.forEach(id => window.disconnect(id)); ++ this._windows.clear(); ++ this._app.disconnect(this._windowsChangedId); ++ } ++ ++ _onWindowsChanged() { ++ const windows = this._app.get_windows(); ++ const removed = [...this._windows].filter(w => !windows.includes(w)); ++ removed.forEach(w => this._untrackWindow(w)); ++ windows.forEach(w => this._trackWindow(w)); ++ this._updateNeedsAttention(); ++ } ++ ++ _trackWindow(window) { ++ if (this._windows.has(window)) ++ return; ++ ++ const signals = [ ++ window.connect('notify::urgent', ++ () => this._updateNeedsAttention()), ++ window.connect('notify::demands-attention', ++ () => this._updateNeedsAttention()), ++ ]; ++ this._windows.set(window, signals); ++ } ++ ++ _untrackWindow(window) { ++ if (!this._windows.has(window)) ++ return; ++ ++ const ids = this._windows.get(window); ++ ids.forEach(id => window.disconnect(id)); ++ this._windows.delete(window); ++ } ++ ++ _updateNeedsAttention() { ++ const needsAttention = ++ [...this._windows.keys()].some(w => w.urgent || w.demandsAttention); ++ this.setNeedsAttention(needsAttention); + } + }); + +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index 4c06ebc0..45b42065 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -103,3 +103,12 @@ + border-radius: 99px; + margin: 6px; + } ++ ++.window-button-attention-indicator { ++ background-color: rgba(27, 106, 203, 1.0); ++ height: 2px; ++} ++ ++.window-button.minimized .window-button-attention-indicator { ++ background-color: rgba(27, 106, 203, 0.6); ++} +-- +2.47.1 + diff --git a/SOURCES/window-list-reordering.patch b/SOURCES/window-list-reordering.patch new file mode 100644 index 0000000..459ae4a --- /dev/null +++ b/SOURCES/window-list-reordering.patch @@ -0,0 +1,2065 @@ +From 6f3e804a4910b43b55848d7bbe35916753c098c0 Mon Sep 17 00:00:00 2001 +From: Jakub Steiner +Date: Tue, 16 Jul 2024 09:40:53 +0200 +Subject: [PATCH 01/24] window-list: Update styling + +- Contemporary look. Fewer borders, thinner outlines for workspace indicators +- Lacks the designed unfocused window separators. +- Relies on https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/328 + +Fixes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/421 + +Part-of: +--- + extensions/window-list/classic.css | 68 ++++++++----- + extensions/window-list/stylesheet.css | 96 +++++++------------ + .../workspace-indicator/stylesheet-dark.css | 4 +- + 3 files changed, 78 insertions(+), 90 deletions(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index d7ceb062..1d9b82f0 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -1,22 +1,24 @@ ++/* ++ * SPDX-FileCopyrightText: 2013 Florian Müllner ++ * SPDX-FileCopyrightText: 2015 Jakub Steiner ++ * ++ * SPDX-License-Identifier: GPL-2.0-or-later ++ */ ++ + @import url("stylesheet.css"); + @import url("stylesheet-workspace-switcher-light.css"); + + #panel.bottom-panel { + border-top-width: 1px; + border-bottom-width: 0px; +- height: 2.25em ; +- padding: 2px; ++ height: 2.5em; + } + +- .bottom-panel .window-button > StWidget, +- .bottom-panel .window-picker-toggle > StWidget { +- color: #2e3436; +- background-color: #eee; ++ .bottom-panel .window-button > StWidget { + border-radius: 3px; + padding: 3px 6px 1px; + box-shadow: none; + text-shadow: none; +- border: 1px solid rgba(0,0,0,0.2); + } + + .bottom-panel .window-button > StWidget { +@@ -24,27 +26,43 @@ + max-width: 18.75em; + } + +- .bottom-panel .window-button:hover > StWidget, +- .bottom-panel .window-picker-toggle:hover > StWidget { +- background-color: #f9f9f9; +- } ++ .window-button > StWidget { ++ color: #000; ++ background-color: transparent; ++} + +- .bottom-panel .window-button:active > StWidget, +- .bottom-panel .window-button:focus > StWidget { +- box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); +- } ++.window-button > StWidget { ++ -st-natural-width: 18.75em; ++ max-width: 18.75em; ++} + +- .bottom-panel .window-button.focused > StWidget, +- .bottom-panel .window-picker-toggle:checked > StWidget { +- background-color: #ccc; +- box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); +- } ++.window-button:hover > StWidget { ++ background-color: #e1e1e1; ++} + +- .bottom-panel .window-button.focused:hover > StWidget { +- background-color: #e9e9e9; ++.window-button:active > StWidget, ++.window-button:focus > StWidget { ++ background-color: #d4d4d4; ++} ++ ++.window-button.focused > StWidget { ++ background-color: #c7c7c7; ++} ++ ++ .window-button.focused:hover > StWidget { ++ background-color: #bbb; + } + +- .bottom-panel .window-button.minimized > StWidget { +- color: #888; +- box-shadow: none; ++ .window-button.focused:active > StWidget { ++ background-color: #aeaeae; + } ++ ++.window-button.minimized > StWidget { ++ color: #aaa; ++ background-color: #f9f9f9; ++} ++ ++.window-button.minimized:active > StWidget { ++ color: #aaa; ++ background-color: #f9f9f9; ++} +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index 4ba47f07..2a835a1b 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -1,3 +1,9 @@ ++/* ++ * SPDX-FileCopyrightText: 2012 Florian Müllner ++ * SPDX-FileCopyrightText: 2013 Giovanni Campagna ++ * ++ * SPDX-License-Identifier: GPL-2.0-or-later ++ */ + @import url("stylesheet-workspace-switcher-dark.css"); + + .window-list { +@@ -5,8 +11,14 @@ + font-size: 10pt; + } + ++.bottom-panel { ++ background-color: #000000; ++ border-top-width: 0px; ++ height: 2.45em; ++} ++ + .window-button { +- padding: 1px; ++ padding: 4px, 3px; + } + + .window-button:first-child:ltr { +@@ -21,22 +33,12 @@ + spacing: 4px; + } + +-.window-button > StWidget, +-.window-picker-toggle > StWidget { +- color: #bbb; +- background-color: black; +- border-radius: 2px; ++.window-button > StWidget { ++ color: #fff; ++ background-color: transparent; ++ border-radius: 4px; + padding: 3px 6px 1px; +- box-shadow: inset 1px 1px 4px rgba(255,255,255,0.5); +- text-shadow: 1px 1px 4px rgba(0,0,0,0.8); +-} +- +-.window-picker-toggle { +- padding: 3px; +-} +- +-.window-picker-toggle > StWidet { +- border: 1px solid rgba(255,255,255,0.3); ++ transition: 100ms ease; + } + + .window-button > StWidget { +@@ -44,35 +46,35 @@ + max-width: 18.75em; + } + +-.window-button:hover > StWidget, +-.window-picker-toggle:hover > StWidget { +- color: white; +- background-color: #1f1f1f; ++.window-button:hover > StWidget { ++ background-color: #303030; + } + + .window-button:active > StWidget, + .window-button:focus > StWidget { +- box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5); ++ background-color: #3c3c3c; + } + +-.window-button.focused > StWidget, +-.window-picker-toggle:checked > StWidget { +- color: white; +- box-shadow: inset 1px 1px 4px rgba(255,255,255,0.7); ++.window-button.focused > StWidget { ++ background-color: #5b5b5b; + } + +-.window-button.focused:active > StWidget, +-.window-picker-toggle:checked:active > StWidget { +- box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7); +-} ++ .window-button.focused:hover > StWidget { ++ background-color: #676767; ++ } ++ ++ .window-button.focused:active > StWidget { ++ background-color: #747474; ++ } + + .window-button.minimized > StWidget { + color: #666; +- box-shadow: inset -1px -1px 4px rgba(255,255,255,0.5); ++ background-color: #161616; + } + + .window-button.minimized:active > StWidget { +- box-shadow: inset -2px -2px 4px rgba(255,255,255,0.5); ++ color: #666; ++ background-color: #161616; + } + + .window-button-icon { +@@ -80,38 +82,6 @@ + height: 24px; + } + +-.window-list-workspace-indicator .status-label-bin { +- background-color: rgba(200, 200, 200, .3); +- border: 1px solid #cccccc; +- padding: 0 3px; +- margin: 3px; +-} +- +-.window-list-workspace-indicator .workspaces-box { +- spacing: 3px; +- padding: 3px; +-} +- +-.window-list-workspace-indicator .workspace { +- border: 2px solid #000; +- width: 52px; +- border-radius: 4px; +- background-color: #595959; +-} +- +-.window-list-workspace-indicator .workspace.active { +- border-color: #fff; +-} +- +-.window-list-workspace-indicator-window-preview { +- background-color: #bebebe; +- border: 1px solid #828282; +-} +- +-.window-list-workspace-indicator-window-preview.active { +- background-color: #d4d4d4; +-} +- + .notification { + font-weight: normal; + } +diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css +index 017d844a..872c6afc 100644 +--- a/extensions/workspace-indicator/stylesheet-dark.css ++++ b/extensions/workspace-indicator/stylesheet-dark.css +@@ -39,7 +39,7 @@ + + .workspace-indicator-menu .workspace, + .workspace-indicator .workspace { +- border: 2px solid transparent; ++ border: 1px solid transparent; + border-radius: 4px; + background-color: #3f3f3f; + } +@@ -55,7 +55,7 @@ + + .workspace-indicator-menu .workspace.active, + .workspace-indicator .workspace.active { +- border-color: #9f9f9f; ++ border-color: #fff; + } + + .workspace-indicator-window-preview { +-- +2.49.0 + + +From 43bc977b5a0883707e6b326afa2424a96472f747 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 03:36:08 +0200 +Subject: [PATCH 02/24] window-list: Small stylesheet cleanup + +The light stylesheet duplicates some declarations, and the +last occurrence matches what we already inherit from the +dark stylesheet. + +Part-of: +--- + extensions/window-list/classic.css | 10 ---------- + 1 file changed, 10 deletions(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index 1d9b82f0..63e5e48c 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -21,21 +21,11 @@ + text-shadow: none; + } + +- .bottom-panel .window-button > StWidget { +- -st-natural-width: 18.7em; +- max-width: 18.75em; +- } +- + .window-button > StWidget { + color: #000; + background-color: transparent; + } + +-.window-button > StWidget { +- -st-natural-width: 18.75em; +- max-width: 18.75em; +-} +- + .window-button:hover > StWidget { + background-color: #e1e1e1; + } +-- +2.49.0 + + +From 452acc5dec2e81530f30d5d55681f76670f12ddd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 25 Jun 2024 19:24:35 +0200 +Subject: [PATCH 03/24] window-list: Don't use homogeneous layout + +We want all buttons in the window list to have the same size, +but that's already achieved via max/natural-width in the CSS. + +Not enforcing the equal size via the layout manager will allow +buttons to temporarily have a different size when we start +animating additions and removals. + +Part-of: +--- + extensions/window-list/extension.js | 9 +-------- + 1 file changed, 1 insertion(+), 8 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 688ca761..a59307e2 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -753,22 +753,15 @@ class WindowList extends St.Widget { + toggle.connect('notify::checked', + this._updateWindowListVisibility.bind(this)); + +- let layout = new Clutter.BoxLayout({ homogeneous: true }); +- this._windowList = new St.Widget({ ++ this._windowList = new St.BoxLayout({ + style_class: 'window-list', + reactive: true, +- layout_manager: layout, + x_align: Clutter.ActorAlign.START, + x_expand: true, + y_expand: true, + }); + box.add_child(this._windowList); + +- this._windowList.connect('style-changed', () => { +- let node = this._windowList.get_theme_node(); +- let spacing = node.get_length('spacing'); +- this._windowList.layout_manager.spacing = spacing; +- }); + this._windowList.connect('scroll-event', this._onScrollEvent.bind(this)); + + let indicatorsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.END }); +-- +2.49.0 + + +From 1a8a31bc63d2001359894f443706401c5df5dff7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 18 Jun 2024 18:55:05 +0200 +Subject: [PATCH 04/24] window-list: Don't hide window button while unmanaging + +This will allow to animate the transition. + +Part-of: +--- + extensions/window-list/extension.js | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index a59307e2..8ac59dd0 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -417,6 +417,10 @@ class WindowButton extends BaseButton { + }); + this._updateVisibility(); + ++ this._unmanaging = false; ++ this._unmanagingId = metaWindow.connect('unmanaging', ++ () => (this._unmanaging = true)); ++ + this._windowTitle = new WindowTitle(this.metaWindow); + this.set_child(this._windowTitle); + this.label_actor = this._windowTitle.label_actor; +@@ -466,6 +470,9 @@ class WindowButton extends BaseButton { + } + + _updateVisibility() { ++ if (this._unmanaging) ++ return; ++ + this.visible = this._isWindowVisible(this.metaWindow); + } + +@@ -476,6 +483,7 @@ class WindowButton extends BaseButton { + _onDestroy() { + super._onDestroy(); + this.metaWindow.disconnect(this._skipTaskbarId); ++ this.metaWindow.disconnect(this._unmanagingId); + this.metaWindow.disconnect(this._workspaceChangedId); + global.display.disconnect(this._notifyFocusId); + this._contextMenu.destroy(); +-- +2.49.0 + + +From a11b4ec2b7eaea3c02d7966459f7e680686d74a6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 18 Jun 2024 18:59:38 +0200 +Subject: [PATCH 05/24] window-list: Animate buttons in and out + +Buttons are currently added and removed from the list without +any transitions, which gives the list a "jumpy" feel. Instead, +do what we do elsewhere and smoothly animate additions and +removals by re-using the dash's ItemContainer class. + +Part-of: +--- + extensions/window-list/extension.js | 34 ++++++++++++++++------------- + 1 file changed, 19 insertions(+), 15 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 8ac59dd0..cb9e7160 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -6,6 +6,7 @@ const ExtensionUtils = imports.misc.extensionUtils; + const Main = imports.ui.main; + const Overview = imports.ui.overview; + const PopupMenu = imports.ui.popupMenu; ++const { DashItemContainer } = imports.ui.dash; + + const Me = ExtensionUtils.getCurrentExtension(); + const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker; +@@ -229,22 +230,25 @@ const BaseButton = GObject.registerClass({ + GObject.ParamFlags.READWRITE, + false), + }, +-}, class BaseButton extends St.Button { ++}, class BaseButton extends DashItemContainer { + _init(perMonitor, monitorIndex) { + this._perMonitor = perMonitor; + this._monitorIndex = monitorIndex; + this._ignoreWorkspace = false; + +- super._init({ ++ super._init(); ++ ++ this._button = new St.Button({ + style_class: 'window-button', + can_focus: true, + x_expand: true, + button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, + }); ++ this.setChild(this._button); + + this.connect('notify::allocation', + this._updateIconGeometry.bind(this)); +- this.connect('clicked', this._onClicked.bind(this)); ++ this._button.connect('clicked', this._onClicked.bind(this)); + this.connect('destroy', this._onDestroy.bind(this)); + this.connect('popup-menu', this._onPopupMenu.bind(this)); + +@@ -422,7 +426,7 @@ class WindowButton extends BaseButton { + () => (this._unmanaging = true)); + + this._windowTitle = new WindowTitle(this.metaWindow); +- this.set_child(this._windowTitle); ++ this._button.set_child(this._windowTitle); + this.label_actor = this._windowTitle.label_actor; + + this._contextMenu = new WindowContextMenu(this, this.metaWindow); +@@ -558,7 +562,7 @@ class AppButton extends BaseButton { + this._updateVisibility(); + + let stack = new St.Widget({ layout_manager: new Clutter.BinLayout() }); +- this.set_child(stack); ++ this._button.set_child(stack); + + this._singleWindowTitle = new St.Bin({ + x_expand: true, +@@ -851,7 +855,7 @@ class WindowList extends St.Widget { + + this._windowSignals = new Map(); + this._windowCreatedId = global.display.connect( +- 'window-created', (dsp, win) => this._addWindow(win)); ++ 'window-created', (dsp, win) => this._addWindow(win, true)); + + this._dragBeginId = Main.xdndHandler.connect('drag-begin', + this._monitorDrag.bind(this)); +@@ -980,14 +984,14 @@ class WindowList extends St.Widget { + w2.metaWindow.get_stable_sequence(); + }); + for (let i = 0; i < windows.length; i++) +- this._addWindow(windows[i].metaWindow); ++ this._addWindow(windows[i].metaWindow, false); + } else { + let apps = this._appSystem.get_running().sort((a1, a2) => { + return _getAppStableSequence(a1) - + _getAppStableSequence(a2); + }); + for (let i = 0; i < apps.length; i++) +- this._addApp(apps[i]); ++ this._addApp(apps[i], false); + } + } + +@@ -1001,26 +1005,26 @@ class WindowList extends St.Widget { + return; + + if (app.state === Shell.AppState.RUNNING) +- this._addApp(app); ++ this._addApp(app, true); + else if (app.state === Shell.AppState.STOPPED) + this._removeApp(app); + } + +- _addApp(app) { ++ _addApp(app, animate) { + let button = new AppButton(app, this._perMonitor, this._monitor.index); + this._settings.bind('display-all-workspaces', + button, 'ignore-workspace', Gio.SettingsBindFlags.GET); + this._windowList.add_child(button); ++ button.show(animate); + } + + _removeApp(app) { + let children = this._windowList.get_children(); + let child = children.find(c => c.app === app); +- if (child) +- child.destroy(); ++ child?.animateOutAndDestroy(); + } + +- _addWindow(win) { ++ _addWindow(win, animate) { + if (!this._grouped) + this._checkGrouping(); + +@@ -1038,6 +1042,7 @@ class WindowList extends St.Widget { + this._settings.bind('display-all-workspaces', + button, 'ignore-workspace', Gio.SettingsBindFlags.GET); + this._windowList.add_child(button); ++ button.show(animate); + } + + _removeWindow(win) { +@@ -1054,8 +1059,7 @@ class WindowList extends St.Widget { + + let children = this._windowList.get_children(); + let child = children.find(c => c.metaWindow === win); +- if (child) +- child.destroy(); ++ child?.animateOutAndDestroy(); + } + + _monitorDrag() { +-- +2.49.0 + + +From d8a3b7c4604936eb4bef7ab51de3d5b32ea0e890 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 18 Jun 2024 20:08:56 +0200 +Subject: [PATCH 06/24] window-list: Replace custom tooltip implementation + +DashItemContainer already has support for showing a tooltip-like +label, so now that we use that for animating items, we can use +it for tooltips as well. + +Part-of: +--- + extensions/window-list/extension.js | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index cb9e7160..0dd3775e 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -246,6 +246,13 @@ const BaseButton = GObject.registerClass({ + }); + this.setChild(this._button); + ++ this._button.connect('notify::hover', () => { ++ if (this._button.hover) ++ this.showLabel(); ++ else ++ this.hideLabel(); ++ }); ++ + this.connect('notify::allocation', + this._updateIconGeometry.bind(this)); + this._button.connect('clicked', this._onClicked.bind(this)); +@@ -287,6 +294,18 @@ const BaseButton = GObject.registerClass({ + this._updateVisibility(); + } + ++ showLabel() { ++ const [, , preferredTitleWidth] = this.label_actor.get_preferred_size(); ++ const maxTitleWidth = this.label_actor.allocation.get_width(); ++ const isTitleFullyShown = preferredTitleWidth <= maxTitleWidth; ++ ++ const labelText = isTitleFullyShown ++ ? '' : this.label_actor.text; ++ ++ this.setLabelText(labelText); ++ super.showLabel(); ++ } ++ + _setLongPressTimeout() { + if (this._longPressTimeoutId) + return; +-- +2.49.0 + + +From 761fe142b4ba63f8be725758ed73c2022938d098 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sat, 13 Jul 2024 00:11:19 +0200 +Subject: [PATCH 07/24] window-list: Fix .focused styling + +Commit 039c66e7b7c wrapped the button in a container to +animate transitions, but didn't adjust the `.focused` +styling to still apply to the button (where it is +expected) rather than the wrapper. + +Fix that. + +Part-of: +--- + extensions/window-list/extension.js | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 0dd3775e..ec37f50f 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -378,9 +378,9 @@ const BaseButton = GObject.registerClass({ + + _updateStyle() { + if (this._isFocused()) +- this.add_style_class_name('focused'); ++ this._button.add_style_class_name('focused'); + else +- this.remove_style_class_name('focused'); ++ this._button.remove_style_class_name('focused'); + } + + _windowEnteredOrLeftMonitor(_metaDisplay, _monitorIndex, _metaWindow) { +-- +2.49.0 + + +From af63f529d679ebd0331d182c04ce393b54e9edcd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 02:23:41 +0200 +Subject: [PATCH 08/24] window-list: Split out AppTitle class + +Even though it's just a box with icon and label, it's cleaner to +have a dedicated class. + +Part-of: +--- + extensions/window-list/extension.js | 66 ++++++++++++++++++----------- + 1 file changed, 42 insertions(+), 24 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index ec37f50f..e778a4d4 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -221,6 +221,47 @@ class WindowTitle extends St.BoxLayout { + } + }); + ++const AppTitle = GObject.registerClass( ++class AppTitle extends St.BoxLayout { ++ _init(app) { ++ super._init({ ++ style_class: 'window-button-box', ++ x_expand: true, ++ y_expand: true, ++ }); ++ ++ this._app = app; ++ ++ const icon = new St.Bin({ ++ style_class: 'window-button-icon', ++ child: app.create_icon_texture(ICON_TEXTURE_SIZE), ++ }); ++ this.add_child(icon); ++ ++ let label = new St.Label({ ++ text: app.get_name(), ++ y_align: Clutter.ActorAlign.CENTER, ++ }); ++ this.add_child(label); ++ this.label_actor = label; ++ ++ this._textureCache = St.TextureCache.get_default(); ++ this._iconThemeChangedId = ++ this._textureCache.connect('icon-theme-changed', () => { ++ icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); ++ }); ++ ++ this.connect('destroy', this._onDestroy.bind(this)); ++ } ++ ++ _onDestroy() { ++ if (this._iconThemeChangedId) ++ this._textureCache.disconnect(this._iconThemeChangedId); ++ this._iconThemeChangedId = 0; ++ this._textureCache = null; ++ } ++}); ++ + + const BaseButton = GObject.registerClass({ + GTypeFlags: GObject.TypeFlags.ABSTRACT, +@@ -588,25 +629,9 @@ class AppButton extends BaseButton { + }); + stack.add_actor(this._singleWindowTitle); + +- this._multiWindowTitle = new St.BoxLayout({ +- style_class: 'window-button-box', +- x_expand: true, +- }); ++ this._multiWindowTitle = new AppTitle(app); + stack.add_actor(this._multiWindowTitle); + +- this._icon = new St.Bin({ +- style_class: 'window-button-icon', +- child: app.create_icon_texture(ICON_TEXTURE_SIZE), +- }); +- this._multiWindowTitle.add(this._icon); +- +- let label = new St.Label({ +- text: app.get_name(), +- y_align: Clutter.ActorAlign.CENTER, +- }); +- this._multiWindowTitle.add(label); +- this._multiWindowTitle.label_actor = label; +- + this._menuManager = new PopupMenu.PopupMenuManager(this); + this._menu = new PopupMenu.PopupMenu(this, 0.5, St.Side.BOTTOM); + this._menu.connect('open-state-changed', _onMenuStateChanged); +@@ -620,12 +645,6 @@ class AppButton extends BaseButton { + this._appContextMenu.actor.hide(); + Main.uiGroup.add_actor(this._appContextMenu.actor); + +- this._textureCache = St.TextureCache.get_default(); +- this._iconThemeChangedId = +- this._textureCache.connect('icon-theme-changed', () => { +- this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); +- }); +- + this._windowsChangedId = this.app.connect( + 'windows-changed', this._windowsChanged.bind(this)); + this._windowsChanged(); +@@ -752,7 +771,6 @@ class AppButton extends BaseButton { + + _onDestroy() { + super._onDestroy(); +- this._textureCache.disconnect(this._iconThemeChangedId); + this._windowTracker.disconnect(this._notifyFocusId); + this.app.disconnect(this._windowsChangedId); + this._menu.destroy(); +-- +2.49.0 + + +From 9b7b6d0f4f7ed077738ae3be2c08ff4c0d52e402 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 02:55:14 +0200 +Subject: [PATCH 09/24] window-list: Simplify app button + +Depending on the number of windows, the button either shows the +title of the lone window, or the app title for multiple windows. + +While we always recreate the single-window title, we only create +the app title once and hide it as necessary. Avoiding re-creating +a simple actor 50% of mode transitions isn't worth the additional +complexity, so just handle both single- and multi-window titles +the same way. + +Part-of: +--- + extensions/window-list/extension.js | 69 ++++++++++------------------- + 1 file changed, 24 insertions(+), 45 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index e778a4d4..f4434afa 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -621,17 +621,6 @@ class AppButton extends BaseButton { + this.app = app; + this._updateVisibility(); + +- let stack = new St.Widget({ layout_manager: new Clutter.BinLayout() }); +- this._button.set_child(stack); +- +- this._singleWindowTitle = new St.Bin({ +- x_expand: true, +- }); +- stack.add_actor(this._singleWindowTitle); +- +- this._multiWindowTitle = new AppTitle(app); +- stack.add_actor(this._multiWindowTitle); +- + this._menuManager = new PopupMenu.PopupMenuManager(this); + this._menu = new PopupMenu.PopupMenu(this, 0.5, St.Side.BOTTOM); + this._menu.connect('open-state-changed', _onMenuStateChanged); +@@ -640,11 +629,6 @@ class AppButton extends BaseButton { + this._menuManager.addMenu(this._menu); + Main.uiGroup.add_actor(this._menu.actor); + +- this._appContextMenu = new AppContextMenu(this); +- this._appContextMenu.connect('open-state-changed', _onMenuStateChanged); +- this._appContextMenu.actor.hide(); +- Main.uiGroup.add_actor(this._appContextMenu.actor); +- + this._windowsChangedId = this.app.connect( + 'windows-changed', this._windowsChanged.bind(this)); + this._windowsChanged(); +@@ -691,37 +675,32 @@ class AppButton extends BaseButton { + } + + _windowsChanged() { +- let windows = this.getWindowList(); +- this._singleWindowTitle.visible = windows.length === 1; +- this._multiWindowTitle.visible = !this._singleWindowTitle.visible; +- +- if (this._singleWindowTitle.visible) { +- if (!this._windowTitle) { +- this.metaWindow = windows[0]; +- this._windowTitle = new WindowTitle(this.metaWindow); +- this._singleWindowTitle.child = this._windowTitle; +- this._windowContextMenu = new WindowContextMenu(this, this.metaWindow); +- this._windowContextMenu.connect( +- 'open-state-changed', _onMenuStateChanged); +- Main.uiGroup.add_actor(this._windowContextMenu.actor); +- this._windowContextMenu.actor.hide(); +- this._contextMenuManager.addMenu(this._windowContextMenu); +- } +- this._contextMenuManager.removeMenu(this._appContextMenu); +- this._contextMenu = this._windowContextMenu; +- this.label_actor = this._windowTitle.label_actor; ++ const windows = this.getWindowList(); ++ const singleWindowMode = windows.length === 1; ++ ++ if (this._singleWindowMode === singleWindowMode) ++ return; ++ ++ this._singleWindowMode = singleWindowMode; ++ ++ this._button.child?.destroy(); ++ this._contextMenu?.destroy(); ++ ++ if (this._singleWindowMode) { ++ const [window] = windows; ++ this._button.child = new WindowTitle(window); ++ this._contextMenu = new WindowContextMenu(this, window); + } else { +- if (this._windowTitle) { +- this.metaWindow = null; +- this._singleWindowTitle.child = null; +- this._windowTitle = null; +- this._windowContextMenu.destroy(); +- this._windowContextMenu = null; +- } +- this._contextMenu = this._appContextMenu; +- this._contextMenuManager.addMenu(this._appContextMenu); +- this.label_actor = this._multiWindowTitle.label_actor; ++ this._button.child = new AppTitle(this.app); ++ this._contextMenu = new AppContextMenu(this); + } ++ ++ this.label_actor = this._button.child.label_actor; ++ ++ this._contextMenu.connect('open-state-changed', _onMenuStateChanged); ++ Main.uiGroup.add_child(this._contextMenu.actor); ++ this._contextMenu.actor.hide(); ++ this._contextMenuManager.addMenu(this._contextMenu); + } + + _onClicked(actor, button) { +-- +2.49.0 + + +From 904be2f12f75eda75d85426f054b06b26e422a72 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 7 Oct 2024 17:22:04 +0200 +Subject: [PATCH 10/24] window-list: Fix minimized styling + +Commit 039c66e7b7c wrapped the button in a container to +animate transitions, but didn't adjust the `.minimized` +styling to still apply to the button (where it is +expected) rather than the wrapper. + +Fix this just like commit c72b8b21 did for the +`.focused` styling. + +Part-of: +--- + extensions/window-list/extension.js | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index f4434afa..6c00686c 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -523,9 +523,9 @@ class WindowButton extends BaseButton { + super._updateStyle(); + + if (this.metaWindow.minimized) +- this.add_style_class_name('minimized'); ++ this._button.add_style_class_name('minimized'); + else +- this.remove_style_class_name('minimized'); ++ this._button.remove_style_class_name('minimized'); + } + + _windowEnteredOrLeftMonitor(metaDisplay, monitorIndex, metaWindow) { +-- +2.49.0 + + +From 29013aca96261a1ba5c87310bdb391f75a8b801e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 7 Oct 2024 17:10:43 +0200 +Subject: [PATCH 11/24] window-list: Fix active state + +Commit c72b8b21 fixed the styling of the active window's button, +but missed that the `active` property uses the style information +as well. + +Adjust it to use the correct actor when checking for the style class. + +Closes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/529 + +Part-of: +--- + 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 6c00686c..936dd9e4 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -316,7 +316,7 @@ const BaseButton = GObject.registerClass({ + } + + get active() { +- return this.has_style_class_name('focused'); ++ return this._button.has_style_class_name('focused'); + } + + // eslint-disable-next-line camelcase +-- +2.49.0 + + +From 7e8255f99b49a373bd93203567acfe895b852245 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 15 Oct 2024 17:48:52 +0200 +Subject: [PATCH 12/24] window-list: Remove outdated style + +A long time ago, the window list used to embed the bottom message +tray, which caused notifications to inherit the window-list's +font style. + +Since that's no longer the case, we have no business in messing +with notification styling, so stop doing that. +--- + extensions/window-list/stylesheet.css | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index 2a835a1b..99d47e32 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -81,7 +81,3 @@ + width: 24px; + height: 24px; + } +- +-.notification { +- font-weight: normal; +-} +-- +2.49.0 + + +From 7bb47650a0c80a7950aa7f87a93543125009ec53 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 26 Sep 2024 19:07:11 +0200 +Subject: [PATCH 13/24] window-list: Split out some common code + +Adding an app button and adding a window button involves some +shared steps, move those to a shared `_addButton()` method. +--- + extensions/window-list/extension.js | 15 ++++++++------- + 1 file changed, 8 insertions(+), 7 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 936dd9e4..d92a4155 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -1026,14 +1026,18 @@ class WindowList extends St.Widget { + this._removeApp(app); + } + +- _addApp(app, animate) { +- let button = new AppButton(app, this._perMonitor, this._monitor.index); ++ _addButton(button, animate) { + this._settings.bind('display-all-workspaces', + button, 'ignore-workspace', Gio.SettingsBindFlags.GET); + this._windowList.add_child(button); + button.show(animate); + } + ++ _addApp(app, animate) { ++ const button = new AppButton(app, this._perMonitor, this._monitor.index); ++ this._addButton(button, animate); ++ } ++ + _removeApp(app) { + let children = this._windowList.get_children(); + let child = children.find(c => c.app === app); +@@ -1054,11 +1058,8 @@ class WindowList extends St.Widget { + this._windowSignals.set( + win, win.connect('unmanaged', () => this._removeWindow(win))); + +- let button = new WindowButton(win, this._perMonitor, this._monitor.index); +- this._settings.bind('display-all-workspaces', +- button, 'ignore-workspace', Gio.SettingsBindFlags.GET); +- this._windowList.add_child(button); +- button.show(animate); ++ const button = new WindowButton(win, this._perMonitor, this._monitor.index); ++ this._addButton(button, animate); + } + + _removeWindow(win) { +-- +2.49.0 + + +From 0b4da110e6539e209cf2ea75533e22b9d1a970ce Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 3 Oct 2024 17:19:31 +0200 +Subject: [PATCH 14/24] window-list: Split out common TitleWidget class + +Both app- and window title use the same structure, so add a shared +base class. +--- + extensions/window-list/extension.js | 61 ++++++++++++++--------------- + 1 file changed, 30 insertions(+), 31 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index d92a4155..69058fcd 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -133,22 +133,35 @@ class WindowContextMenu extends PopupMenu.PopupMenu { + } + } + +-const WindowTitle = GObject.registerClass( +-class WindowTitle extends St.BoxLayout { +- _init(metaWindow) { +- this._metaWindow = metaWindow; +- ++const TitleWidget = GObject.registerClass({ ++ GTypeFlags: GObject.TypeFlags.ABSTRACT, ++}, class TitleWidget extends St.BoxLayout { ++ _init() { + super._init({ + style_class: 'window-button-box', + x_expand: true, + y_expand: true, + }); + +- this._icon = new St.Bin({ style_class: 'window-button-icon' }); +- this.add(this._icon); +- this.label_actor = new St.Label({ y_align: Clutter.ActorAlign.CENTER }); +- this.label_actor.clutter_text.single_line_mode = true; +- this.add(this.label_actor); ++ this._icon = new St.Bin({ ++ style_class: 'window-button-icon', ++ }); ++ this.add_child(this._icon); ++ ++ this._label = new St.Label({ ++ y_align: Clutter.ActorAlign.CENTER, ++ }); ++ this.add_child(this._label); ++ this.label_actor = this._label; ++ } ++}); ++ ++const WindowTitle = GObject.registerClass( ++class WindowTitle extends TitleWidget { ++ _init(metaWindow) { ++ super._init(); ++ ++ this._metaWindow = metaWindow; + + this._textureCache = St.TextureCache.get_default(); + this._iconThemeChangedId = this._textureCache.connect( +@@ -178,9 +191,9 @@ class WindowTitle extends St.BoxLayout { + return; + + if (this._metaWindow.minimized) +- this.label_actor.text = '[%s]'.format(this._metaWindow.title); ++ this._label.text = '[%s]'.format(this._metaWindow.title); + else +- this.label_actor.text = this._metaWindow.title; ++ this._label.text = this._metaWindow.title; + } + + _updateIcon() { +@@ -222,33 +235,19 @@ class WindowTitle extends St.BoxLayout { + }); + + const AppTitle = GObject.registerClass( +-class AppTitle extends St.BoxLayout { ++class AppTitle extends TitleWidget { + _init(app) { +- super._init({ +- style_class: 'window-button-box', +- x_expand: true, +- y_expand: true, +- }); ++ super._init(); + + this._app = app; + +- const icon = new St.Bin({ +- style_class: 'window-button-icon', +- child: app.create_icon_texture(ICON_TEXTURE_SIZE), +- }); +- this.add_child(icon); +- +- let label = new St.Label({ +- text: app.get_name(), +- y_align: Clutter.ActorAlign.CENTER, +- }); +- this.add_child(label); +- this.label_actor = label; ++ this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); ++ this._label.text = app.get_name(); + + this._textureCache = St.TextureCache.get_default(); + this._iconThemeChangedId = + this._textureCache.connect('icon-theme-changed', () => { +- icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); ++ this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); + }); + + this.connect('destroy', this._onDestroy.bind(this)); +-- +2.49.0 + + +From a4373e1ceac4955325da97ce2deb9d7f2c8714d7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 3 Oct 2024 17:27:57 +0200 +Subject: [PATCH 15/24] window-list: Add TitleWidget:abstract-label property + +When true, the real label is replaced by a more abstract +representation. When used as drag actor, the focus is not +on identifying the window/app, but about picking a drop +location, and the reduced style helps with that. +--- + extensions/window-list/extension.js | 22 ++++++++++++++++++++++ + extensions/window-list/stylesheet.css | 6 ++++++ + 2 files changed, 28 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 69058fcd..fc47d2e6 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -135,6 +135,12 @@ class WindowContextMenu extends PopupMenu.PopupMenu { + + const TitleWidget = GObject.registerClass({ + GTypeFlags: GObject.TypeFlags.ABSTRACT, ++ Properties: { ++ 'abstract-label': GObject.ParamSpec.boolean( ++ 'abstract-label', '', '', ++ GObject.ParamFlags.READWRITE, ++ false), ++ }, + }, class TitleWidget extends St.BoxLayout { + _init() { + super._init({ +@@ -153,6 +159,22 @@ const TitleWidget = GObject.registerClass({ + }); + this.add_child(this._label); + this.label_actor = this._label; ++ ++ this.bind_property('abstract-label', ++ this._label, 'visible', ++ GObject.BindingFlags.SYNC_CREATE | ++ GObject.BindingFlags.INVERT_BOOLEAN); ++ ++ this._abstractLabel = new St.Widget({ ++ style_class: 'window-button-abstract-label', ++ x_expand: true, ++ y_expand: true, ++ }); ++ this.add_child(this._abstractLabel); ++ ++ this.bind_property('abstract-label', ++ this._abstractLabel, 'visible', ++ GObject.BindingFlags.SYNC_CREATE); + } + }); + +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index 99d47e32..ee500299 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -81,3 +81,9 @@ + width: 24px; + height: 24px; + } ++ ++.window-button-abstract-label { ++ background-color: #888; ++ border-radius: 99px; ++ margin: 6px; ++} +-- +2.49.0 + + +From 0fb4950e013eea7513029e9a5f18e602c0925dfe Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 03:20:52 +0200 +Subject: [PATCH 16/24] window-list: Split out `_createTitleActor()` hook + +This will allow creating a suitable drag actor that matches the +current title. In particular this allows for a drag actor that +isn't based on `ClutterClone`, and therefore doesn't inherit +focus/active/minimize/etc. styles that don't make sense outside +the actual window list. +--- + extensions/window-list/extension.js | 23 ++++++++++++++++++++--- + 1 file changed, 20 insertions(+), 3 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index fc47d2e6..a65e2108 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -418,6 +418,11 @@ const BaseButton = GObject.registerClass({ + this._onClicked(this, 1); + } + ++ _createTitleActor() { ++ throw new GObject.NotImplementedError( ++ `_createTitleActor in ${this.constructor.name}`); ++ } ++ + _onClicked(_actor, _button) { + throw new GObject.NotImplementedError( + `_onClicked in ${this.constructor.name}`); +@@ -506,7 +511,7 @@ class WindowButton extends BaseButton { + this._unmanagingId = metaWindow.connect('unmanaging', + () => (this._unmanaging = true)); + +- this._windowTitle = new WindowTitle(this.metaWindow); ++ this._windowTitle = this._createTitleActor(); + this._button.set_child(this._windowTitle); + this.label_actor = this._windowTitle.label_actor; + +@@ -524,6 +529,10 @@ class WindowButton extends BaseButton { + this._updateStyle(); + } + ++ _createTitleActor() { ++ return new WindowTitle(this.metaWindow); ++ } ++ + _onClicked(actor, button) { + if (this._contextMenu.isOpen) { + this._contextMenu.close(); +@@ -709,13 +718,12 @@ class AppButton extends BaseButton { + + if (this._singleWindowMode) { + const [window] = windows; +- this._button.child = new WindowTitle(window); + this._contextMenu = new WindowContextMenu(this, window); + } else { +- this._button.child = new AppTitle(this.app); + this._contextMenu = new AppContextMenu(this); + } + ++ this._button.child = this._createTitleActor(); + this.label_actor = this._button.child.label_actor; + + this._contextMenu.connect('open-state-changed', _onMenuStateChanged); +@@ -724,6 +732,15 @@ class AppButton extends BaseButton { + this._contextMenuManager.addMenu(this._contextMenu); + } + ++ _createTitleActor() { ++ if (this._singleWindowMode) { ++ const [window] = this.getWindowList(); ++ return new WindowTitle(window); ++ } else { ++ return new AppTitle(this.app); ++ } ++ } ++ + _onClicked(actor, button) { + let menuWasOpen = this._menu.isOpen; + if (menuWasOpen) +-- +2.49.0 + + +From 3047fdf423f395ac677d1184a8a71680563635f5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 19 Jun 2024 13:01:37 +0200 +Subject: [PATCH 17/24] window-list: Rename XDND related methods and props + +The window list buttons themselves will become draggable, so +include "xdnd" in the existing drag handling to disambiguate +it. +--- + extensions/window-list/extension.js | 20 ++++++++++---------- + 1 file changed, 10 insertions(+), 10 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index a65e2108..263a2af8 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -912,11 +912,11 @@ class WindowList extends St.Widget { + 'window-created', (dsp, win) => this._addWindow(win, true)); + + this._dragBeginId = Main.xdndHandler.connect('drag-begin', +- this._monitorDrag.bind(this)); ++ this._monitorXdndDrag.bind(this)); + this._dragEndId = Main.xdndHandler.connect('drag-end', +- this._stopMonitoringDrag.bind(this)); +- this._dragMonitor = { +- dragMotion: this._onDragMotion.bind(this), ++ this._stopMonitoringXdndDrag.bind(this)); ++ this._xdndDragMonitor = { ++ dragMotion: this._onXdndDragMotion.bind(this), + }; + + this._dndTimeoutId = 0; +@@ -1117,16 +1117,16 @@ class WindowList extends St.Widget { + child?.animateOutAndDestroy(); + } + +- _monitorDrag() { +- DND.addDragMonitor(this._dragMonitor); ++ _monitorXdndDrag() { ++ DND.addDragMonitor(this._xdndDragMonitor); + } + +- _stopMonitoringDrag() { +- DND.removeDragMonitor(this._dragMonitor); ++ _stopMonitoringXdndDrag() { ++ DND.removeDragMonitor(this._xdndDragMonitor); + this._removeActivateTimeout(); + } + +- _onDragMotion(dragEvent) { ++ _onXdndDragMotion(dragEvent) { + if (Main.overview.visible || + !this.contains(dragEvent.targetActor)) { + this._removeActivateTimeout(); +@@ -1195,7 +1195,7 @@ class WindowList extends St.Widget { + global.display.disconnect(this._fullscreenChangedId); + global.display.disconnect(this._windowCreatedId); + +- this._stopMonitoringDrag(); ++ this._stopMonitoringXdndDrag(); + Main.xdndHandler.disconnect(this._dragBeginId); + Main.xdndHandler.disconnect(this._dragEndId); + +-- +2.49.0 + + +From 41888462f1675a1f9ce8abbdac5c66e154c24432 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 04:13:25 +0200 +Subject: [PATCH 18/24] window-list: Allow rearranging window buttons + +We currently sort buttons by the stable sequence to get a persistent +and predictable order. However some users want to customize that +order, and rearrange the buttons as they see fit. + +Support that use case by implementing drag-and-drop behavior based +on the overview's dash. + +Closes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/4 +--- + extensions/window-list/classic.css | 5 + + extensions/window-list/extension.js | 140 ++++++++++++++++++++++++++ + extensions/window-list/stylesheet.css | 14 ++- + 3 files changed, 157 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index 63e5e48c..a43f11cf 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -56,3 +56,8 @@ + color: #aaa; + background-color: #f9f9f9; + } ++ ++.window-button-drag-actor { ++ background-color: #ddd; ++ border-color: #888; ++} +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 263a2af8..574c85ac 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -18,6 +18,8 @@ const _ = Gettext.gettext; + const ICON_TEXTURE_SIZE = 24; + const DND_ACTIVATE_TIMEOUT = 500; + ++const MIN_DRAG_UPDATE_INTERVAL = 500 * GLib.TIME_SPAN_MILLISECOND; ++ + const GroupingMode = { + NEVER: 0, + AUTO: 1, +@@ -25,6 +27,14 @@ const GroupingMode = { + }; + + ++const DragPlaceholderItem = GObject.registerClass( ++class DragPlaceholderItem extends DashItemContainer { ++ _init() { ++ super._init(); ++ this.setChild(new St.Bin({ style_class: 'placeholder' })); ++ } ++}); ++ + function _minimizeOrActivateWindow(window) { + let focusWindow = global.display.focus_window; + if (focusWindow === window || +@@ -283,6 +293,18 @@ class AppTitle extends TitleWidget { + } + }); + ++const DragActor = GObject.registerClass( ++class DragActor extends St.Bin { ++ _init(source, titleActor) { ++ super._init({ ++ style_class: 'window-button-drag-actor', ++ child: titleActor, ++ width: source.width, ++ }); ++ ++ this.source = source; ++ } ++}); + + const BaseButton = GObject.registerClass({ + GTypeFlags: GObject.TypeFlags.ABSTRACT, +@@ -292,6 +314,10 @@ const BaseButton = GObject.registerClass({ + GObject.ParamFlags.READWRITE, + false), + }, ++ Signals: { ++ 'drag-begin': {}, ++ 'drag-end': {}, ++ }, + }, class BaseButton extends DashItemContainer { + _init(perMonitor, monitorIndex) { + this._perMonitor = perMonitor; +@@ -334,6 +360,15 @@ const BaseButton = GObject.registerClass({ + 'window-left-monitor', + this._windowEnteredOrLeftMonitor.bind(this)); + } ++ ++ this._button._delegate = this; ++ this._draggable = DND.makeDraggable(this._button); ++ this._draggable.connect('drag-begin', () => { ++ this._removeLongPressTimeout(); ++ this.emit('drag-begin'); ++ }); ++ this._draggable.connect('drag-cancelled', () => this.emit('drag-end')); ++ this._draggable.connect('drag-end', () => this.emit('drag-end')); + } + + get active() { +@@ -418,6 +453,17 @@ const BaseButton = GObject.registerClass({ + this._onClicked(this, 1); + } + ++ getDragActor() { ++ const titleActor = this._createTitleActor(); ++ titleActor.set({ abstractLabel: true }); ++ ++ return new DragActor(this, titleActor); ++ } ++ ++ getDragActorSource() { ++ return this; ++ } ++ + _createTitleActor() { + throw new GObject.NotImplementedError( + `_createTitleActor in ${this.constructor.name}`); +@@ -919,9 +965,19 @@ class WindowList extends St.Widget { + dragMotion: this._onXdndDragMotion.bind(this), + }; + ++ this._itemDragMonitor = { ++ dragMotion: this._onItemDragMotion.bind(this), ++ }; ++ + this._dndTimeoutId = 0; + this._dndWindow = null; + ++ this._dragPlaceholder = null; ++ this._dragPlaceholderPos = -1; ++ this._lastPlaceholderUpdate = 0; ++ ++ this._delegate = this; ++ + this._settings = ExtensionUtils.getSettings(); + this._settings.connect('changed::grouping-mode', + () => this._groupingModeChanged()); +@@ -1067,6 +1123,14 @@ class WindowList extends St.Widget { + _addButton(button, animate) { + this._settings.bind('display-all-workspaces', + button, 'ignore-workspace', Gio.SettingsBindFlags.GET); ++ ++ button.connect('drag-begin', ++ () => this._monitorItemDrag()); ++ button.connect('drag-end', () => { ++ this._stopMonitoringItemDrag(); ++ this._clearDragPlaceholder(); ++ }); ++ + this._windowList.add_child(button); + button.show(animate); + } +@@ -1117,6 +1181,82 @@ class WindowList extends St.Widget { + child?.animateOutAndDestroy(); + } + ++ _clearDragPlaceholder() { ++ this._dragPlaceholder?.animateOutAndDestroy(); ++ this._dragPlaceholder = null; ++ this._dragPlaceholderPos = -1; ++ } ++ ++ handleDragOver(source, _actor, x, _y, _time) { ++ if (!(source instanceof BaseButton)) ++ return DND.DragMotionResult.NO_DROP; ++ ++ const buttons = this._windowList.get_children().filter(c => c instanceof BaseButton); ++ const buttonPos = buttons.indexOf(source); ++ const numButtons = buttons.length; ++ let boxWidth = this._windowList.width; ++ ++ // Transform to window list coordinates for index calculation ++ // (mostly relevant for RTL to discard workspace indicator etc.) ++ x -= this._windowList.x; ++ ++ const rtl = this.text_direction === Clutter.TextDirection.RTL; ++ let pos = rtl ++ ? numButtons - Math.round(x * numButtons / boxWidth) ++ : Math.round(x * numButtons / boxWidth); ++ ++ pos = Math.clamp(pos, 0, numButtons); ++ ++ const timeDelta = ++ GLib.get_monotonic_time() - this._lastPlaceholderUpdate; ++ ++ if (pos !== this._dragPlaceholderPos && timeDelta >= MIN_DRAG_UPDATE_INTERVAL) { ++ this._clearDragPlaceholder(); ++ this._dragPlaceholderPos = pos; ++ ++ this._lastPlaceholderUpdate = GLib.get_monotonic_time(); ++ ++ // Don't allow positioning before or after self ++ if (pos === buttonPos || pos === buttonPos + 1) ++ return DND.DragMotionResult.CONTINUE; ++ ++ this._dragPlaceholder = new DragPlaceholderItem(); ++ const sibling = buttons[pos]; ++ if (sibling) ++ this._windowList.insert_child_below(this._dragPlaceholder, sibling); ++ else ++ this._windowList.insert_child_above(this._dragPlaceholder, null); ++ this._dragPlaceholder.show(true); ++ } ++ ++ return this._dragPlaceholder ++ ? DND.DragMotionResult.MOVE_DROP ++ : DND.DragMotionResult.NO_DROP; ++ } ++ ++ acceptDrop(source, _actor, _x, _y, _time) { ++ if (this._dragPlaceholderPos >= 0) ++ this._windowList.set_child_at_index(source, this._dragPlaceholderPos); ++ ++ this._clearDragPlaceholder(); ++ ++ return true; ++ } ++ ++ _monitorItemDrag() { ++ DND.addDragMonitor(this._itemDragMonitor); ++ } ++ ++ _stopMonitoringItemDrag() { ++ DND.removeDragMonitor(this._itemDragMonitor); ++ } ++ ++ _onItemDragMotion(dragEvent) { ++ if (!this._windowList.contains(dragEvent.targetActor)) ++ this._clearDragPlaceholder(); ++ return DND.DragMotionResult.CONTINUE; ++ } ++ + _monitorXdndDrag() { + DND.addDragMonitor(this._xdndDragMonitor); + } +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index ee500299..afa1aaf0 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -17,10 +17,19 @@ + height: 2.45em; + } + +-.window-button { ++.window-button, ++.window-button-drag-actor { + padding: 4px, 3px; + } + ++.window-button-drag-actor { ++ background-color: #444; ++ border-radius: 7px; ++ border-width: 2px; ++ border-color: #fff; ++ box-shadow: 0 1px 2px rgba(0,0,0,0.1); ++} ++ + .window-button:first-child:ltr { + padding-left: 2px; + } +@@ -41,7 +50,8 @@ + transition: 100ms ease; + } + +-.window-button > StWidget { ++.window-button > StWidget, ++.window-list .placeholder { + -st-natural-width: 18.75em; + max-width: 18.75em; + } +-- +2.49.0 + + +From 1175589f3e0a11ffda5460259a5fac161b79b864 Mon Sep 17 00:00:00 2001 +From: Jakub Steiner +Date: Thu, 3 Oct 2024 14:18:32 +0200 +Subject: [PATCH 19/24] window-list: Indicate drop target more prominently + +The drop target is the main focus of the drag operation, so make +its styling more prominent. +--- + extensions/window-list/classic.css | 5 ++++- + extensions/window-list/stylesheet.css | 6 ++++++ + 2 files changed, 10 insertions(+), 1 deletion(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index a43f11cf..81b161c9 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -23,7 +23,6 @@ + + .window-button > StWidget { + color: #000; +- background-color: transparent; + } + + .window-button:hover > StWidget { +@@ -61,3 +60,7 @@ + background-color: #ddd; + border-color: #888; + } ++ ++.window-list .placeholder { ++ border-color: rgba(0,0,0,0.5); ++} +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index afa1aaf0..24747442 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -56,6 +56,12 @@ + max-width: 18.75em; + } + ++.window-list .placeholder { ++ border: 1px solid rgba(255,255,255,0.4); ++ border-radius: 7px; ++ margin: 4px; ++} ++ + .window-button:hover > StWidget { + background-color: #303030; + } +-- +2.49.0 + + +From 9117f214d2ccf8cd7d88c7dca44127349eca2fb0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 3 Oct 2024 17:05:42 +0200 +Subject: [PATCH 20/24] window-list: Fade out drag source during drag + +During a drag operation, the focus is on the where to drop the dragged +item, not to identify it or its origin. +--- + extensions/window-list/extension.js | 18 ++++++++++++++++-- + 1 file changed, 16 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 574c85ac..5aca473c 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -20,6 +20,9 @@ const DND_ACTIVATE_TIMEOUT = 500; + + const MIN_DRAG_UPDATE_INTERVAL = 500 * GLib.TIME_SPAN_MILLISECOND; + ++const DRAG_OPACITY = 0.3; ++const DRAG_FADE_DURATION = 200; ++ + const GroupingMode = { + NEVER: 0, + AUTO: 1, +@@ -1124,9 +1127,20 @@ class WindowList extends St.Widget { + this._settings.bind('display-all-workspaces', + button, 'ignore-workspace', Gio.SettingsBindFlags.GET); + +- button.connect('drag-begin', +- () => this._monitorItemDrag()); ++ button.connect('drag-begin', () => { ++ button.ease({ ++ opacity: 255 * DRAG_OPACITY, ++ duration: DRAG_FADE_DURATION, ++ }); ++ ++ this._monitorItemDrag(); ++ }); + button.connect('drag-end', () => { ++ button.ease({ ++ opacity: 255, ++ duration: DRAG_FADE_DURATION, ++ }); ++ + this._stopMonitoringItemDrag(); + this._clearDragPlaceholder(); + }); +-- +2.49.0 + + +From 8a43b5dfe8fa5c6e896480c10b69fa51aedbaccf Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 9 Oct 2024 19:15:16 +0200 +Subject: [PATCH 21/24] window-list: Shrink drag-actor size during drags + +Like the previous commit, this helps with putting the focus on +the target location instead of the dragged item. +--- + extensions/window-list/extension.js | 32 +++++++++++++++++++++++++++-- + 1 file changed, 30 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 5aca473c..2d5ce525 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -23,6 +23,8 @@ const MIN_DRAG_UPDATE_INTERVAL = 500 * GLib.TIME_SPAN_MILLISECOND; + const DRAG_OPACITY = 0.3; + const DRAG_FADE_DURATION = 200; + ++const DRAG_RESIZE_DURATION = 400; ++ + const GroupingMode = { + NEVER: 0, + AUTO: 1, +@@ -307,6 +309,23 @@ class DragActor extends St.Bin { + + this.source = source; + } ++ ++ setTargetWidth(width) { ++ const currentWidth = this.width; ++ ++ // set width immediately so shell's DND code uses correct values ++ this.set({ width }); ++ ++ // then transition from the original to the new width ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { ++ this.set({ width: currentWidth }); ++ this.ease({ ++ width, ++ duration: DRAG_RESIZE_DURATION, ++ }); ++ return GLib.SOURCE_REMOVE; ++ }); ++ } + }); + + const BaseButton = GObject.registerClass({ +@@ -370,7 +389,10 @@ const BaseButton = GObject.registerClass({ + this._removeLongPressTimeout(); + this.emit('drag-begin'); + }); +- this._draggable.connect('drag-cancelled', () => this.emit('drag-end')); ++ this._draggable.connect('drag-cancelled', () => { ++ this._draggable._dragActor?.setTargetWidth(this.width); ++ this.emit('drag-end'); ++ }); + this._draggable.connect('drag-end', () => this.emit('drag-end')); + } + +@@ -460,7 +482,13 @@ const BaseButton = GObject.registerClass({ + const titleActor = this._createTitleActor(); + titleActor.set({ abstractLabel: true }); + +- return new DragActor(this, titleActor); ++ const dragActor = new DragActor(this, titleActor); ++ ++ const [, natWidth] = this.get_preferred_width(-1); ++ const targetWidth = Math.min(natWidth / 2, this.width); ++ dragActor.setTargetWidth(targetWidth); ++ ++ return dragActor; + } + + getDragActorSource() { +-- +2.49.0 + + +From a69401b07fa2e01932b847fdb3704755710e6c68 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 8 Oct 2024 19:25:53 +0200 +Subject: [PATCH 22/24] window-list: Handle DND events near the drop target + +Even with the previous change, the dragged actor has the tendency +of obscuring the possible drop target. To alleviate this, handle +DND events near drop targets as if they occurred on the target. +--- + extensions/window-list/extension.js | 26 ++++++++++++++++++++++++-- + 1 file changed, 24 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 2d5ce525..b6c00f8b 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -25,6 +25,8 @@ const DRAG_FADE_DURATION = 200; + + const DRAG_RESIZE_DURATION = 400; + ++const DRAG_PROXIMITY_THRESHOLD = 30; ++ + const GroupingMode = { + NEVER: 0, + AUTO: 1, +@@ -998,6 +1000,7 @@ class WindowList extends St.Widget { + + this._itemDragMonitor = { + dragMotion: this._onItemDragMotion.bind(this), ++ dragDrop: this._onItemDragDrop.bind(this), + }; + + this._dndTimeoutId = 0; +@@ -1294,11 +1297,30 @@ class WindowList extends St.Widget { + } + + _onItemDragMotion(dragEvent) { +- if (!this._windowList.contains(dragEvent.targetActor)) +- this._clearDragPlaceholder(); ++ const { source, targetActor, dragActor, x, y } = dragEvent; ++ ++ const hasTarget = this._windowList.contains(targetActor); ++ const isNear = Math.abs(y - this.y) < DRAG_PROXIMITY_THRESHOLD; ++ ++ if (hasTarget || isNear) ++ return this.handleDragOver(source, dragActor, x, y); ++ ++ this._clearDragPlaceholder(); + return DND.DragMotionResult.CONTINUE; + } + ++ _onItemDragDrop(dropEvent) { ++ if (this._dragPlaceholderPos < 0) ++ return DND.DragDropResult.CONTINUE; ++ ++ const { source } = dropEvent.dropActor; ++ this.acceptDrop(source); ++ dropEvent.dropActor.destroy(); ++ // HACK: SUCESS would make more sense, but results in gnome-shell ++ // skipping all drag-end code ++ return DND.DragDropResult.CONTINUE; ++ } ++ + _monitorXdndDrag() { + DND.addDragMonitor(this._xdndDragMonitor); + } +-- +2.49.0 + + +From b3dcf28250968bd6f3e1ddbab52ea9be768ad168 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 26 Jun 2024 00:58:18 +0200 +Subject: [PATCH 23/24] window-list: Add `id` property to buttons + +A string ID that uniquely identifies a button will allow to +serialize/deserialize the positions in the next commit. +--- + extensions/window-list/extension.js | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index b6c00f8b..feefc66e 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -608,6 +608,10 @@ class WindowButton extends BaseButton { + this._updateStyle(); + } + ++ get id() { ++ return `window:${this.metaWindow.get_id()}`; ++ } ++ + _createTitleActor() { + return new WindowTitle(this.metaWindow); + } +@@ -748,6 +752,10 @@ class AppButton extends BaseButton { + this._updateStyle(); + } + ++ get id() { ++ return `app:${this.app.get_id()}`; ++ } ++ + _windowEnteredOrLeftMonitor(metaDisplay, monitorIndex, metaWindow) { + if (this._windowTracker.get_window_app(metaWindow) === this.app && + monitorIndex === this._monitorIndex) { +-- +2.49.0 + + +From 9344fce65ca3b8705f05cc55395b2cd002a5dd83 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 24 Sep 2024 20:31:06 +0200 +Subject: [PATCH 24/24] window-list: Save and restore positions as runtime + state + +While it doesn't make sense for window list positions to be truly +persistent like dash items, some persistence is desirable. + +Otherwise any manually set position is lost when the extension +is disabled, for example when locking the screen. + +To address this, serialize the positions as runtime state on drop, +and restore them when populating the list. +--- + extensions/window-list/extension.js | 28 ++++++++++++++++++++++++++++ + 1 file changed, 28 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index feefc66e..bb9ca80f 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -27,6 +27,8 @@ const DRAG_RESIZE_DURATION = 400; + + const DRAG_PROXIMITY_THRESHOLD = 30; + ++const SAVED_POSITIONS_KEY = 'window-list-positions'; ++ + const GroupingMode = { + NEVER: 0, + AUTO: 1, +@@ -1145,6 +1147,8 @@ class WindowList extends St.Widget { + for (let i = 0; i < apps.length; i++) + this._addApp(apps[i], false); + } ++ ++ this._restorePositions(); + } + + _updateKeyboardAnchor() { +@@ -1293,9 +1297,33 @@ class WindowList extends St.Widget { + + this._clearDragPlaceholder(); + ++ this._savePositions(); ++ + return true; + } + ++ _getPositionStateKey() { ++ return `${SAVED_POSITIONS_KEY}:${this._monitor.index}`; ++ } ++ ++ _savePositions() { ++ const buttons = this._windowList.get_children() ++ .filter(b => b instanceof BaseButton); ++ global.set_runtime_state(this._getPositionStateKey(), ++ new GLib.Variant('as', buttons.map(b => b.id))); ++ } ++ ++ _restorePositions() { ++ const positions = global.get_runtime_state('as', ++ this._getPositionStateKey())?.deepUnpack() ?? []; ++ ++ for (const button of this._windowList.get_children()) { ++ const pos = positions.indexOf(button.id); ++ if (pos > -1) ++ this._windowList.set_child_at_index(button, pos); ++ } ++ } ++ + _monitorItemDrag() { + DND.addDragMonitor(this._itemDragMonitor); + } +-- +2.49.0 + diff --git a/SPECS/gnome-shell-extensions.spec b/SPECS/gnome-shell-extensions.spec index 6a9ec70..df127a0 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: 15%{?dist} +Release: 29%{?dist} Summary: Modify and extend GNOME Shell functionality and behavior License: GPLv2+ @@ -43,8 +43,17 @@ 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 +Patch024: 0001-desktop-icons-Notify-icon-drags.patch +Patch025: prefer-window-icon.patch +Patch026: 0001-desktop-icons-Handle-touch-events.patch +Patch027: more-ws-previews.patch +Patch028: 0001-Add-move-notifications-extension.patch +Patch029: 0001-workspace-indicator-Re-fittsify-workspace-previews.patch +Patch030: window-list-reordering.patch +Patch031: 0001-dash-to-panel-Remove-faulty-version-check.patch +Patch032: window-list-attention-indicator.patch +Patch033: 0001-dash-to-panel-Stop-messing-with-overview-allocation.patch +Patch034: improve-workspace-names.patch %description GNOME Shell Extensions is a collection of extensions providing additional and @@ -62,6 +71,7 @@ Enabled extensions: * gesture-inhibitor * launch-new-instance * heads-up-display + * move-notifications * native-window-placement * panel-favorites * places-menu @@ -200,6 +210,15 @@ This GNOME Shell extension modifies the behavior of clicking in the dash and app launcher to always launch a new application instance. +%package -n %{pkg_prefix}-move-notifications +Summary: Move GNOME Shell notifications +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-move-notifications +This GNOME Shell extension moves notification banners to a different position + + %package -n %{pkg_prefix}-heads-up-display Summary: Display persistent on-screen message Group: User Interface/Desktops @@ -393,6 +412,11 @@ workspaces. %{_datadir}/gnome-shell/extensions/launch-new-instance*/ +%files -n %{pkg_prefix}-move-notifications +%{_datadir}/gnome-shell/extensions/move-notifications*/ +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.move-notifications.gschema.xml + + %files -n %{pkg_prefix}-heads-up-display %{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.heads-up-display.gschema.xml %{_datadir}/gnome-shell/extensions/heads-up-display*/ @@ -449,17 +473,73 @@ workspaces. %changelog -* Fri Apr 19 2024 Florian Müllner - 40.7-15 +* Tue Nov 18 2025 Florian Müllner - 40.7-29 +- Fix workspace-indicator cleanups + Resolves: RHEL-129396 + +* Mon Jun 30 2025 Florian Müllner - 40.7-28 +- Make workspace names more prominent + Resolves: RHEL-96218 + +* Mon Jun 09 2025 Florian Müllner - 40.7-27 +- Fix window-list styling + Resolves: RHEL-95787 + +* Mon May 05 2025 Florian Müllner - 40.7-26 +- Stop overriding gnome-shell's overview allocation + Resolves: RHEL-70892 + +* Wed Jan 08 2025 Florian Müllner - 40.7-25 +- Indicate urgency-hint in window-list + Resolves: RHEL-73146 + +* Wed Dec 18 2024 Florian Müllner - 40.7-24 +- Change "move-clock" to "move-notifications" + Resolves: RHEL-33429 + +* Mon Dec 02 2024 Florian Müllner - 40.7-23 +- Fix app grid with dash-to-panel extension + Resolves: RHEL-69665 + +* Tue Nov 19 2024 Florian Müllner - 40.7-22 +- Fix another bug in window-list reordering backport + Resolves: RHEL-22692 + +* Fri Nov 15 2024 Florian Müllner - 40.7-21 +- Fix bug in window-list reordering backport + Resolves: RHEL-22692 + +* Thu Sep 26 2024 Florian Müllner - 40.7-20 +- Allow reordering items in window-list + Resolves: RHEL-22692 + +* Tue Jul 02 2024 Florian Müllner - 40.7-19 +- Extend workspace buttons to screen edge + Resolves: RHEL-43545 + +* Tue May 21 2024 Florian Müllner - 40.7-18 +- Add "move-clock" extension + Resolves: RHEL-33429 + +* Fri Apr 19 2024 Florian Müllner - 40.7-17 - Fix downstream stylesheets - Resolves: RHEL-31885 + Resolves: RHEL-25016 -* Thu Apr 18 2024 Florian Müllner - 40.7-14 +* Wed Apr 03 2024 Florian Müllner - 40.7-16 - Improve workspace previews - Resolves: RHEL-31885 + Resolves: RHEL-25016 -* Tue Mar 19 2024 Florian Müllner - 40.7-13 +* Tue Mar 19 2024 Florian Müllner - 40.7-15 +- Handle touch events in desktop icons + Resolves: RHEL-22713 + +* Tue Mar 19 2024 Florian Müllner - 40.7-14 - Prefer window icons in window list - Resolves: RHEL-29659 + Resolves: RHEL-24713 + +* Wed Mar 06 2024 Florian Müllner - 40.7-13 +- Notify on desktop icon drags + Resolves: RHEL-26989 * Fri Feb 02 2024 Florian Müllner - 40.7-12 - Hide classification banners from picks