Repository navigation
Description
Distribution
Linux Mint 22 (wilma)
Package version
cinnamon 6.2.10 - the same code is on master (checked at d67b361)
Frequency
Always
Bug description
With Group windows by application off, you can drag window buttons into any order. That order
is thrown away whenever the button list is rebuilt. On a rebuild, each app's windows come back
most-recently-used first, so several windows of the same app (for example, a row of terminals) end
up scrambled.
The common trigger is a drag-and-drop on another workspace. Dropping a button rebuilds every
other workspace's list.
A monitor change rebuilds the current workspace's list the same way, as do a pin change or a theme
change. The order is also lost whenever the applet reloads.
Steps to reproduce
- Turn off Group windows by application in the Grouped window list applet. Keep the default of
showing only the current workspace's windows. - On workspace 2, open about five windows of one app (gnome-terminal is easiest), in order. Give them
titles such as 1 to 5, so the order is easy to see. - Click a few of them in a random order, so the order they were last used in differs from their
panel order. - Switch to workspace 1 and drag any panel button to a new position.
- Switch back to workspace 2.
Actual: the buttons on workspace 2 are in most-recently-used order.
Expected behavior
The buttons on workspace 2 are in the same order as before.
Additional information
Cause
References are to master.
applet.js:931: after a drop,moveLauncher()callsrefreshAllWorkspaces({ exceptCurrentOne: true }).
Other triggers callrefreshCurrentWorkspace()orrefreshAllWorkspaces():_onMonitorsChanged()via
_updateState()(:375),onFavoritesChange()(:82), andtheme-set(:291).workspace.js:195:refreshList()destroys every app group and rebuilds the list withloadFavorites()
andrefreshApps().workspace.js:213:loadFavorites()adds a pinned app's windows inapp.get_windows()order, which is
most recently used first.refreshApps()adds the rest inlist_windows()order. Neither looks at the
order the buttons were in before.
The drag case looks unintended. The refresh is there to keep pinned favourites consistent across
workspaces, but it also undoes the user's manual ordering on those workspaces.
Suggested fix
Have refreshList() remember the window order before it destroys the groups. After rebuilding, put each
app's window buttons back in that order, within the slots the rebuild gave that app. Pinned launchers,
other apps and grouped mode are unaffected. Windows not seen before follow, in the order they were
opened, so a fresh start shows windows in the order they were opened rather than most-recently-used
order.
I've been running this as a local patch on 6.2.10 and it fixes the problem. A drag on another
workspace, a monitor change and a forced refreshList() all leave the order intact. The 6.2.10 patch
(against appList.js) is below. Ported to master's names (appGroups, container), it would be
roughly as follows; I haven't run this version:
refreshList() {
const prevOrder = [];
this.appGroups.forEach(appGroup => prevOrder.push(...appGroup.groupState.metaWindows));
this.appGroups.forEach( appGroup => {
appGroup.destroy();
appGroup = null;
});
this.appGroups = [];
this.loadFavorites();
this.refreshApps();
this.restoreOrder(prevOrder);
this.scrollToLastFocusedApp();
}
// Put each app's window buttons back in their previous order, within the slots that app holds.
// Windows not seen before follow in the order they were opened.
restoreOrder(prevOrder) {
const rank = appGroup => {
const metaWindow = appGroup.groupState.metaWindows[0];
const i = prevOrder.indexOf(metaWindow);
return i > -1 ? i : prevOrder.length + metaWindow.get_stable_sequence();
};
const slotsByApp = new Map();
this.appGroups.forEach((appGroup, slot) => {
if (appGroup.groupState.metaWindows.length === 0) return;
const appId = appGroup.groupState.appId;
if (!slotsByApp.has(appId)) slotsByApp.set(appId, []);
slotsByApp.get(appId).push(slot);
});
const newAppGroups = this.appGroups.slice();
slotsByApp.forEach(slots => {
const groups = slots.map(slot => this.appGroups[slot]).sort((a, b) => rank(a) - rank(b));
slots.forEach((slot, n) => { newAppGroups[slot] = groups[n]; });
});
this.appGroups = newAppGroups;
this.appGroups.forEach((appGroup, slot) => this.container.set_child_at_index(appGroup.actor, slot));
}In grouped mode each app has a single slot, so restoreOrder() changes nothing there.
Related
On 6.2.10 there was a second symptom on rebuilds in ungrouped mode: every button of the last focused app
got the focus style. Master already fixes this in onFocusChange()
(lastFocusedApp === appId && getFocusState(lastFocused)), so it isn't part of this report. The 6.2.10
patch below has a small workaround for it at the end of restoreOrder(), which master doesn't need.
The 6.2.10 patch (against appList.js)
--- a/appList.js
+++ b/appList.js
@@ -170,6 +170,11 @@
}
refreshList() {
+ // LOCAL PATCH (~/scripts/cinnamon-gwl): remember the button order so a rebuild keeps it
+ const prevOrder = [];
+ for (let i = 0, len = this.appList.length; i < len; i++) {
+ prevOrder.push(...this.appList[i].groupState.metaWindows);
+ }
for (let i = 0, len = this.appList.length; i < len; i++) {
this.appList[i].destroy();
this.appList[i] = null;
@@ -177,6 +182,45 @@
this.appList = [];
this.loadFavorites();
this.refreshApps();
+ this.restoreOrder(prevOrder);
+ }
+
+ // LOCAL PATCH: a rebuild adds each app's windows most-recently-used first. Put each app's
+ // window buttons back in their previous order, within the slots that app already holds.
+ // Windows not seen before follow, in the order they were opened.
+ restoreOrder(prevOrder) {
+ const rank = appGroup => {
+ const metaWindow = appGroup.groupState.metaWindows[0];
+ const i = prevOrder.indexOf(metaWindow);
+ return i > -1 ? i : prevOrder.length + metaWindow.get_stable_sequence();
+ };
+ const slotsByApp = new Map();
+ this.appList.forEach((appGroup, slot) => {
+ if (appGroup.groupState.metaWindows.length === 0) return;
+ const appId = appGroup.groupState.appId;
+ if (!slotsByApp.has(appId)) slotsByApp.set(appId, []);
+ slotsByApp.get(appId).push(slot);
+ });
+ const newAppList = this.appList.slice();
+ slotsByApp.forEach(slots => {
+ const groups = slots.map(slot => this.appList[slot]).sort((a, b) => rank(a) - rank(b));
+ slots.forEach((slot, n) => { newAppList[slot] = groups[n]; });
+ });
+ this.appList = newAppList;
+ this.appList.forEach((appGroup, slot) => this.actor.set_child_at_index(appGroup.actor, slot));
+
+ // onFocusChange() marks every button of the workspace's last focused app. With one button
+ // per window that greys them all - keep the mark on that app's most recently used window only
+ if (this.state.settings.groupApps) return;
+ const appId = this.listState.lastFocusedApp;
+ const appGroups = this.appList.filter(
+ appGroup => appGroup.groupState.appId === appId && appGroup.groupState.metaWindows.length > 0
+ );
+ const userTime = appGroup => appGroup.groupState.metaWindows[0].get_user_time();
+ const recent = appGroups.reduce((a, b) => (userTime(b) > userTime(a) ? b : a), appGroups[0]);
+ appGroups.forEach(appGroup => {
+ if (appGroup !== recent) appGroup.actor.remove_style_pseudo_class('focus');
+ });
}
loadFavorites() {
Activity
greencardamom commentedon Nov 4, 2026
Thanks @jkramer5103 for the fix, and @clefebvre for merging it so quickly. Much appreciated.