Skip to content

Add append_layout for declarative layout restore#9138

Open
codegax wants to merge 13 commits into
swaywm:masterfrom
codegax:add-append-layout
Open

Add append_layout for declarative layout restore#9138
codegax wants to merge 13 commits into
swaywm:masterfrom
codegax:add-append-layout

Conversation

@codegax
Copy link
Copy Markdown

@codegax codegax commented May 3, 2026

This adds append_layout, the i3 feature for restoring a workspace from a JSON tree where leaves are placeholders that swallow matching windows on map. I've wanted it for a while in my own setup and finally took a swing at it.

The implementation reuses the existing PCRE2 criteria engine for swallow matching, and placeholders sit on the existing view-less container path so no new node type is needed. A sway-save-tree --workspace <name|num> companion produces compatible JSON. Both i3's strict and concatenated-object forms are accepted, with app_id added to the swallows schema for Wayland views.

Two things I left out: floating_nodes are skipped with a warning (tiling only for now), and window_type accepts literal atom names since the criteria parser treats it as an enum. Happy to extend either if it's something you'd want. Tested on a real DRM session with a 70/30 tabbed layout restoring across reboots, and against i3-save-tree output.

codegax added 13 commits April 25, 2026 14:00
Add is_placeholder and swallows fields plus
container_init_border_rects, used by upcoming append_layout
support. No behaviour change.
The swallow walker matches against a view that has not yet been
attached to a container. Factor the view-intrinsic checks out of
criteria_matches_view and expose them as
criteria_matches_view_unmapped.
Parse i3-save-tree-style JSON into a detached subtree of
placeholder containers and append to a workspace. Reuses
criteria_parse for swallows. Tiling-only; floating_nodes are
skipped with a debug log.
Wires the JSON loader to a config/runtime command. Defers at
config-read time so workspaces exist when the layout is applied.
Before container_create in view_map, look up an unfilled
placeholder whose swallows match. On hit, install the view in
place and skip the workspace/sibling placement path. Border
config is preserved; for_window still runs post-placement.
ipc_json_describe_container emits the original swallows JSON for
unfilled placeholders. New sway-save-tree binary dumps a
workspace tiling tree as append_layout-compatible JSON,
synthesising swallows from app_id / class / instance.
Add sway(5) entry next to assign and a sway-save-tree(1) page;
register the new manpage in meson.build.
workspace_add_tiling wraps each top-level container with
container_split when config->default_layout is set, mutating
restored layouts. Use workspace_insert_tiling_direct so the
parsed subtree is attached as-is.
Swallow values were embedded directly into the synthesised
criteria string; a value containing " produced a malformed
[key="..."..."] that the criteria tokenizer could not parse, so
layouts with quoted titles failed to load. Escape " as \" before
embedding so the value survives the tokenizer and is recovered
by the existing criteria unescape.
A placeholder leaf has view==NULL, so arrange_container routes it
to its view-less branch, which calls arrange_children with the
container's layout. arrange_children asserts on L_NONE, so a
plain { swallows: [...] } JSON entry crashed assert-enabled
builds before any window could match.

Default the layout to L_HORIZ when JSON does not specify one.
The placeholder has no children, so the iteration short-circuits
without rendering anything until promoted.
--workspace was documented as accepting a name or a number, but
the lookup only compared against the IPC name, so common
workspaces such as "1: web" (num=1) could not be selected with
--workspace 1. Parse a numeric argument and also compare against
the num field. Update the manpage accordingly.
Strip C-style header comments before json parsing so the
i3-save-tree vim modeline does not break }\n{ concat detection.
Pass window_type to the criteria parser bare and unanchored,
since the parser treats it as an enum token and the i3 form
"^name$" was being silently dropped to ATOM_LAST.
Anchored ^name$ and bare names are accepted; everything else,
including regex alternation, returns a clear error instead of
silently falling through to ATOM_LAST.
@codegax codegax force-pushed the add-append-layout branch from 86cbe37 to 93931c3 Compare May 3, 2026 00:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant