From 058bfa732006439083cbaf0d85cf770dea10b90d Mon Sep 17 00:00:00 2001 From: lars-berger Date: Tue, 1 Oct 2024 08:08:26 +0100 Subject: [PATCH] feat: add sample configs of new window effects; fix window border effect flickering (#752) --- packages/wm/src/app_command.rs | 33 ++++++-- .../wm/src/common/commands/platform_sync.rs | 81 ++++++++++++------- .../common/events/handle_window_focused.rs | 4 +- .../wm/src/common/platform/native_window.rs | 24 +++--- packages/wm/src/user_config.rs | 12 +-- packages/wm/src/windows/commands/mod.rs | 2 - .../commands/set_title_bar_visibility.rs | 16 ---- resources/assets/sample-config.yaml | 17 ++++ 8 files changed, 114 insertions(+), 75 deletions(-) delete mode 100644 packages/wm/src/windows/commands/set_title_bar_visibility.rs diff --git a/packages/wm/src/app_command.rs b/packages/wm/src/app_command.rs index 8f3ebd3ce..3a93dde6c 100644 --- a/packages/wm/src/app_command.rs +++ b/packages/wm/src/app_command.rs @@ -1,7 +1,7 @@ use std::{iter, path::PathBuf}; use anyhow::{bail, Context}; -use clap::{error::KindFormatter, ArgAction, Args, Parser, ValueEnum}; +use clap::{error::KindFormatter, Args, Parser, ValueEnum}; use serde::{Deserialize, Deserializer, Serialize}; use tracing::{warn, Level}; use uuid::Uuid; @@ -26,8 +26,7 @@ use crate::{ windows::{ commands::{ ignore_window, move_window_in_direction, move_window_to_workspace, - resize_window, set_title_bar_visibility, set_window_size, - update_window_state, + resize_window, set_window_size, update_window_state, }, traits::WindowGetters, WindowState, @@ -207,8 +206,8 @@ pub enum InvokeCommand { SetMinimized, SetTiling, SetTitleBarVisibility { - #[clap(required = true, action = ArgAction::Set)] - visible: bool, + #[clap(required = true, value_enum)] + visibility: TitleBarVisibility, }, ShellExec { #[clap(required = true, trailing_var_arg = true)] @@ -490,8 +489,20 @@ impl InvokeCommand { _ => Ok(()), } } - InvokeCommand::SetTitleBarVisibility { visible } => { - set_title_bar_visibility(*visible, subject_container) + InvokeCommand::SetTitleBarVisibility { visibility } => { + match subject_container.as_window_container() { + Ok(window) => { + _ = window.native().set_title_bar_visibility( + if *visibility == TitleBarVisibility::Shown { + true + } else { + false + }, + ); + Ok(()) + } + _ => Ok(()), + } } InvokeCommand::ShellExec { command } => { shell_exec(&command.join(" ")) @@ -676,6 +687,14 @@ impl<'de> Deserialize<'de> for InvokeCommand { } } +#[derive(Clone, Debug, PartialEq, Serialize, ValueEnum)] +#[clap(rename_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum TitleBarVisibility { + Shown, + Hidden, +} + #[derive(Args, Clone, Debug, PartialEq, Serialize)] #[group(required = true, multiple = true)] pub struct InvokeAdjustBordersCommand { diff --git a/packages/wm/src/common/commands/platform_sync.rs b/packages/wm/src/common/commands/platform_sync.rs index 9f1493f0c..189e88c4c 100644 --- a/packages/wm/src/common/commands/platform_sync.rs +++ b/packages/wm/src/common/commands/platform_sync.rs @@ -1,4 +1,7 @@ +use std::time::Duration; + use anyhow::Context; +use tokio::task; use tracing::warn; use crate::{ @@ -7,7 +10,9 @@ use crate::{ traits::{CommonGetters, PositionGetters}, Container, WindowContainer, }, - user_config::{CursorJumpTrigger, UserConfig, WindowEffectConfig}, + user_config::{ + CornerStyle, CursorJumpTrigger, UserConfig, WindowEffectConfig, + }, windows::traits::WindowGetters, wm_event::WmEvent, wm_state::WmState, @@ -31,34 +36,39 @@ pub fn platform_sync( state.pending_sync.cursor_jump = false; } - if state.pending_sync.focus_change { - sync_focus(focused_container.clone(), state)?; - state.pending_sync.focus_change = false; - } - - if let Ok(window) = focused_container.as_window_container() { - apply_window_effects(window, true, config); - } + if state.pending_sync.focus_change + || state.pending_sync.reset_window_effects + { + if let Ok(window) = focused_container.as_window_container() { + apply_window_effects(window, true, config); + } - // Get windows that should have the unfocused border applied to them. - // For the sake of performance, we only update the border of the - // previously focused window. If the `reset_window_effects` flag is - // passed, the unfocused border is applied to all unfocused windows. - let unfocused_windows = match state.pending_sync.reset_window_effects { - true => state.windows(), - false => recent_focused_container - .and_then(|container| container.as_window_container().ok()) + // Get windows that should have the unfocused border applied to them. + // For the sake of performance, we only update the border of the + // previously focused window. If the `reset_window_effects` flag is + // passed, the unfocused border is applied to all unfocused windows. + let unfocused_windows = + match state.pending_sync.reset_window_effects { + true => state.windows(), + false => recent_focused_container + .and_then(|container| container.as_window_container().ok()) + .into_iter() + .collect(), + } .into_iter() - .collect(), - } - .into_iter() - .filter(|window| window.id() != focused_container.id()); + .filter(|window| window.id() != focused_container.id()); + + for window in unfocused_windows { + apply_window_effects(window, false, config); + } - for window in unfocused_windows { - apply_window_effects(window, false, config); + state.pending_sync.reset_window_effects = false; } - state.pending_sync.reset_window_effects = false; + if state.pending_sync.focus_change { + sync_focus(focused_container.clone(), state)?; + state.pending_sync.focus_change = false; + } Ok(()) } @@ -194,8 +204,8 @@ fn apply_window_effects( apply_hide_title_bar_effect(&window, effect_config); } - if window_effects.focused_window.corner.enabled - || window_effects.other_windows.corner.enabled + if window_effects.focused_window.corner_style.enabled + || window_effects.other_windows.corner_style.enabled { apply_corner_effect(&window, effect_config); } @@ -211,6 +221,16 @@ fn apply_border_effect( }; _ = window.native().set_border_color(border_color); + + let native = window.native().clone(); + let border_color = border_color.cloned(); + + // Re-apply border color after a short delay to better handle + // windows that change it themselves. + task::spawn(async move { + tokio::time::sleep(Duration::from_millis(50)).await; + _ = native.set_border_color(border_color.as_ref()); + }); } fn apply_hide_title_bar_effect( @@ -226,7 +246,10 @@ fn apply_corner_effect( window: &WindowContainer, effect_config: &WindowEffectConfig, ) { - _ = window - .native() - .set_corner_style(effect_config.corner.style.clone()); + let corner_style = match effect_config.corner_style.enabled { + true => &effect_config.corner_style.style, + false => &CornerStyle::Default, + }; + + _ = window.native().set_corner_style(corner_style); } diff --git a/packages/wm/src/common/events/handle_window_focused.rs b/packages/wm/src/common/events/handle_window_focused.rs index 26a3efac5..e684161a7 100644 --- a/packages/wm/src/common/events/handle_window_focused.rs +++ b/packages/wm/src/common/events/handle_window_focused.rs @@ -78,9 +78,7 @@ pub fn handle_window_focused( config, )?; - state.emit_event(WmEvent::FocusChanged { - focused_container: window.to_dto()?, - }) + state.pending_sync.focus_change = true; } Ok(()) diff --git a/packages/wm/src/common/platform/native_window.rs b/packages/wm/src/common/platform/native_window.rs index 9072d953c..6b29c9a13 100644 --- a/packages/wm/src/common/platform/native_window.rs +++ b/packages/wm/src/common/platform/native_window.rs @@ -331,13 +331,13 @@ impl NativeWindow { pub fn set_corner_style( &self, - corner_type: CornerStyle, + corner_style: &CornerStyle, ) -> anyhow::Result<()> { - let corner_preference = match corner_type { - CornerStyle::WindowsDefault => DWMWCP_DEFAULT, + let corner_preference = match corner_style { + CornerStyle::Default => DWMWCP_DEFAULT, CornerStyle::Square => DWMWCP_DONOTROUND, - CornerStyle::Round => DWMWCP_ROUND, - CornerStyle::SmallRound => DWMWCP_ROUNDSMALL, + CornerStyle::Rounded => DWMWCP_ROUND, + CornerStyle::SmallRounded => DWMWCP_ROUNDSMALL, }; unsafe { @@ -356,15 +356,15 @@ impl NativeWindow { &self, visible: bool, ) -> anyhow::Result<()> { - unsafe { - let style = GetWindowLongPtrW(HWND(self.handle), GWL_STYLE); + let style = unsafe { GetWindowLongPtrW(HWND(self.handle), GWL_STYLE) }; - let new_style = match visible { - true => style | (WS_DLGFRAME.0 as isize), - false => style & !(WS_DLGFRAME.0 as isize), - }; + let new_style = match visible { + true => style | (WS_DLGFRAME.0 as isize), + false => style & !(WS_DLGFRAME.0 as isize), + }; - if new_style != style { + if new_style != style { + unsafe { SetWindowLongPtrW(HWND(self.handle), GWL_STYLE, new_style); SetWindowPos( HWND(self.handle), diff --git a/packages/wm/src/user_config.rs b/packages/wm/src/user_config.rs index cc27f54d7..792e1b627 100644 --- a/packages/wm/src/user_config.rs +++ b/packages/wm/src/user_config.rs @@ -512,7 +512,7 @@ pub struct WindowEffectConfig { /// Config for optionally changing the corner style. #[serde(default)] - pub corner: CornerEffectConfig, + pub corner_style: CornerEffectConfig, } #[derive(Clone, Debug, Deserialize, Serialize)] @@ -550,10 +550,10 @@ pub struct CornerEffectConfig { #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] #[serde(rename_all = "snake_case")] pub enum CornerStyle { - WindowsDefault, + Default, Square, - Round, - SmallRound, + Rounded, + SmallRounded, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -651,7 +651,7 @@ fn default_window_rule_on() -> Vec { impl Default for CornerStyle { fn default() -> Self { - CornerStyle::WindowsDefault + CornerStyle::Default } } @@ -665,7 +665,7 @@ impl Default for CornerEffectConfig { fn default() -> Self { CornerEffectConfig { enabled: false, - style: CornerStyle::WindowsDefault, + style: CornerStyle::Default, } } } diff --git a/packages/wm/src/windows/commands/mod.rs b/packages/wm/src/windows/commands/mod.rs index 73101cc9d..63abc576b 100644 --- a/packages/wm/src/windows/commands/mod.rs +++ b/packages/wm/src/windows/commands/mod.rs @@ -4,7 +4,6 @@ mod move_window_in_direction; mod move_window_to_workspace; mod resize_window; mod run_window_rules; -mod set_title_bar_visibility; mod set_window_size; mod unmanage_window; mod update_window_state; @@ -15,7 +14,6 @@ pub use move_window_in_direction::*; pub use move_window_to_workspace::*; pub use resize_window::*; pub use run_window_rules::*; -pub use set_title_bar_visibility::*; pub use set_window_size::*; pub use unmanage_window::*; pub use update_window_state::*; diff --git a/packages/wm/src/windows/commands/set_title_bar_visibility.rs b/packages/wm/src/windows/commands/set_title_bar_visibility.rs deleted file mode 100644 index 7aa75ea22..000000000 --- a/packages/wm/src/windows/commands/set_title_bar_visibility.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::{ - containers::{traits::CommonGetters, Container}, - windows::traits::WindowGetters, -}; - -pub fn set_title_bar_visibility( - title_bar_is_visible: bool, - subject_container: Container, -) -> anyhow::Result<()> { - let window = subject_container.as_window_container()?; - window - .native() - .set_title_bar_visibility(title_bar_is_visible)?; - - Ok(()) -} diff --git a/resources/assets/sample-config.yaml b/resources/assets/sample-config.yaml index a99faa49d..a56bfad9f 100644 --- a/resources/assets/sample-config.yaml +++ b/resources/assets/sample-config.yaml @@ -47,11 +47,28 @@ window_effects: enabled: true color: '#8dbcff' + # Remove the title bar from the window's frame. + # ** Exclusive to Windows 11 due to API limitations. + hide_title_bar: + enabled: false + + # Change the corner style of the window's frame. + # ** Exclusive to Windows 11 due to API limitations. + corner_style: + enabled: false + # Allowed values: 'square', 'rounded', 'small_rounded'. + style: 'square' + # Visual effects to apply to non-focused windows. other_windows: border: enabled: true color: '#a1a1a1' + hide_title_bar: + enabled: false + corner_style: + enabled: false + style: 'square' window_behavior: # New windows are created in this state whenever possible.