Repository navigation
Conversation
fc40ac9 to
1996833
Compare
|
Updated as some fixes related to cross-display moves overlapped with my changes. |
1996833 to
3526ac0
Compare
With scope = "global" every display still holds its own copy of every workspace, but a workspace now has one owner: the display whose copy of it is showing or holds windows. A workspace rule binding it to a display forces the owner. A workspace no display owns does not exist yet, as in i3. The reactor feeds the owners to the workspace store through the display bindings acsandmann#545 added for workspace rules, so the store needs no new state: a display starts on default_workspace if no other display owns it, else on the first workspace nobody owns (displays connected together claim their starting workspaces left to right), and cycling, relative window moves and swipes on a display skip the workspaces owned by another one. Unlike rule bindings the owners change with every switch and window move, so they are recomputed after each event. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VGErZJPSEqYf1pfQ3crYKa
… snapshot The passes acsandmann#545 runs for workspace rule bindings now run for derived owners too: a display showing a workspace another display owns switches back to its last own workspace, or the one it starts on, and windows in a workspace's copy on another display move to the owner's copy. With shared workspaces that repairs what the routing cannot prevent: a layout restored from per-display workspaces (two displays showing the same workspace; the copy with more windows wins, then the display the user is on, then the leftmost), a window an app rule or an overview drop put in a workspace that lives elsewhere, and a display back from native fullscreen. The passes stay edge-triggered, idempotent and bounded, and run after every full snapshot in global scope since a restore or a fullscreen exit announces itself no other way. A display in native fullscreen keeps its workspaces: the spaces actor reports it without a Space, so the owner is looked up through the user Space it left. Switching to or moving a window into one of its workspaces is refused with a warning until it is back, and cycling on the other displays skips them, instead of opening second copies that would have to be merged later. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VGErZJPSEqYf1pfQ3crYKa
3526ac0 to
de126c5
Compare
With scope = "global" every display still holds its own copy of every workspace, but a workspace now has one owner: the display whose copy of it is showing or holds windows. A workspace rule binding it to a display forces the owner. A workspace no display owns does not exist yet, as in i3. The reactor feeds the owners to the workspace store through the display bindings acsandmann#545 added for workspace rules, so the store needs no new state: a display starts on default_workspace if no other display owns it, else on the first workspace nobody owns (displays connected together claim their starting workspaces left to right), and cycling, relative window moves and swipes on a display skip the workspaces owned by another one. Unlike rule bindings the owners change with every switch and window move, so they are recomputed after each event. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VGErZJPSEqYf1pfQ3crYKa
… snapshot The passes acsandmann#545 runs for workspace rule bindings now run for derived owners too: a display showing a workspace another display owns switches back to its last own workspace, or the one it starts on, and windows in a workspace's copy on another display move to the owner's copy. With shared workspaces that repairs what the routing cannot prevent: a layout restored from per-display workspaces (two displays showing the same workspace; the copy with more windows wins, then the display the user is on, then the leftmost), a window an app rule or an overview drop put in a workspace that lives elsewhere, and a display back from native fullscreen. The passes stay edge-triggered, idempotent and bounded, and run after every full snapshot in global scope since a restore or a fullscreen exit announces itself no other way. A display in native fullscreen keeps its workspaces: the spaces actor reports it without a Space, so the owner is looked up through the user Space it left. Switching to or moving a window into one of its workspaces is refused with a warning until it is back, and cycling on the other displays skips them, instead of opening second copies that would have to be merged later. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01VGErZJPSEqYf1pfQ3crYKa
4e27f54 to
0a8ffd7
Compare
|
okay this is next on the docket |
|
I've been running this for a while. It works well for my setup. One gap which I want to create an issue for discussion: moving focus between displays when several windows border the edge. Will do this after merging. My arrangement: the Dell above the laptop, with the Dell split into B and C. Moving up from A has two candidates. With the traditional layout, rift always picks C: Position doesn't settle it either: A sits under the split between B and C. Proposal: Use the arrangement as much as possible, but also remember the path in the case of such cases
|
|
I also now think we should deprecate/remove |
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
…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
0a8ffd7 to
4413f64
Compare
|
synced to main |
|
@acsandmann let me know if you'd need anything. I'd like to work on the move_focus improvement once this lands. |
Try opening a stacked pr, I haven't used it yet but that would work well so its based on this one. Trying to land 0.7.0 to get some bug fixes out the door but that means a lot of stuff has to be refined in terms of all the ui that will be released then. |
|
hey guys, i was looking into the implementation and i decided to go a different direction to implement this feature. my wip is in feat/multidisplayv2 and it should generally work the same as this but with a cleaner underlying system as this is clean but adds a lot of complexity. if you want to start move_focus work, open a pr to that branch. (The bulk of the changes are that workspaces will no longer be keyed by space id and that part of ownership will be abstracted away Edit: you will also of course be credited as a co author. And, if you have any issues with that branch let me know so it can be as good as possible. |
|
#603 is working quite well for me with no issues. Feel free to open a pr to its branch or suggest changes if you have any differing ideas as this is a significant change. This would not have happened without your pr so thank you |
|
Not a problem, I'd prefer a robust cleaner solution anyway @acsandmann , which seems like you have got one. Haven't had the chance to play around it, but will soon. |
it should work almost perfectly but id like to test for a while before merging due to the magnitude of the changes |
|
Will check and report back soon. |
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
