Skip to content

Preserve tiling layouts across screen lock - #112

Open
italomlp wants to merge 1 commit into
oliwebd:mainfrom
italomlp:fix/preserve-layout-on-unlock
Open

italomlp wants to merge 1 commit into
oliwebd:mainfrom
italomlp:fix/preserve-layout-on-unlock

Conversation

@italomlp

@italomlp italomlp commented Oct 20, 2026 •

Copy link
Copy Markdown

Locking and unlocking GNOME tears down O-Tiling and reconstructs its layout from window rectangles. This can rearrange windows and lose split relationships and stack ordering.

Capture a plain, session-local layout snapshot before lock teardown and restore it using stable window sequence IDs on unlock. Preserve split directions and proportions, stack order and active tabs, and manual floating overrides. Prune missing or moved windows, tile new windows normally, and fall back to spatial reconstruction when monitor work areas change. Retain pending snapshots if another lock interrupts startup. Ordinary disable clears the snapshot; no live window/actor references or disk persistence are retained.

Validation:

  • 16 regression tests pass, covering snapshots, interrupted startup, closed/new windows, repeated rebuilds, split ratios, stack order, floating overrides, and changed work-area fallback.
  • ESLint, TypeScript checks, and clean make pack builds pass. Both packages contain the required files, have no forbidden extension-process Gdk imports, and meet the 10 MB size limit.
  • The reporter tested the combined local build on Ubuntu 26.04.1 / GNOME 50.1 and confirmed the reported layout-preservation issue is resolved.

Automated tests use lightweight GNOME boundary doubles. Multi-monitor hotplug and broader environment coverage remain unverified. This change is independent of #113.

@italomlp
italomlp marked this pull request as ready for review October 20, 2026 16:29

oliwebd commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Thanks for this, the approach is sound but it still needs major refinement.

Verified

  • Applies cleanly onto current main (7be3016).
  • tests/layout-snapshot.test.mjs (9) and tests/lock-layout.test.mjs (7) pass.
  • ESLint is clean on the changed files. tsc --noEmit shows one error in panel_settings.ts, which main also has, so it is not from this PR.
  • Every API the PR relies on exists with the expected signature: Forest.create_fork, parents, toplevel, stacks, Fork.set_ratio / prev_ratio, Stack.active_id, AutoTiler.update_stack / update_toplevel, Ext.on_tile_attach / add_tag / monitor_work_area.
  • Restore mirrors reconstruct.ts. Tab order and the active tab survive because Stack.clear() does not reset active_id.

Not verified

  • That sessionMode.isLocked is already true when disable() runs on lock.
  • Real GNOME behavior. The tests mock Node, Stack and the Shell, so they never touch real tab widgets. Please run the manual checklist from the README (nested splits, a stack, a floating window, several lock/unlock cycles, suspend/resume) and note the GNOME version.

EGO review risk
const lockLayout = new PendingLayout() is a module-scope instance that intentionally keeps data across disable(). The review guidelines say module-scope class instances must be destroyed and dynamic data cleared in disable(). I would rather not rely on a reviewer accepting an exception, so I suggest a different approach (below).

Suggested approach: save the layout in the extension schema
Instead of keeping a snapshot in module scope, store it in the extension's own GSettings schema and restore the tiling tree from there when the extension is re-enabled.

  1. Add one string key (for example lock-layout, default '') to schemas/org.gnome.shell.extensions.o-tiling.gschema.xml, next to the existing type="s" keys. The schema is already shipped in the zip.
  2. In disable(), when sessionMode.isLocked, write JSON.stringify(captureLayout(ext)) to that key before ext.destroy(), while the settings object is still alive. On an ordinary disable, clear the key.
  3. In enable(), read and parse the key, pass the result to auto_tile_on(false, false, snapshot), and reset the key to '' once restoration finishes.
  4. Delete PendingLayout and the module-scope lockLayout. Nothing then outlives disable() in memory, so the guideline concern goes away.

Two things to handle with this approach:

  • The window IDs come from get_stable_sequence(), which is only stable within one shell session. Make sure a stale value left in dconf after a crash, logout or reboot is never applied to different windows, for example by clearing the key on fresh startup.
  • Wrap the JSON parse so a corrupt value falls back to the existing reconstruct path.

Requested changes

  1. Switch to the schema-based persistence above.
  2. restoreWorkspace re-implements link_children from reconstruct.ts. Export that one and reuse it.
  3. Remove if (root.inner.kind !== NodeKind.FORK) return [];. It is unreachable after the wrap and would leave forks behind if it ever fired.
  4. Format the test files like the rest of the repo. The one-line style can read as generated code to EGO reviewers.
  5. Move the new README test section out from under "Happy tiling."

Minor, no action needed: if auto-tiling is soft-disabled, auto_tile_on returns before the snapshot is cleared, so it lingers until the next ordinary disable. The schema approach with an explicit clear on disable covers this too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants