Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 25 additions & 2 deletions rift.default.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ animation_fps = 100.0
focus_follows_mouse = true
mouse_follows_focus = true
mouse_hides_on_focus = true
# Which display a newly created window opens on:
# - "default": wherever macOS and the app put it
# - "focused": the display holding keyboard focus when the window appears
# - "cursor": the display under the mouse cursor when the window appears. Useful
# when a launcher activates an app whose other windows live on another display
# before asking it for a new window.
# A window whose app rule names a workspace always goes where the rule says.
new_window_display = "default"
# Treat displays stacked in macOS as a horizontal mouse crossing chain when
# they are physically side-by-side. At a left/right edge, the cursor enters
# the logical neighbor at the same vertical offset, if that point exists.
Expand Down Expand Up @@ -301,11 +309,26 @@ prevent_wrapping = false
reapply_app_rules_on_title_change = false

# Workspace-specific rules
# - workspace: target workspace by index (integer) or name (string)
# - workspace: target workspace by index (integer) or name (string); unnamed
# workspaces are named "Workspace N"
# - layout: layout mode to use ("traditional", "bsp", "stack", "master_stack", "scrolling", "floating")
# - display: the display the workspace lives on, as a display UUID (recommended:
# stable across reconnects, see `rift-cli query displays`) or a 0-based index in
# physical order (left to right, then top to bottom)
#
# A workspace bound to a display lives there while the display is connected:
# - switch_to_workspace focuses that display and switches the workspace there
# - move_window_to_workspace sends the window to that display
# - app_rules targeting the workspace place new windows on that display
# - when the display is disconnected the workspace falls back to the current
# display; once it reconnects, windows parked there are moved back
# Where several rules match a workspace, the last one giving a setting wins.
#
# workspace_rules = [
# { workspace = 1, layout = "bsp" },
# { workspace = "second", layout = "scrolling" }
# { workspace = "second", layout = "scrolling" },
# { workspace = "web", display = "37D8832A-2D66-02CA-B9F7-8F30A301B230" },
# { workspace = 4, display = 1 },
# ]
workspace_rules = []

Expand Down
5 changes: 4 additions & 1 deletion src/actor/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,7 +982,10 @@ impl State {
Duration::from_secs(60)
}

_ => Duration::ZERO,
// A brief grace period for everyone else: a busy app can reject the
// first accessibility requests, and giving up at once would leave it
// unmanaged until it relaunches.
_ => Duration::from_secs(2),
};
let mut sleep_dur = Duration::from_millis(20);
let mut sleep = || {
Expand Down
363 changes: 307 additions & 56 deletions src/actor/reactor.rs

Large diffs are not rendered by default.

31 changes: 23 additions & 8 deletions src/actor/reactor/events/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,10 @@ pub struct MoveWindowToDisplayPayload {
pub target_space: SpaceId,
pub target_screen: objc2_core_foundation::CGRect,
pub target_frame: objc2_core_foundation::CGRect,
/// Workspace on the target display to move into; its active one when `None`.
pub target_workspace: Option<crate::model::VirtualWorkspaceId>,
/// Activate the target workspace and focus the window there.
pub follow: bool,
}

pub fn handle_command_reactor_move_window_to_display(
Expand All @@ -462,13 +466,24 @@ pub fn handle_command_reactor_move_window_to_display(
return Ok(EventOutcome::no_change());
}

let response = layout.layout_engine.move_window_to_space(
&mut state.windows,
payload.source_space,
payload.target_space,
payload.target_screen.size,
payload.window,
);
let response = match payload.target_workspace {
None => layout.layout_engine.move_window_to_space(
&mut state.windows,
payload.source_space,
payload.target_space,
payload.target_screen.size,
payload.window,
),
Some(workspace) => layout.layout_engine.move_window_to_workspace_on_space(
&mut state.windows,
payload.source_space,
payload.target_space,
payload.target_screen.size,
payload.window,
workspace,
payload.follow,
),
};

if state
.windows
Expand All @@ -480,7 +495,7 @@ pub fn handle_command_reactor_move_window_to_display(
}

Ok(EventOutcome::layout_changed(false)
.with_layout_response(response, None)
.with_layout_response(response, payload.follow.then_some(payload.target_space))
.with_pre_layout_window_frame_write(payload.window, payload.target_frame, true))
}

Expand Down
21 changes: 21 additions & 0 deletions src/actor/reactor/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@ impl Reactor {
.expect("test window should have a WindowServer identity")
}

/// End the grace period of every cross-display move rift started.
pub fn expire_display_moves_for_test(&mut self) {
let expired = std::time::Instant::now() - std::time::Duration::from_millis(1);
for (_, deadline) in self.in_flight_display_moves.values_mut() {
*deadline = expired;
}
}

pub fn test_active_workspace_windows(&self, space: SpaceId) -> Vec<WindowId> {
self.layout_manager
.layout_engine
Expand Down Expand Up @@ -680,3 +688,16 @@ pub fn next_test_topology_revision() -> u64 {
next
})
}

/// A 1000x1000 display at the origin: the left one of two side by side.
pub fn left_screen() -> CGRect { CGRect::new(CGPoint::new(0., 0.), CGSize::new(1000., 1000.)) }

/// A 1000x1000 display just right of [`left_screen`].
pub fn right_screen() -> CGRect { CGRect::new(CGPoint::new(1000., 0.), CGSize::new(1000., 1000.)) }

/// Deliver the snapshot that follows connecting exactly these displays.
pub fn connect_displays(reactor: &mut Reactor, frames: Vec<CGRect>, spaces: Vec<Option<SpaceId>>) {
reactor.handle_event(space_state_event_with(frames, spaces, |state| {
state.display_set_changed = true
}));
}
Loading
Loading