From 8b189c3979c47bb61b0047b8017cf766674f488c Mon Sep 17 00:00:00 2001 From: Lukas-Klein Date: Wed, 25 Mar 2026 16:06:11 +0100 Subject: [PATCH 1/2] feat: add logic for move-everything-to-display feature --- src/actor/reactor/events/command.rs | 115 ++++++++++++++++++++++++++++ src/actor/wm_controller.rs | 8 +- src/bin/rift-cli.rs | 17 ++++ src/model/reactor.rs | 7 +- 4 files changed, 144 insertions(+), 3 deletions(-) diff --git a/src/actor/reactor/events/command.rs b/src/actor/reactor/events/command.rs index 57e3c953..f7406c32 100644 --- a/src/actor/reactor/events/command.rs +++ b/src/actor/reactor/events/command.rs @@ -213,6 +213,9 @@ impl CommandEventHandler { ReactorCommand::MoveWindowToDisplay { selector, window_id } => { Self::handle_command_reactor_move_window_to_display(reactor, &selector, window_id); } + ReactorCommand::MoveEverythingToDisplay { selector } => { + Self::handle_command_reactor_move_everything_to_display(reactor, &selector); + } } } @@ -506,6 +509,118 @@ impl CommandEventHandler { let _ = reactor.update_layout_or_warn(false, false); } + pub fn handle_command_reactor_move_everything_to_display( + reactor: &mut Reactor, + selector: &DisplaySelector, + ) { + if reactor.is_in_drag() { + warn!("Ignoring move-window-to-display while a drag is active"); + return; + } + + let Some(source_space) = reactor.workspace_command_space() else { + warn!("No source_space could be identified!"); + return; + }; + if !reactor.is_space_active(source_space) { + warn!("Move everything to display ignored: source space is inactive"); + return; + } + + let origin_screen = reactor.space_manager.screen_by_space(source_space); + + let origin_point = + origin_screen.map(|s| s.frame.mid()).or_else(|| reactor.current_screen_center()); + let target_screen = reactor.screen_for_selector(selector, origin_point).cloned(); + + let Some(target_screen) = target_screen else { + warn!( + ?selector, + "Move window to display ignored: target display not found" + ); + return; + }; + let Some(target_space) = target_screen.space else { + warn!( + uuid = ?target_screen.display_uuid, + "Move window to display ignored: display has no active space" + ); + return; + }; + if !reactor.is_space_active(target_space) { + warn!( + ?selector, + ?target_space, + "Move window to display ignored: target display space is inactive" + ); + return; + } + + if target_space == source_space { + return; + } + + let windows = + reactor.layout_manager.layout_engine.windows_in_active_workspace(source_space); + + for window in windows { + let (window_server_id, window_frame) = match reactor.window_manager.windows.get(&window) + { + Some(state) => (state.info.sys_id, state.frame_monotonic), + None => { + warn!(?window, "Move window to display ignored: unknown window"); + continue; + } + }; + let mut target_frame = window_frame; + let size = window_frame.size; + let dest_rect = target_screen.frame; + let mut origin = dest_rect.mid(); + origin.x -= size.width / 2.0; + origin.y -= size.height / 2.0; + let min = dest_rect.min(); + let max = dest_rect.max(); + origin.x = origin.x.max(min.x).min(max.x - size.width); + origin.y = origin.y.max(min.y).min(max.y - size.height); + target_frame.origin = origin; + + if let Some(app) = reactor.app_manager.apps.get(&window.pid) { + if let Some(wsid) = window_server_id { + let txid = reactor.transaction_manager.generate_next_txid(wsid); + reactor.transaction_manager.set_last_sent_txid(wsid, txid); + let _ = app.handle.send(crate::actor::app::Request::SetWindowFrame( + window, + target_frame, + txid, + true, + )); + } else { + let txid = TransactionId::default(); + let _ = app.handle.send(crate::actor::app::Request::SetWindowFrame( + window, + target_frame, + txid, + true, + )); + } + } + + if let Some(state) = reactor.window_manager.windows.get_mut(&window) { + state.frame_monotonic = target_frame; + } + + let response = reactor.layout_manager.layout_engine.move_window_to_space( + source_space, + target_space, + target_screen.frame.size, + window, + ); + + reactor.handle_layout_response(response, None); + } + + let _ = reactor.update_layout_or_warn(false, false); + } pub fn handle_command_reactor_close_window( reactor: &mut Reactor, window_server_id: Option, diff --git a/src/actor/wm_controller.rs b/src/actor/wm_controller.rs index 904d51fe..312cdd02 100644 --- a/src/actor/wm_controller.rs +++ b/src/actor/wm_controller.rs @@ -104,11 +104,15 @@ static BUILTIN_WM_CMD_VARIANTS: Lazy> = Lazy::new(|| { }); impl WmCmd { - pub fn snake_case_variants() -> &'static [String] { &BUILTIN_WM_CMD_VARIANTS } + pub fn snake_case_variants() -> &'static [String] { + &BUILTIN_WM_CMD_VARIANTS + } } impl WmCommand { - pub fn builtin_candidates() -> &'static [String] { WmCmd::snake_case_variants() } + pub fn builtin_candidates() -> &'static [String] { + WmCmd::snake_case_variants() + } } pub struct Config { diff --git a/src/bin/rift-cli.rs b/src/bin/rift-cli.rs index 8ac4da04..eb1b629d 100644 --- a/src/bin/rift-cli.rs +++ b/src/bin/rift-cli.rs @@ -351,6 +351,18 @@ enum DisplayCommands { #[arg(long)] window_id: Option, }, + /// Move all windows of the current workspace to a display by direction, index, or UUID + MoveEverything { + /// Direction relative to the window's current display (left, right, up, down). + #[arg(long)] + direction: Option, + /// Display index (0-based). + #[arg(long)] + index: Option, + /// Display UUID. + #[arg(long)] + uuid: Option, + }, } #[derive(Subcommand)] @@ -835,6 +847,11 @@ fn map_display_command(cmd: DisplayCommands) -> Result { window_id, }, ))), + DisplayCommands::MoveEverything { direction, index, uuid } => Ok(RiftCommand::Reactor( + reactor::Command::Reactor(reactor::ReactorCommand::MoveEverythingToDisplay { + selector: build_display_selector(direction, index, uuid)?, + }), + )), } } diff --git a/src/model/reactor.rs b/src/model/reactor.rs index 6c4d4a8c..a013ad2c 100644 --- a/src/model/reactor.rs +++ b/src/model/reactor.rs @@ -51,6 +51,9 @@ pub enum ReactorCommand { selector: DisplaySelector, window_id: Option, }, + MoveEverythingToDisplay { + selector: DisplaySelector, + }, } #[derive(Debug, Clone)] @@ -67,7 +70,9 @@ pub(crate) struct FullscreenSpaceTrack { } impl Default for FullscreenSpaceTrack { - fn default() -> Self { FullscreenSpaceTrack { windows: Vec::new() } } + fn default() -> Self { + FullscreenSpaceTrack { windows: Vec::new() } + } } #[derive(Debug, Clone)] From ea4593b73424c122b718673c533fe7079e5fdb65 Mon Sep 17 00:00:00 2001 From: Lukas-Klein Date: Tue, 7 Apr 2026 10:39:12 +0200 Subject: [PATCH 2/2] fix: linting errors --- src/actor/reactor/events/command.rs | 1 + src/actor/wm_controller.rs | 8 ++------ src/model/reactor.rs | 4 +--- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/actor/reactor/events/command.rs b/src/actor/reactor/events/command.rs index f7406c32..8aa2fdbd 100644 --- a/src/actor/reactor/events/command.rs +++ b/src/actor/reactor/events/command.rs @@ -621,6 +621,7 @@ impl CommandEventHandler { let _ = reactor.update_layout_or_warn(false, false); } + pub fn handle_command_reactor_close_window( reactor: &mut Reactor, window_server_id: Option, diff --git a/src/actor/wm_controller.rs b/src/actor/wm_controller.rs index 312cdd02..904d51fe 100644 --- a/src/actor/wm_controller.rs +++ b/src/actor/wm_controller.rs @@ -104,15 +104,11 @@ static BUILTIN_WM_CMD_VARIANTS: Lazy> = Lazy::new(|| { }); impl WmCmd { - pub fn snake_case_variants() -> &'static [String] { - &BUILTIN_WM_CMD_VARIANTS - } + pub fn snake_case_variants() -> &'static [String] { &BUILTIN_WM_CMD_VARIANTS } } impl WmCommand { - pub fn builtin_candidates() -> &'static [String] { - WmCmd::snake_case_variants() - } + pub fn builtin_candidates() -> &'static [String] { WmCmd::snake_case_variants() } } pub struct Config { diff --git a/src/model/reactor.rs b/src/model/reactor.rs index a013ad2c..85ff4bde 100644 --- a/src/model/reactor.rs +++ b/src/model/reactor.rs @@ -70,9 +70,7 @@ pub(crate) struct FullscreenSpaceTrack { } impl Default for FullscreenSpaceTrack { - fn default() -> Self { - FullscreenSpaceTrack { windows: Vec::new() } - } + fn default() -> Self { FullscreenSpaceTrack { windows: Vec::new() } } } #[derive(Debug, Clone)]