diff --git a/komorebi-client/src/lib.rs b/komorebi-client/src/lib.rs index 8b64952c..13afc3cd 100644 --- a/komorebi-client/src/lib.rs +++ b/komorebi-client/src/lib.rs @@ -1,6 +1,7 @@ #![warn(clippy::all)] #![allow(clippy::missing_errors_doc)] +pub use komorebi::animation::prefix::AnimationPrefix; pub use komorebi::asc::ApplicationSpecificConfiguration; pub use komorebi::colour::Colour; pub use komorebi::colour::Rgb; diff --git a/komorebi/src/animation/engine.rs b/komorebi/src/animation/engine.rs index 0104c891..ab12663c 100644 --- a/komorebi/src/animation/engine.rs +++ b/komorebi/src/animation/engine.rs @@ -9,7 +9,7 @@ use std::time::Duration; use std::time::Instant; use super::RenderDispatcher; -use super::ANIMATION_DURATION; +use super::ANIMATION_DURATION_GLOBAL; use super::ANIMATION_FPS; use super::ANIMATION_MANAGER; @@ -27,7 +27,7 @@ impl AnimationEngine { } std::thread::sleep(Duration::from_millis( - ANIMATION_DURATION.load(Ordering::SeqCst), + ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst), )); } } diff --git a/komorebi/src/animation/mod.rs b/komorebi/src/animation/mod.rs index 3fe6f0c2..47bf763c 100644 --- a/komorebi/src/animation/mod.rs +++ b/komorebi/src/animation/mod.rs @@ -2,6 +2,8 @@ use crate::animation::animation_manager::AnimationManager; use crate::core::animation::AnimationStyle; use lazy_static::lazy_static; +use prefix::AnimationPrefix; +use std::collections::HashMap; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU64; use std::sync::Arc; @@ -16,14 +18,37 @@ pub mod prefix; pub mod render_dispatcher; pub use render_dispatcher::RenderDispatcher; pub mod style; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; -lazy_static! { - pub static ref ANIMATION_STYLE: Arc> = - Arc::new(Mutex::new(AnimationStyle::Linear)); - pub static ref ANIMATION_MANAGER: Arc> = - Arc::new(Mutex::new(AnimationManager::new())); +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[serde(untagged)] +pub enum PerAnimationPrefixConfig { + Prefix(HashMap), + Global(T), } -pub static ANIMATION_ENABLED: AtomicBool = AtomicBool::new(false); -pub static ANIMATION_DURATION: AtomicU64 = AtomicU64::new(250); -pub static ANIMATION_FPS: AtomicU64 = AtomicU64::new(60); +pub const DEFAULT_ANIMATION_ENABLED: bool = false; +pub const DEFAULT_ANIMATION_STYLE: AnimationStyle = AnimationStyle::Linear; +pub const DEFAULT_ANIMATION_DURATION: u64 = 250; +pub const DEFAULT_ANIMATION_FPS: u64 = 60; + +lazy_static! { + pub static ref ANIMATION_MANAGER: Arc> = + Arc::new(Mutex::new(AnimationManager::new())); + pub static ref ANIMATION_STYLE_GLOBAL: Arc> = + Arc::new(Mutex::new(DEFAULT_ANIMATION_STYLE)); + pub static ref ANIMATION_ENABLED_GLOBAL: Arc = + Arc::new(AtomicBool::new(DEFAULT_ANIMATION_ENABLED)); + pub static ref ANIMATION_DURATION_GLOBAL: Arc = + Arc::new(AtomicU64::new(DEFAULT_ANIMATION_DURATION)); + pub static ref ANIMATION_STYLE_PER_ANIMATION: Arc>> = + Arc::new(Mutex::new(HashMap::new())); + pub static ref ANIMATION_ENABLED_PER_ANIMATION: Arc>> = + Arc::new(Mutex::new(HashMap::new())); + pub static ref ANIMATION_DURATION_PER_ANIMATION: Arc>> = + Arc::new(Mutex::new(HashMap::new())); +} + +pub static ANIMATION_FPS: AtomicU64 = AtomicU64::new(DEFAULT_ANIMATION_FPS); diff --git a/komorebi/src/animation/prefix.rs b/komorebi/src/animation/prefix.rs index 86eadfba..8512dcdc 100644 --- a/komorebi/src/animation/prefix.rs +++ b/komorebi/src/animation/prefix.rs @@ -6,8 +6,21 @@ use strum::Display; use strum::EnumString; #[derive( - Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, JsonSchema, + Copy, + Clone, + Debug, + Hash, + PartialEq, + Eq, + Serialize, + Deserialize, + Display, + EnumString, + ValueEnum, + JsonSchema, )] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] pub enum AnimationPrefix { WindowMove, WindowTransparency, diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index 3f2a2399..104c6c59 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -14,6 +14,7 @@ use serde::Serialize; use strum::Display; use strum::EnumString; +use crate::animation::prefix::AnimationPrefix; use crate::KomorebiTheme; pub use animation::AnimationStyle; pub use arrangement::Arrangement; @@ -146,10 +147,10 @@ pub enum SocketMessage { CompleteConfiguration, AltFocusHack(bool), Theme(KomorebiTheme), - Animation(bool), - AnimationDuration(u64), + Animation(bool, Option), + AnimationDuration(u64, Option), AnimationFps(u64), - AnimationStyle(AnimationStyle), + AnimationStyle(AnimationStyle, Option), #[serde(alias = "ActiveWindowBorder")] Border(bool), #[serde(alias = "ActiveWindowBorderColour")] diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 1507c683..4f601975 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -18,7 +18,8 @@ use clap::Parser; use color_eyre::Result; use crossbeam_utils::Backoff; use komorebi::animation::AnimationEngine; -use komorebi::animation::ANIMATION_ENABLED; +use komorebi::animation::ANIMATION_ENABLED_GLOBAL; +use komorebi::animation::ANIMATION_ENABLED_PER_ANIMATION; #[cfg(feature = "deadlock_detection")] use parking_lot::deadlock; use parking_lot::Mutex; @@ -289,7 +290,8 @@ fn main() -> Result<()> { tracing::error!("received ctrl-c, restoring all hidden windows and terminating process"); - ANIMATION_ENABLED.store(false, Ordering::SeqCst); + ANIMATION_ENABLED_PER_ANIMATION.lock().clear(); + ANIMATION_ENABLED_GLOBAL.store(false, Ordering::SeqCst); wm.lock().restore_all_windows()?; AnimationEngine::wait_for_all_animations(); diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 1a302feb..12e869ad 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -23,6 +23,9 @@ use schemars::schema_for; use uds_windows::UnixStream; use crate::animation::AnimationEngine; +use crate::animation::ANIMATION_DURATION_PER_ANIMATION; +use crate::animation::ANIMATION_ENABLED_PER_ANIMATION; +use crate::animation::ANIMATION_STYLE_PER_ANIMATION; use crate::core::config_generation::ApplicationConfiguration; use crate::core::config_generation::IdWithIdentifier; use crate::core::config_generation::MatchingRule; @@ -41,10 +44,10 @@ use crate::core::StateQuery; use crate::core::WindowContainerBehaviour; use crate::core::WindowKind; -use crate::animation::ANIMATION_DURATION; -use crate::animation::ANIMATION_ENABLED; +use crate::animation::ANIMATION_DURATION_GLOBAL; +use crate::animation::ANIMATION_ENABLED_GLOBAL; use crate::animation::ANIMATION_FPS; -use crate::animation::ANIMATION_STYLE; +use crate::animation::ANIMATION_STYLE_GLOBAL; use crate::border_manager; use crate::border_manager::IMPLEMENTATION; use crate::border_manager::STYLE; @@ -878,7 +881,8 @@ impl WindowManager { "received stop command, restoring all hidden windows and terminating process" ); - ANIMATION_ENABLED.store(false, Ordering::SeqCst); + ANIMATION_ENABLED_PER_ANIMATION.lock().clear(); + ANIMATION_ENABLED_GLOBAL.store(false, Ordering::SeqCst); self.restore_all_windows()?; AnimationEngine::wait_for_all_animations(); @@ -1523,18 +1527,41 @@ impl WindowManager { SocketMessage::BorderOffset(offset) => { border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst); } - SocketMessage::Animation(enable) => { - ANIMATION_ENABLED.store(enable, Ordering::SeqCst); - } - SocketMessage::AnimationDuration(duration) => { - ANIMATION_DURATION.store(duration, Ordering::SeqCst); - } + SocketMessage::Animation(enable, prefix) => match prefix { + Some(prefix) => { + ANIMATION_ENABLED_PER_ANIMATION + .lock() + .insert(prefix, enable); + } + None => { + ANIMATION_ENABLED_GLOBAL.store(enable, Ordering::SeqCst); + ANIMATION_ENABLED_PER_ANIMATION.lock().clear(); + } + }, + SocketMessage::AnimationDuration(duration, prefix) => match prefix { + Some(prefix) => { + ANIMATION_DURATION_PER_ANIMATION + .lock() + .insert(prefix, duration); + } + None => { + ANIMATION_DURATION_GLOBAL.store(duration, Ordering::SeqCst); + ANIMATION_DURATION_PER_ANIMATION.lock().clear(); + } + }, SocketMessage::AnimationFps(fps) => { ANIMATION_FPS.store(fps, Ordering::SeqCst); } - SocketMessage::AnimationStyle(style) => { - *ANIMATION_STYLE.lock() = style; - } + SocketMessage::AnimationStyle(style, prefix) => match prefix { + Some(prefix) => { + ANIMATION_STYLE_PER_ANIMATION.lock().insert(prefix, style); + } + None => { + let mut animation_style = ANIMATION_STYLE_GLOBAL.lock(); + *animation_style = style; + ANIMATION_STYLE_PER_ANIMATION.lock().clear(); + } + }, SocketMessage::ToggleTransparency => { let current = transparency_manager::TRANSPARENCY_ENABLED.load(Ordering::SeqCst); transparency_manager::TRANSPARENCY_ENABLED.store(!current, Ordering::SeqCst); diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 978c40b8..40a2dcae 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -1,7 +1,12 @@ -use crate::animation::ANIMATION_DURATION; -use crate::animation::ANIMATION_ENABLED; +use crate::animation::PerAnimationPrefixConfig; +use crate::animation::ANIMATION_DURATION_GLOBAL; +use crate::animation::ANIMATION_DURATION_PER_ANIMATION; +use crate::animation::ANIMATION_ENABLED_GLOBAL; +use crate::animation::ANIMATION_ENABLED_PER_ANIMATION; use crate::animation::ANIMATION_FPS; -use crate::animation::ANIMATION_STYLE; +use crate::animation::ANIMATION_STYLE_GLOBAL; +use crate::animation::ANIMATION_STYLE_PER_ANIMATION; +use crate::animation::DEFAULT_ANIMATION_FPS; use crate::border_manager; use crate::border_manager::ZOrder; use crate::border_manager::IMPLEMENTATION; @@ -374,11 +379,11 @@ pub struct StaticConfig { #[derive(Debug, Serialize, Deserialize, JsonSchema)] pub struct AnimationsConfig { /// Enable or disable animations (default: false) - enabled: bool, + enabled: PerAnimationPrefixConfig, /// Set the animation duration in ms (default: 250) - duration: Option, + duration: Option>, /// Set the animation style (default: Linear) - style: Option, + style: Option>, /// Set the animation FPS (default: 60) fps: Option, } @@ -654,11 +659,43 @@ impl StaticConfig { } if let Some(animations) = &self.animation { - ANIMATION_ENABLED.store(animations.enabled, Ordering::SeqCst); - ANIMATION_DURATION.store(animations.duration.unwrap_or(250), Ordering::SeqCst); - ANIMATION_FPS.store(animations.fps.unwrap_or(60), Ordering::SeqCst); - let mut animation_style = ANIMATION_STYLE.lock(); - *animation_style = animations.style.unwrap_or(AnimationStyle::Linear); + match &animations.enabled { + PerAnimationPrefixConfig::Prefix(enabled) => { + ANIMATION_ENABLED_PER_ANIMATION.lock().clone_from(enabled); + } + PerAnimationPrefixConfig::Global(enabled) => { + ANIMATION_ENABLED_GLOBAL.store(*enabled, Ordering::SeqCst); + ANIMATION_ENABLED_PER_ANIMATION.lock().clear(); + } + } + + match &animations.style { + Some(PerAnimationPrefixConfig::Prefix(style)) => { + ANIMATION_STYLE_PER_ANIMATION.lock().clone_from(style); + } + Some(PerAnimationPrefixConfig::Global(style)) => { + let mut animation_style = ANIMATION_STYLE_GLOBAL.lock(); + *animation_style = *style; + ANIMATION_STYLE_PER_ANIMATION.lock().clear(); + } + None => {} + } + + match &animations.duration { + Some(PerAnimationPrefixConfig::Prefix(duration)) => { + ANIMATION_DURATION_PER_ANIMATION.lock().clone_from(duration); + } + Some(PerAnimationPrefixConfig::Global(duration)) => { + ANIMATION_DURATION_GLOBAL.store(*duration, Ordering::SeqCst); + ANIMATION_DURATION_PER_ANIMATION.lock().clear(); + } + None => {} + } + + ANIMATION_FPS.store( + animations.fps.unwrap_or(DEFAULT_ANIMATION_FPS), + Ordering::SeqCst, + ); } if let Some(container) = self.default_container_padding { diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index ccf80763..48270209 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -3,10 +3,13 @@ use crate::animation::prefix::new_animation_key; use crate::animation::prefix::AnimationPrefix; use crate::animation::AnimationEngine; use crate::animation::RenderDispatcher; -use crate::animation::ANIMATION_DURATION; -use crate::animation::ANIMATION_ENABLED; +use crate::animation::ANIMATION_DURATION_GLOBAL; +use crate::animation::ANIMATION_DURATION_PER_ANIMATION; +use crate::animation::ANIMATION_ENABLED_GLOBAL; +use crate::animation::ANIMATION_ENABLED_PER_ANIMATION; use crate::animation::ANIMATION_MANAGER; -use crate::animation::ANIMATION_STYLE; +use crate::animation::ANIMATION_STYLE_GLOBAL; +use crate::animation::ANIMATION_STYLE_PER_ANIMATION; use crate::border_manager; use crate::com::SetCloak; use crate::focus_manager; @@ -156,7 +159,6 @@ impl Serialize for Window { } struct WindowMoveRenderDispatcher { - prefix: AnimationPrefix, hwnd: isize, start_rect: Rect, target_rect: Rect, @@ -165,6 +167,8 @@ struct WindowMoveRenderDispatcher { } impl WindowMoveRenderDispatcher { + const PREFIX: AnimationPrefix = AnimationPrefix::WindowMove; + pub fn new( hwnd: isize, start_rect: Rect, @@ -173,7 +177,6 @@ impl WindowMoveRenderDispatcher { style: AnimationStyle, ) -> Self { Self { - prefix: AnimationPrefix::WindowMove, hwnd, start_rect, target_rect, @@ -185,7 +188,7 @@ impl WindowMoveRenderDispatcher { impl RenderDispatcher for WindowMoveRenderDispatcher { fn get_animation_key(&self) -> String { - new_animation_key(self.prefix, self.hwnd.to_string()) + new_animation_key(WindowMoveRenderDispatcher::PREFIX, self.hwnd.to_string()) } fn pre_render(&self) -> Result<()> { @@ -211,7 +214,11 @@ impl RenderDispatcher for WindowMoveRenderDispatcher { fn post_render(&self) -> Result<()> { WindowsApi::position_window(self.hwnd, &self.target_rect, self.top)?; - if ANIMATION_MANAGER.lock().count_in_progress(self.prefix) == 0 { + if ANIMATION_MANAGER + .lock() + .count_in_progress(WindowMoveRenderDispatcher::PREFIX) + == 0 + { if WindowsApi::foreground_window().unwrap_or_default() == self.hwnd { focus_manager::send_notification(self.hwnd) } @@ -229,7 +236,6 @@ impl RenderDispatcher for WindowMoveRenderDispatcher { } struct WindowTransparencyRenderDispatcher { - prefix: AnimationPrefix, hwnd: isize, start_opacity: u8, target_opacity: u8, @@ -238,6 +244,8 @@ struct WindowTransparencyRenderDispatcher { } impl WindowTransparencyRenderDispatcher { + const PREFIX: AnimationPrefix = AnimationPrefix::WindowTransparency; + pub fn new( hwnd: isize, is_opaque: bool, @@ -246,7 +254,6 @@ impl WindowTransparencyRenderDispatcher { style: AnimationStyle, ) -> Self { Self { - prefix: AnimationPrefix::WindowTransparency, hwnd, start_opacity, target_opacity, @@ -258,7 +265,10 @@ impl WindowTransparencyRenderDispatcher { impl RenderDispatcher for WindowTransparencyRenderDispatcher { fn get_animation_key(&self) -> String { - new_animation_key(self.prefix, self.hwnd.to_string()) + new_animation_key( + WindowTransparencyRenderDispatcher::PREFIX, + self.hwnd.to_string(), + ) } fn pre_render(&self) -> Result<()> { @@ -346,9 +356,22 @@ impl Window { return Ok(()); } - if ANIMATION_ENABLED.load(Ordering::SeqCst) { - let duration = Duration::from_millis(ANIMATION_DURATION.load(Ordering::SeqCst)); - let style = *ANIMATION_STYLE.lock(); + let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock(); + let move_enabled = animation_enabled.get(&WindowMoveRenderDispatcher::PREFIX); + + if move_enabled.is_some_and(|enabled| *enabled) + || ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst) + { + let duration = Duration::from_millis( + *ANIMATION_DURATION_PER_ANIMATION + .lock() + .get(&WindowMoveRenderDispatcher::PREFIX) + .unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)), + ); + let style = *ANIMATION_STYLE_PER_ANIMATION + .lock() + .get(&WindowMoveRenderDispatcher::PREFIX) + .unwrap_or(&ANIMATION_STYLE_GLOBAL.lock()); let render_dispatcher = WindowMoveRenderDispatcher::new(self.hwnd, window_rect, *layout, top, style); @@ -462,9 +485,23 @@ impl Window { } pub fn transparent(self) -> Result<()> { - if ANIMATION_ENABLED.load(Ordering::SeqCst) { - let duration = Duration::from_millis(ANIMATION_DURATION.load(Ordering::SeqCst)); - let style = *ANIMATION_STYLE.lock(); + let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock(); + let transparent_enabled = + animation_enabled.get(&WindowTransparencyRenderDispatcher::PREFIX); + + if transparent_enabled.is_some_and(|enabled| *enabled) + || ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst) + { + let duration = Duration::from_millis( + *ANIMATION_DURATION_PER_ANIMATION + .lock() + .get(&WindowTransparencyRenderDispatcher::PREFIX) + .unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)), + ); + let style = *ANIMATION_STYLE_PER_ANIMATION + .lock() + .get(&WindowTransparencyRenderDispatcher::PREFIX) + .unwrap_or(&ANIMATION_STYLE_GLOBAL.lock()); let render_dispatcher = WindowTransparencyRenderDispatcher::new( self.hwnd, @@ -487,9 +524,23 @@ impl Window { } pub fn opaque(self) -> Result<()> { - if ANIMATION_ENABLED.load(Ordering::SeqCst) { - let duration = Duration::from_millis(ANIMATION_DURATION.load(Ordering::SeqCst)); - let style = *ANIMATION_STYLE.lock(); + let animation_enabled = ANIMATION_ENABLED_PER_ANIMATION.lock(); + let transparent_enabled = + animation_enabled.get(&WindowTransparencyRenderDispatcher::PREFIX); + + if transparent_enabled.is_some_and(|enabled| *enabled) + || ANIMATION_ENABLED_GLOBAL.load(Ordering::SeqCst) + { + let duration = Duration::from_millis( + *ANIMATION_DURATION_PER_ANIMATION + .lock() + .get(&WindowTransparencyRenderDispatcher::PREFIX) + .unwrap_or(&ANIMATION_DURATION_GLOBAL.load(Ordering::SeqCst)), + ); + let style = *ANIMATION_STYLE_PER_ANIMATION + .lock() + .get(&WindowTransparencyRenderDispatcher::PREFIX) + .unwrap_or(&ANIMATION_STYLE_GLOBAL.lock()); let render_dispatcher = WindowTransparencyRenderDispatcher::new( self.hwnd, diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 7c60031e..032f8e4b 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -723,12 +723,18 @@ struct BorderImplementation { struct Animation { #[clap(value_enum)] boolean_state: BooleanState, + /// Animation type to apply the state to. If not specified, sets global state + #[clap(value_enum, short, long)] + animation_type: Option, } #[derive(Parser)] struct AnimationDuration { /// Desired animation durations in ms duration: u64, + /// Animation type to apply the duration to. If not specified, sets global duration + #[clap(value_enum, short, long)] + animation_type: Option, } #[derive(Parser)] @@ -742,6 +748,9 @@ struct AnimationStyle { /// Desired ease function for animation #[clap(value_enum, short, long, default_value = "linear")] style: komorebi_client::AnimationStyle, + /// Animation type to apply the style to. If not specified, sets global style + #[clap(value_enum, short, long)] + animation_type: Option, } #[derive(Parser)] @@ -2539,16 +2548,25 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue send_message(&SocketMessage::ToggleTransparency)?; } SubCommand::Animation(arg) => { - send_message(&SocketMessage::Animation(arg.boolean_state.into()))?; + send_message(&SocketMessage::Animation( + arg.boolean_state.into(), + arg.animation_type, + ))?; } SubCommand::AnimationDuration(arg) => { - send_message(&SocketMessage::AnimationDuration(arg.duration))?; + send_message(&SocketMessage::AnimationDuration( + arg.duration, + arg.animation_type, + ))?; } SubCommand::AnimationFps(arg) => { send_message(&SocketMessage::AnimationFps(arg.fps))?; } SubCommand::AnimationStyle(arg) => { - send_message(&SocketMessage::AnimationStyle(arg.style))?; + send_message(&SocketMessage::AnimationStyle( + arg.style, + arg.animation_type, + ))?; } SubCommand::ResizeDelta(arg) => {