Skip to content

feat: add i3-style scratchpad - #488

Open
rojnwa wants to merge 1 commit into
acsandmann:mainfrom
rojnwa:feat/scratchpad
Open

rojnwa wants to merge 1 commit into
acsandmann:mainfrom
rojnwa:feat/scratchpad

Conversation

@rojnwa

@rojnwa rojnwa commented Sep 11, 2026

Copy link
Copy Markdown

Adds an i3-style scratchpad (roadmap item "scratchpad windows/workspace").

Behaviour

  • move_to_scratchpad: park the focused window off every virtual workspace (i3 move scratchpad). The window becomes floating.
  • toggle_scratchpad (i3 scratchpad show), in order:
    1. focused window is a scratchpad window: hide it;
    2. a scratchpad window is visible on another workspace/display: pull it onto the current workspace and focus it;
    3. otherwise show the least recently parked window on the current workspace, centered, and focus it (repeated toggles cycle);
    4. no scratchpad windows at all: park the focused window.
  • First show after a tiled park sizes the window to 50% x 75% of the screen and centers it (i3 defaults). Later shows keep whatever geometry the user left.
  • While a scratchpad window is visible on a space, focus-follows-mouse does not raise other windows on that space, so the scratchpad stays on top until the user clicks another window or toggles it away.
  • Cmd-Tab / Dock activation of a parked window shows it instead of leaving focus on an off-screen window.
  • Tiling a scratchpad window (toggle_window_floating) removes it from the scratchpad; closing it does too.
  • Membership survives restarts through the layout file (additive, #[serde(default)], no schema bump) and is pruned with the same rules as floating state.
  • App rule scratchpad = true parks matching windows as soon as they appear (implies floating). Validation rejects it together with workspace, position, size, focus, or manage = false, since none of them apply to a parked window.
  • rift-cli execute window toggle-scratchpad | move-to-scratchpad.
[keys]
"Alt + Escape"         = "toggle_scratchpad"
"Alt + Shift + Escape" = "move_to_scratchpad"

app_rules = [
  { app_id = "net.kovidgoyal.kitty", title_substring = "Scratchpad", scratchpad = true },
]

Design

A parked window is a floating window with no workspace assignment (the analogue of i3's __i3_scratch), tracked in a small Scratchpad struct owned by LayoutEngine (parked queue, shown set, "fresh" set). Because parked windows are unassigned, workspace queries and counts exclude them for free, and showing one is just assigning it to the active workspace as a normal floating window. No new visibility machinery: the reactor parks them in the hidden screen corner using the existing HiddenWindowPlacement geometry in LayoutManager::calculate_layout.

Guards for the "unassigned but managed" state sit at existing choke points rather than per caller:

  • LayoutEngine::is_window_in_active_workspace returns false for parked windows (unassigned windows otherwise count as active).
  • assign_window_with_app_info_policy returns a new AppRuleResult::Unchanged for parked windows, so discovery re-inventory, title-change reapply, and space activation cannot re-assign them. The check runs after persistence identity restore so a persisted parked window maps onto its live id first.
  • add_window_to_layout ignores parked windows.
  • window_hidden_from_active_workspace (renamed from window_in_non_active_workspace) treats parked windows as hidden, so the existing post-discovery refocus handles a rule-parked window stealing focus on launch.

This supersedes the approach in #233/#378 (separate manager, hide_windows side channel in EventResponse, duplicated hide geometry in the reactor).

Tests

  • Unit tests for Scratchpad.
  • Reactor tests: park/unassign/off-screen placement, show geometry and raise, hide-and-refocus, cycling order, empty-toggle parks, pull from inactive workspace, unfloat leaves scratchpad, discovery keeps parked windows hidden, Cmd-Tab unparks, app rule parks and refocuses, focus-follows-mouse suppression while shown.
  • Persistence round-trip and ghost pruning; config parsing and validation; rift-cli mapping.

cargo test --lib on this branch: 599 passed, 1 failed. The failure, topology_change_clears_stale_pending_hide_target_before_next_workspace_layout, fails identically on main without this change (macOS 15.6, this machine); happy to look into it separately if it is not known.

Out of scope

Named scratchpads, per-rule scratchpad size/position, and a scratchpad flag in rift-cli query windows output. Easy follow-ups if wanted.

Add a scratchpad in the spirit of i3: park the focused window off every
virtual workspace with `move_to_scratchpad`, and summon or hide it with
`toggle_scratchpad`. Toggle hides the focused scratchpad window, otherwise
shows the least recently parked one on the current workspace (pulling a
member that is visible elsewhere), and parks the focused window when the
scratchpad is empty.

A parked window keeps its floating flag but has no workspace assignment; the
reactor moves it to the hidden screen corner like inactive-workspace windows.
The first show after a tiled park sizes the window to 50% x 75% of the screen
and centers it; later shows keep the geometry the user left. While a
scratchpad window is visible, focus-follows-mouse does not raise other
windows on that space, so it stays on top until clicked away or toggled.
Cmd-Tab onto a parked window shows it instead of focusing it off-screen.
Tiling the window removes it from the scratchpad. Membership is persisted
with the layout file and pruned with the same rules as floating state.

App rules accept `scratchpad = true` to park matching windows on launch
(implies floating; rejected together with workspace, position, size, focus,
or manage = false). rift-cli gains `execute window toggle-scratchpad` and
`move-to-scratchpad`; rift.default.toml documents both.
@acsandmann

Copy link
Copy Markdown
Owner

im reviewing, this looks good so far

@acsandmann acsandmann added the enhancement New feature or request label Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants