Skip to content

Commit

Permalink
fix: save/restore state on un/tiling instead of session lock/unlock
Browse files Browse the repository at this point in the history
While I don't know why if it's expected that my extension crashes GNOME
Shell when trying to access some window props during a screen lock or if
it's a bug in mutter... but either way. This should workaround the crash.

Fixes: #267
  • Loading branch information
Leleat committed Aug 5, 2024
1 parent 9c9b058 commit 4258520
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 36 deletions.
59 changes: 23 additions & 36 deletions tiling-assistant@leleat-on-github/extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,30 +274,6 @@ export default class TilingAssistantExtension extends Extension {

this._wasLocked = true;

const rectToJsObj = rect => rect && {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height
};

// can't just check for isTiled because maximized windows may
// have an untiledRect as well in case window gaps are used
const openWindows = this._twm.getWindows(true);
const savedWindows = openWindows.filter(w => w.untiledRect).map(w => {
return {
windowId: w.get_stable_sequence(),
isTiled: w.isTiled,
tiledRect: rectToJsObj(w.tiledRect),
untiledRect: rectToJsObj(w.untiledRect)
};
});

const saveObj = {
'windows': savedWindows,
'tileGroups': Array.from(this._twm.getTileGroups())
};

const userPath = GLib.get_user_config_dir();
const parentPath = GLib.build_filenamev([userPath, '/tiling-assistant']);
const parent = Gio.File.new_for_path(parentPath);
Expand All @@ -310,7 +286,7 @@ export default class TilingAssistantExtension extends Extension {
}
}

const path = GLib.build_filenamev([parentPath, '/tiledSessionRestore.json']);
const path = GLib.build_filenamev([parentPath, '/tiledSessionRestore2.json']);
const file = Gio.File.new_for_path(path);

try {
Expand All @@ -321,8 +297,13 @@ export default class TilingAssistantExtension extends Extension {
}
}

file.replace_contents(JSON.stringify(saveObj), null, false,
Gio.FileCreateFlags.REPLACE_DESTINATION, null);
file.replace_contents(
JSON.stringify(Object.fromEntries(this._twm.getTilingStates())),
null,
false,
Gio.FileCreateFlags.REPLACE_DESTINATION,
null
);
}

/**
Expand All @@ -336,7 +317,7 @@ export default class TilingAssistantExtension extends Extension {
this._wasLocked = false;

const userPath = GLib.get_user_config_dir();
const path = GLib.build_filenamev([userPath, '/tiling-assistant/tiledSessionRestore.json']);
const path = GLib.build_filenamev([userPath, '/tiling-assistant/tiledSessionRestore2.json']);
const file = Gio.File.new_for_path(path);
if (!file.query_exists(null))
return;
Expand All @@ -353,16 +334,17 @@ export default class TilingAssistantExtension extends Extension {
if (!success || !contents.length)
return;

const openWindows = this._twm.getWindows(true);
const saveObj = JSON.parse(new TextDecoder().decode(contents));
const states = JSON.parse(new TextDecoder().decode(contents));
const windowObjects = states.windows;
const openWindows = global.display.list_all_windows();

openWindows.forEach(window => {
const savedState = windowObjects[window.get_id()];

const windowObjects = saveObj['windows'];
windowObjects.forEach(wObj => {
const { windowId, isTiled, tiledRect, untiledRect } = wObj;
const window = openWindows.find(w => w.get_stable_sequence() === windowId);
if (!window)
if (!savedState)
return;

const { isTiled, tiledRect, untiledRect } = savedState;
const jsToRect = jsRect => jsRect && new Rect(
jsRect.x, jsRect.y, jsRect.width, jsRect.height
);
Expand All @@ -372,13 +354,18 @@ export default class TilingAssistantExtension extends Extension {
window.untiledRect = jsToRect(untiledRect);
});

const tileGroups = new Map(saveObj['tileGroups']);
const tileGroups = new Map(states.tileGroups);
this._twm.setTileGroups(tileGroups);
openWindows.forEach(w => {
if (tileGroups.has(w.get_id())) {
const group = this._twm.getTileGroupFor(w);
this._twm.updateTileGroup(group);
}
});

this._twm.setTilingStates({
windows: states.windows,
tileGroups: states.tileGroups
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,8 @@ export default class TilingResizeHandler {
newGrabbedTiledRectHeight
);

Twm.updateSavedTilingState(window);

// Now calculate the new tiledRects for the windows, which were resized
// along the window based on the diff of the window's tiledRect pre
// and after the grab.
Expand Down Expand Up @@ -379,6 +381,8 @@ export default class TilingResizeHandler {
win.tiledRect.y += isResizingS ? tiledRectDiffHeight : 0;
win.tiledRect.height -= tiledRectDiffHeight;
}

Twm.updateSavedTilingState(win);
});

this._preGrabRects.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ export class TilingWindowManager {
// { windowId1: [windowIdX, windowIdY, ...], windowId2: [...], ... }
this._tileGroups = new Map();

/**
* The states of the windows tiling states during the runtime of the extension.
* It is used for for saving/restoring the states when un/locking the session.
*/
this._tilingStates = new Map([
// `windows` value will map the window.id to an object with isTiled,
// tiledRect, and untiledRect props
['windows', {}],
// `tileGroups` value will be an array of tuples (arrays). The first value
// in a tuple will be the window.id. The second tuple value is an array of
// window.ids of the windows in the tile group.
['tileGroups', []]
]);

