diff --git a/README.md b/README.md index 28819b0db..744c739a2 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,12 @@ general: # another application). startup_commands: [] + # Commands to run just before the WM is shutdown. + shutdown_commands : [] + + # Commands to run after the WM config has reloaded. + config_reload_commands: [] + # Whether to automatically focus windows underneath the cursor. focus_follows_cursor: false diff --git a/packages/watcher/src/main.rs b/packages/watcher/src/main.rs index 0fae81c2a..9f074b185 100644 --- a/packages/watcher/src/main.rs +++ b/packages/watcher/src/main.rs @@ -31,7 +31,7 @@ async fn main() -> anyhow::Result<()> { match subscribe_res { Ok(_) => info!("WM exited successfully. Skipping watcher cleanup."), Err(err) => { - info!("Running watcher cleanup. WM exitted unexpectedly: {}", err); + info!("Running watcher cleanup. WM exited unexpectedly: {}", err); let managed_windows = managed_handles .into_iter() diff --git a/packages/wm/src/app_command.rs b/packages/wm/src/app_command.rs index 4e6c8c642..33835e8f4 100644 --- a/packages/wm/src/app_command.rs +++ b/packages/wm/src/app_command.rs @@ -10,7 +10,7 @@ use crate::{ common::{ commands::{ cycle_focus, disable_binding_mode, enable_binding_mode, - reload_config, shell_exec, + platform_sync, reload_config, shell_exec, }, Direction, LengthValue, RectDelta, TilingDirection, }, @@ -604,6 +604,14 @@ impl InvokeCommand { enable_binding_mode(name, state, config) } InvokeCommand::WmExit => { + Self::run_multiple( + config.value.general.shutdown_commands.clone(), + subject_container, + state, + config, + )?; + platform_sync(state, config)?; + state.emit_exit(); Ok(()) } @@ -616,9 +624,48 @@ impl InvokeCommand { Ok(()) } - InvokeCommand::WmReloadConfig => reload_config(state, config), + InvokeCommand::WmReloadConfig => { + reload_config(state, config)?; + + Self::run_multiple( + config.value.general.config_reload_commands.clone(), + subject_container, + state, + config, + )?; + platform_sync(state, config)?; + Ok(()) + } } } + + pub fn run_multiple( + commands: Vec, + subject_container: Container, + state: &mut WmState, + config: &mut UserConfig, + ) -> anyhow::Result { + let mut current_subject_container = subject_container; + + for command in commands { + command.run(current_subject_container.clone(), state, config)?; + + // Update the subject container in case the container type changes. + // For example, when going from a tiling to a floating window. + current_subject_container = + match current_subject_container.is_detached() { + false => current_subject_container, + true => { + match state.container_by_id(current_subject_container.id()) { + Some(container) => container, + None => break, + } + } + } + } + + Ok(current_subject_container.id()) + } } impl<'de> Deserialize<'de> for InvokeCommand { diff --git a/packages/wm/src/user_config.rs b/packages/wm/src/user_config.rs index 458b1440e..07787820b 100644 --- a/packages/wm/src/user_config.rs +++ b/packages/wm/src/user_config.rs @@ -395,6 +395,14 @@ pub struct GeneralConfig { /// launch another application). #[serde(default)] pub startup_commands: Vec, + + /// Commands to run just before the WM is shutdown. + #[serde(default)] + pub shutdown_commands: Vec, + + /// Commands to run after the WM config has reloaded. + #[serde(default)] + pub config_reload_commands: Vec, } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/packages/wm/src/wm.rs b/packages/wm/src/wm.rs index a5c7978a9..210d69385 100644 --- a/packages/wm/src/wm.rs +++ b/packages/wm/src/wm.rs @@ -17,7 +17,6 @@ use crate::{ }, platform::PlatformEvent, }, - containers::traits::CommonGetters, user_config::UserConfig, wm_event::WmEvent, wm_state::WmState, @@ -110,7 +109,7 @@ impl WindowManager { let state = &mut self.state; // Get the container to run WM commands with. - let mut subject_container = match subject_container_id { + let subject_container = match subject_container_id { Some(id) => state.container_by_id(id).with_context(|| { format!("No container found with the given ID '{}'.", id) })?, @@ -119,22 +118,14 @@ impl WindowManager { .context("No subject container for command.")?, }; - for command in commands { - command.run(subject_container.clone(), state, config)?; - - // Update the subject container in case the container type changes. - // For example, when going from a tiling to a floating window. - subject_container = match subject_container.is_detached() { - false => subject_container, - true => match state.container_by_id(subject_container.id()) { - Some(container) => container, - None => break, - }, - } - } - + let new_subject_container_id = InvokeCommand::run_multiple( + commands, + subject_container, + state, + config, + )?; platform_sync(state, config)?; - Ok(subject_container.id()) + Ok(new_subject_container_id) } } diff --git a/resources/assets/sample-config.yaml b/resources/assets/sample-config.yaml index 25ec87dfa..2d7a255be 100644 --- a/resources/assets/sample-config.yaml +++ b/resources/assets/sample-config.yaml @@ -2,6 +2,16 @@ general: # Commands to run when the WM has started (e.g. to run a script or launch # another application). Here we are running a batch script to start Zebar. startup_commands: ['shell-exec %userprofile%/.glzr/zebar/start.bat'] + # Similarly commands can be executed just before the WM is shutdown + # and after the config has reloaded. + # Here we shutdown Zebar when the WM is shutdown. + shutdown_commands: ['shell-exec taskkill /IM zebar.exe /F'] + # Here we restart Zebar to reload the Zebar config when reloading the + # WM config. + config_reload_commands: [ + 'shell-exec taskkill /IM zebar.exe /F', + 'shell-exec %userprofile%/.glzr/zebar/start.bat' + ] # Whether to automatically focus windows underneath the cursor. focus_follows_cursor: false