diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 4f601975..a2a64262 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -7,6 +7,7 @@ clippy::doc_markdown )] +use std::env::temp_dir; use std::net::Shutdown; use std::path::PathBuf; use std::sync::atomic::Ordering; @@ -43,6 +44,7 @@ use komorebi::stackbar_manager; use komorebi::static_config::StaticConfig; use komorebi::theme_manager; use komorebi::transparency_manager; +use komorebi::window_manager::State; use komorebi::window_manager::WindowManager; use komorebi::windows_api::WindowsApi; use komorebi::winevent_listener; @@ -156,6 +158,9 @@ struct Opts { /// Path to a static configuration JSON file #[clap(short, long)] config: Option, + /// Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi + #[clap(long)] + clean_state: bool, } #[tracing::instrument] @@ -260,6 +265,13 @@ fn main() -> Result<()> { } } + let dumped_state = temp_dir().join("komorebi.state.json"); + + if !opts.clean_state && dumped_state.is_file() { + let state: State = serde_json::from_str(&std::fs::read_to_string(&dumped_state)?)?; + wm.lock().apply_state(state); + } + wm.lock().retile_all(false)?; listen_for_events(wm.clone()); @@ -290,6 +302,9 @@ fn main() -> Result<()> { tracing::error!("received ctrl-c, restoring all hidden windows and terminating process"); + let state = State::from(&*wm.lock()); + std::fs::write(dumped_state, serde_json::to_string_pretty(&state)?)?; + ANIMATION_ENABLED_PER_ANIMATION.lock().clear(); ANIMATION_ENABLED_GLOBAL.store(false, Ordering::SeqCst); wm.lock().restore_all_windows()?; diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 45615413..da5b89b5 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::env::temp_dir; use std::fs::File; use std::fs::OpenOptions; use std::io::BufRead; @@ -916,6 +917,12 @@ impl WindowManager { "received stop command, restoring all hidden windows and terminating process" ); + let state = &window_manager::State::from(&*self); + std::fs::write( + temp_dir().join("komorebi.state.json"), + serde_json::to_string_pretty(&state)?, + )?; + ANIMATION_ENABLED_PER_ANIMATION.lock().clear(); ANIMATION_ENABLED_GLOBAL.store(false, Ordering::SeqCst); self.restore_all_windows()?; diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 0ee3172c..60d2f265 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::collections::HashSet; use std::collections::VecDeque; +use std::env::temp_dir; use std::io::ErrorKind; use std::num::NonZeroUsize; use std::path::Path; @@ -362,6 +363,81 @@ impl WindowManager { WindowsApi::load_workspace_information(&mut self.monitors) } + #[tracing::instrument(skip(self, state))] + pub fn apply_state(&mut self, state: State) { + let mut can_apply = true; + + let state_monitors_len = state.monitors.elements().len(); + let current_monitors_len = self.monitors.elements().len(); + if state_monitors_len != current_monitors_len { + tracing::warn!( + "cannot apply state from {}; state file has {state_monitors_len} monitors, but only {current_monitors_len} are currently connected", + temp_dir().join("komorebi.state.json").to_string_lossy() + ); + + return; + } + + for monitor in state.monitors.elements() { + for workspace in monitor.workspaces() { + for container in workspace.containers() { + for window in container.windows() { + if window.exe().is_err() { + can_apply = false; + break; + } + } + } + + if let Some(window) = workspace.maximized_window() { + if window.exe().is_err() { + can_apply = false; + break; + } + } + + if let Some(container) = workspace.monocle_container() { + for window in container.windows() { + if window.exe().is_err() { + can_apply = false; + break; + } + } + } + + for window in workspace.floating_windows() { + if window.exe().is_err() { + can_apply = false; + break; + } + } + } + } + + if can_apply { + tracing::info!( + "applying state from {}", + temp_dir().join("komorebi.state.json").to_string_lossy() + ); + + for (monitor_idx, monitor) in self.monitors_mut().iter_mut().enumerate() { + for (workspace_idx, workspace) in monitor.workspaces_mut().iter_mut().enumerate() { + if let Some(state_monitor) = state.monitors.elements().get(monitor_idx) { + if let Some(state_workspace) = state_monitor.workspaces().get(workspace_idx) + { + *workspace = state_workspace.clone(); + } + } + } + } + } else { + tracing::warn!( + "cannot apply state from {}; some windows referenced in the state file no longer exist", + temp_dir().join("komorebi.state.json").to_string_lossy() + ); + } + } + #[tracing::instrument] pub fn reload_configuration() { tracing::info!("reloading configuration"); diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 572a9f35..309204a5 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -782,6 +782,9 @@ struct Start { /// Start masir in a background process for focus-follows-mouse #[clap(long)] masir: bool, + /// Do not attempt to auto-apply a dumped state temp file from a previously running instance of komorebi + #[clap(long)] + clean_state: bool, } #[derive(Parser)] @@ -2012,6 +2015,10 @@ fn main() -> Result<()> { flags.push(format!("'--tcp-port={port}'")); } + if arg.clean_state { + flags.push("'--clean-state'".to_string()); + } + let script = if flags.is_empty() { format!( "Start-Process '{}' -WindowStyle hidden",