Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions src/actor/reactor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,8 @@ pub enum ReactorCommand {
selector: DisplaySelector,
window_id: Option<u32>,
},
/// Toggle whether rift manages the current macOS space
ToggleSpaceActivated,
}

#[derive(Default, Debug, Clone)]
Expand Down Expand Up @@ -860,6 +862,9 @@ impl Reactor {
Event::Command(Command::Reactor(ReactorCommand::CloseWindow { window_server_id })) => {
CommandEventHandler::handle_command_reactor_close_window(self, window_server_id)
}
Event::Command(Command::Reactor(ReactorCommand::ToggleSpaceActivated)) => {
CommandEventHandler::handle_command_reactor_toggle_space_activated(self)
}
_ => (),
}
if let Some(raised_window) = raised_window {
Expand Down
10 changes: 10 additions & 0 deletions src/actor/reactor/events/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -443,4 +443,14 @@ impl CommandEventHandler {
warn!("Close window command ignored because no window is tracked");
}
}

pub fn handle_command_reactor_toggle_space_activated(reactor: &mut Reactor) {
if let Some(wm) = reactor.communication_manager.wm_sender.as_ref() {
let _ = wm.send(crate::actor::wm_controller::WmEvent::Command(
crate::actor::wm_controller::WmCommand::Wm(
crate::actor::wm_controller::WmCmd::ToggleSpaceActivated,
),
));
}
}
}
90 changes: 90 additions & 0 deletions src/bin/rift-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,19 @@ enum ExecuteCommands {
#[command(subcommand)]
display_cmd: DisplayCommands,
},
/// macOS space management commands
Space {
#[command(subcommand)]
space_cmd: SpaceCommands,
},
/// Save current state and exit rift
SaveAndExit,
/// Show timing metrics
ShowTiming,
/// Print layout tree to stdout (for debugging)
Debug,
/// Print serialized engine state to stdout (for debugging)
Serialize,
}

#[derive(Subcommand)]
Expand All @@ -126,6 +135,15 @@ enum WindowCommands {
Focus {
direction: String, // up, down, left, right
},
/// Focus a specific window by its internal ID (for scripting/automation)
FocusId {
/// Internal window ID in pid:idx format (e.g., "1234:0")
#[arg(long)]
window_id: String,
/// Optional window server ID for fallback focusing
#[arg(long)]
window_server_id: Option<u32>,
},
/// Toggle window floating state
ToggleFloat,
/// Toggle fullscreen mode (fills the whole screen, ignores outer gaps)
Expand Down Expand Up @@ -190,6 +208,17 @@ enum LayoutCommands {
ToggleFocusFloat,
}

#[derive(Subcommand)]
enum SpaceCommands {
/// Toggle whether rift manages the current macOS space
ToggleActivated,
/// Switch to an adjacent macOS space (Mission Control spaces, not virtual workspaces)
Switch {
/// Direction to switch (left, right, up, down)
direction: String,
},
}

#[derive(Subcommand)]
enum ConfigCommands {
/// Update animation settings
Expand Down Expand Up @@ -451,12 +480,19 @@ fn build_execute_request(execute: ExecuteCommands) -> Result<RiftRequest, String
map_mission_control_command(mission_cmd)?
}
ExecuteCommands::Display { display_cmd } => map_display_command(display_cmd)?,
ExecuteCommands::Space { space_cmd } => map_space_command(space_cmd)?,
ExecuteCommands::SaveAndExit => {
RiftCommand::Reactor(reactor::Command::Reactor(reactor::ReactorCommand::SaveAndExit))
}
ExecuteCommands::ShowTiming => RiftCommand::Reactor(reactor::Command::Metrics(
rift_wm::common::log::MetricsCommand::ShowTiming,
)),
ExecuteCommands::Debug => {
RiftCommand::Reactor(reactor::Command::Reactor(reactor::ReactorCommand::Debug))
}
ExecuteCommands::Serialize => {
RiftCommand::Reactor(reactor::Command::Reactor(reactor::ReactorCommand::Serialize))
}
};

if let RiftCommand::Config(rift_wm::common::config::ConfigCommand::GetConfig) = &rift_command {
Expand Down Expand Up @@ -495,6 +531,16 @@ fn map_window_command(cmd: WindowCommands) -> Result<RiftCommand, String> {
WindowCommands::Focus { direction } => Ok(RiftCommand::Reactor(reactor::Command::Layout(
LC::MoveFocus(direction.into()),
))),
WindowCommands::FocusId { window_id, window_server_id } => {
let wid = parse_window_id(&window_id)?;
let wsid = window_server_id.map(WindowServerId::new);
Ok(RiftCommand::Reactor(reactor::Command::Reactor(
reactor::ReactorCommand::FocusWindow {
window_id: wid,
window_server_id: wsid,
},
)))
}
WindowCommands::ToggleFloat => Ok(RiftCommand::Reactor(reactor::Command::Layout(
LC::ToggleWindowFloating,
))),
Expand Down Expand Up @@ -537,6 +583,36 @@ fn parse_window_server_id(input: &str) -> Result<WindowServerId, String> {
Ok(WindowServerId::new(value))
}

fn parse_window_id(input: &str) -> Result<rift_wm::actor::app::WindowId, String> {
use std::num::NonZeroU32;

let trimmed = input.trim();
if trimmed.is_empty() {
return Err("window_id cannot be empty".to_string());
}

let parts: Vec<&str> = trimmed.split(':').collect();
if parts.len() != 2 {
return Err(format!(
"Invalid window_id format '{}'. Expected 'pid:idx' (e.g., '1234:1')",
trimmed
));
}

let pid: i32 = parts[0]
.parse()
.map_err(|_| format!("Invalid pid '{}' in window_id", parts[0]))?;

let idx: u32 = parts[1]
.parse()
.map_err(|_| format!("Invalid idx '{}' in window_id", parts[1]))?;

let idx = NonZeroU32::new(idx)
.ok_or_else(|| "Window index must be non-zero".to_string())?;

Ok(rift_wm::actor::app::WindowId { pid, idx })
}

fn map_workspace_command(cmd: WorkspaceCommands) -> Result<RiftCommand, String> {
use layout::LayoutCommand as LC;
match cmd {
Expand Down Expand Up @@ -720,6 +796,20 @@ fn map_display_command(cmd: DisplayCommands) -> Result<RiftCommand, String> {
}
}

fn map_space_command(cmd: SpaceCommands) -> Result<RiftCommand, String> {
match cmd {
SpaceCommands::ToggleActivated => Ok(RiftCommand::Reactor(reactor::Command::Reactor(
reactor::ReactorCommand::ToggleSpaceActivated,
))),
SpaceCommands::Switch { direction } => {
let dir = parse_focus_direction(&direction)?;
Ok(RiftCommand::Reactor(reactor::Command::Reactor(
reactor::ReactorCommand::SwitchSpace(dir),
)))
}
}
}

fn build_display_selector(
direction: Option<String>,
index: Option<usize>,
Expand Down
Loading