Repository navigation
Conversation
sj-cstar
force-pushed
the
sjoshi/workspace-display-bindings
branch
2 times, most recently
from
October 5, 2026 09:32
fc40ac9 to
1996833
Compare
Contributor
Author
|
Updated as some fixes related to cross-display moves overlapped with my changes. |
An app actor gives up when registering for an app's accessibility notifications fails, and nothing tries again until the app relaunches or opens a new window, so its windows stay unmanaged. Registration had no grace period outside JetBrains IDEs and Emacs. - give every app a two second grace period for the first registration - retry attaching whenever the user activates an app that has no app actor 🤖 Generated with Claude Code
Rift hides the windows of inactive workspaces by parking them past the bottom-left or bottom-right corner of their display, leaving a sliver on screen. With a display arranged above another, a window parked past the upper display's bottom edge sits on the lower display. Rift then adopts it there, and it turns up in the wrong display's workspace. - add right-edge and left-edge placements, top-aligned with the display and clamped to its height - pick the placement that overlaps other displays least - treat a sliver in either dimension as hidden 🤖 Generated with Claude Code
After move-node carried the focused window onto another display, the next command still acted on the display the window had left: macOS moves its active display along with the key window only later. Make the window's new display the command context, as focus_display would, so a second move-node or a focus command acts where the window now is. 🤖 Generated with Claude Code
After rift moves a window to another display, macOS and the app take a moment to catch up. Until then the window server still reports the old display, and the app can send frame reports from before the move. Rift treated a live report of the old display as the user moving the window back, so it followed the window there, then followed its own move again once the window landed, and the window flipped between displays. move_window_to_display, move_workspace_to_display, overview drops and move-node across displays all go through this path. Record every cross-display move rift starts and, for two seconds, resolve the window to its new display instead of following reports of the old one. After that, a real move by the user is followed as before. 🤖 Generated with Claude Code
When a display disconnects, macOS moves its windows onto a remaining display. Rift's active-space reconcile, and rediscovery after display churn, then put each of those windows into the workspace that display was showing, so a window in workspace 9 turned up in workspace 1. The topology delta that keeps the workspace number runs after the reconcile has already moved the window, too late to help. Remember the native spaces of displays that leave the display set. A window moving off one of them keeps its workspace number, on both the reconcile and the rediscovery paths. Windows dragged between connected displays still join the workspace the target display shows. 🤖 Generated with Claude Code
…or cursor display A new window opens wherever macOS and the app put it. Launchers that activate an app before asking it for a window get the window next to that app's other windows, even while the user works on another display. Add settings.new_window_display: - "default": unchanged behaviour - "focused": move a new window to the display holding keyboard focus - "cursor": move a new window to the display under the mouse cursor A window whose app rule names a workspace stays where the rule puts it. The move is recorded as a display move in flight, so reports of the old display that arrive while macOS catches up do not pull the window back. 🤖 Generated with Claude Code
Workspace display bindings need to focus a display from inside other commands. Move the body of the focus_display handler into Reactor::focus_display_by_selector unchanged; the handler now calls it. No behaviour change. 🤖 Generated with Claude Code
Let a workspace rule name the display its workspace lives on:
workspace_rules = [{ workspace = "web", display = "37D8832A-…" }]
`display` is a DisplaySelector, the type display commands already take: a
display UUID (stable across reconnects) or an index in physical order.
Directions make no sense for a binding and are reported by validation, as
are empty UUIDs and rules that set neither layout nor display. `layout`
becomes optional so a rule can set only a display; rules keep matching by
index or name, the last one giving a setting winning.
This only adds the setting; the following commits make rift honour it.
🤖 Generated with Claude Code
…cling With workspace display bindings, every display started on default_workspace, so a display could come up showing a workspace bound to another one, and windows found on it were filed there. Cycling and back-and-forth could land on such workspaces too. On every snapshot and config reload the reactor now tells the workspace store, for each display, which workspace it starts on (default_workspace if it may live there, else its first own workspace, else the first unbound one) and which workspaces are bound to another connected display. The store starts new spaces on the former, and step_workspace and last_workspace skip the latter, so next/prev (commands, swipes and scroll overscroll), relative window moves, back-and-forth and switch_to_last_workspace stay on a display's own workspaces with no further routing. 🤖 Generated with Claude Code
While a bound workspace's display is connected, keep the workspace there: - switch_to_workspace, or picking the workspace in the overview, from another display focuses the owning display and switches there; when the workspace is already showing there it just focuses the display - move_window_to_workspace moves the window into the owning display's copy of the workspace, with or without follow; a window named by id may live on any display - move_workspace_to_display refuses to move a bound workspace off its display Bound moves go through the existing move-window-to-display workflow, which now takes an optional target workspace and follow flag, and LayoutEngine::move_window_to_workspace_on_space, which move_window_to_space now delegates to instead of repeating the relocation bookkeeping. 🤖 Generated with Claude Code
A window can end up in a workspace bound to a display it is not on: an app rule placed it there, it was dropped there in the overview, or macOS parked it on the remaining display while the owner was unplugged. And after a display joins, leaves or moves, a display can be left showing a workspace whose owner is back. After each event that can cause this (window discovery and placement, a display change, an overview drop, a config reload) re-apply the bindings once the event's outcome has settled window membership: - a display showing a workspace bound elsewhere switches back to its last own workspace, or the one it starts on; when the owner shows nothing it takes the workspace over, so the user keeps seeing it - windows in a bound workspace on another display move to the owner's copy of that workspace, without taking focus - a window macOS moves back onto its workspace's display keeps its workspace number instead of joining the workspace that display shows The work waits while the native topology is invalidated (sleep, wake, lock, display churn), since moving windows on transient data fights macOS; the authoritative snapshot that ends the instability runs it. 🤖 Generated with Claude Code
sj-cstar
force-pushed
the
sjoshi/workspace-display-bindings
branch
from
October 5, 2026 23:51
1996833 to
3526ac0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements per-display workspaces from #124: a workspace rule can bind its workspace to a display. Usability wise, It is built such that displays feel like a natural extension workspace-working area rather than introducing new set of commands. You can also re-arrange the displays, both focus and moves will adapt to it.
displayis aDisplaySelector, the same type the display commands take, so it can be a UUID or an index in physical order. While a bound workspace's display is connected, the workspace lives there:switch_to_workspaceand the overview focus the owning display and switch there.move_window_to_workspacesends the window there.move_workspace_to_displaywon't take a bound workspace off its display.When the display is unplugged, its workspaces fall back to the remaining displays and keep their windows. When it comes back, those windows move home. None of this runs while the spaces actor has the topology invalidated.
How it is built
The feature leans on existing primitives rather than adding parallel paths:
step_workspaceandlast_workspaceskip those, so next/prev (commands, swipes, overscroll), relative moves and back-and-forth need no routing of their own.move_window_to_spacenow delegates to the newmove_window_to_workspace_on_space.Also included
These came up while running the feature on a laptop with a display above it:
move-nodecarries the focused window to another display, the next command acts on that display.settings.new_window_display: open new windows on the focused or cursor display.Testing
cargo +nightly fmt --all --checkandcargo check. At the tip,cargo check --lockedandcargo nextest run --libpass, apart from tests that fail the same way on unmodifiedmainon this machine, because they read the live window server.My arrangement