// [windowIds]
this._unmanagingWindows = [];

Expand All @@ -38,6 +52,7 @@ export class TilingWindowManager {
global.workspace_manager.disconnectObject(this);

this._tileGroups.clear();
this._tilingStates.clear();
this._unmanagingWindows = [];

if (this._openAppTiledTimerId) {
Expand Down Expand Up @@ -220,19 +235,22 @@ export class TilingWindowManager {
// Maximized with gaps
if (maximize) {
this._updateGappedMaxWindowSignals(window);
this.updateSavedTilingState(window);

// Tiled window
} else if (!fakeTile) {
// Make the tile group only consist of the window itself to stop
// resizing or raising together. Also don't call the Tiling Popup.
if (Settings.getBoolean('disable-tile-groups') || ignoreTA) {
this.updateTileGroup([window]);
this.updateSavedTilingState(window);
return;
}

// Setup the (new) tileGroup to raise tiled windows as a group
const topTileGroup = this._getWindowsForBuildingTileGroup(monitor);
this.updateTileGroup(topTileGroup);
this.updateSavedTilingState(window);

this.emit('window-tiled', window);

Expand Down Expand Up @@ -308,6 +326,8 @@ export class TilingWindowManager {
window.tiledRect = null;
window.untiledRect = null;

this.deleteSavedTilingState(window);

this.emit('window-untiled', window);
}

Expand Down Expand Up @@ -366,6 +386,15 @@ export class TilingWindowManager {
this.updateTileGroup(tileGroup);
}

static getTilingStates() {
return this._tilingStates;
}

static setTilingStates({ windows, tileGroups }) {
this._tilingStates.set('windows', windows);
this._tilingStates.set('tileGroups', tileGroups);
}

/**
* @returns {Map<number,number>}
* For ex: { windowId1: [windowIdX, windowIdY, ...], windowId2: ... }
Expand Down Expand Up @@ -1032,6 +1061,58 @@ export class TilingWindowManager {
app.open_new_window(-1);
}

static updateSavedTilingState(window) {
// window state
const windowState = this._tilingStates.get('windows')[window.get_id()];
const rectToJsObject = rect => {
return rect
? { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
: undefined;
};

if (windowState) {
windowState.isTiled = window.isTiled;
windowState.tiledRect = rectToJsObject(window.tiledRect);
windowState.untiledRect = rectToJsObject(window.untiledRect);
} else {
this._tilingStates.get('windows')[window.get_id()] = {
isTiled: window.isTiled,
tiledRect: rectToJsObject(window.tiledRect),
untiledRect: rectToJsObject(window.untiledRect)
};
}

// tile groups
const tileGroups = this._tilingStates.get('tileGroups');
const group = tileGroups.find(g => g[0] === window.get_id());

if (group) {
group.splice(1);
group.push(this._tileGroups.get(window.get_id()));
} else {
tileGroups.push([
window.get_id(),
this._tileGroups.get(window.get_id())
]);
}
}

static deleteSavedTilingState(window) {
const savedTilingState = this._tilingStates.get('windows');

if (!savedTilingState[window.get_id()])
return;

delete savedTilingState[window.get_id()];

this._tilingStates.set(
'tileGroups',
this._tilingStates.get('tileGroups').filter(group => {
return group[0] !== window.get_id();
})
);
}

/**
* Gets the top windows, which are supposed to be in a tile group. That
* means windows, which are tiled, and don't overlap each other.
Expand Down

0 comments on commit 4258520

Please sign in to comment.