From 83272dd0ec053666caf8fa0dd4750253d7b0abca Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Thu, 20 Jun 2024 20:13:22 -0700 Subject: [PATCH] feat(borders): add windows accent implementation This commit adds the ability for users to select between komorebi's implementation of borders with variable widths and the native Windows 11 implementation of thin "accent" borders. The new border_implementation configuration option defaults to BorderImplementation::Komorebi in order to preserve compat with existing user configurations. This option will be most useful for people who prefer ultra-thin borders, and people who want borders to be animated along with application windows if they are using the animation feature being worked on in PR #685. --- komorebi-core/src/lib.rs | 34 +- komorebi/src/border_manager/border.rs | 14 +- komorebi/src/border_manager/mod.rs | 452 +++++++++++++++----------- komorebi/src/process_command.rs | 15 + komorebi/src/static_config.rs | 17 + komorebi/src/window.rs | 8 + komorebi/src/window_manager.rs | 19 ++ komorebi/src/windows_api.rs | 15 + komorebic/src/main.rs | 25 ++ 9 files changed, 394 insertions(+), 205 deletions(-) diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 281405a5..086bebdb 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -142,6 +142,7 @@ pub enum SocketMessage { BorderStyle(BorderStyle), BorderWidth(i32), BorderOffset(i32), + BorderImplementation(BorderImplementation), Transparency(bool), TransparencyAlpha(u8), InvisibleBorders(Rect), @@ -219,7 +220,17 @@ pub enum StackbarLabel { } #[derive( - Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema, + Default, + Copy, + Clone, + Debug, + Eq, + PartialEq, + Display, + Serialize, + Deserialize, + JsonSchema, + ValueEnum, )] pub enum BorderStyle { #[default] @@ -231,6 +242,27 @@ pub enum BorderStyle { Square, } +#[derive( + Default, + Copy, + Clone, + Debug, + Eq, + PartialEq, + Display, + Serialize, + Deserialize, + JsonSchema, + ValueEnum, +)] +pub enum BorderImplementation { + #[default] + /// Use the adjustable komorebi border implementation + Komorebi, + /// Use the thin Windows accent border implementation + Windows, +} + #[derive( Copy, Clone, diff --git a/komorebi/src/border_manager/border.rs b/komorebi/src/border_manager/border.rs index 720a6dbb..65feecce 100644 --- a/komorebi/src/border_manager/border.rs +++ b/komorebi/src/border_manager/border.rs @@ -1,12 +1,9 @@ +use crate::border_manager::window_kind_colour; use crate::border_manager::WindowKind; use crate::border_manager::BORDER_OFFSET; use crate::border_manager::BORDER_WIDTH; -use crate::border_manager::FOCUSED; use crate::border_manager::FOCUS_STATE; -use crate::border_manager::MONOCLE; -use crate::border_manager::STACK; use crate::border_manager::STYLE; -use crate::border_manager::UNFOCUSED; use crate::border_manager::Z_ORDER; use crate::WindowsApi; use crate::WINDOWS_11; @@ -165,7 +162,7 @@ impl Border { match WindowsApi::window_rect(window) { Ok(rect) => { // Grab the focus kind for this border - let focus_kind = { + let window_kind = { FOCUS_STATE .lock() .get(&window.0) @@ -177,12 +174,7 @@ impl Border { let hpen = CreatePen( PS_SOLID | PS_INSIDEFRAME, BORDER_WIDTH.load(Ordering::SeqCst), - COLORREF(match focus_kind { - WindowKind::Unfocused => UNFOCUSED.load(Ordering::SeqCst), - WindowKind::Single => FOCUSED.load(Ordering::SeqCst), - WindowKind::Stack => STACK.load(Ordering::SeqCst), - WindowKind::Monocle => MONOCLE.load(Ordering::SeqCst), - }), + COLORREF(window_kind_colour(window_kind)), ); let hbrush = WindowsApi::create_solid_brush(0); diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs index aa6809a9..d1606f0a 100644 --- a/komorebi/src/border_manager/mod.rs +++ b/komorebi/src/border_manager/mod.rs @@ -6,6 +6,7 @@ use crossbeam_channel::Receiver; use crossbeam_channel::Sender; use crossbeam_utils::atomic::AtomicCell; use crossbeam_utils::atomic::AtomicConsume; +use komorebi_core::BorderImplementation; use komorebi_core::BorderStyle; use lazy_static::lazy_static; use parking_lot::Mutex; @@ -17,6 +18,7 @@ use std::collections::HashMap; use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicI32; use std::sync::atomic::AtomicU32; +use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::OnceLock; use windows::Win32::Foundation::HWND; @@ -35,10 +37,13 @@ pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8); pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1); pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(true); +pub static BORDER_TEMPORARILY_DISABLED: AtomicBool = AtomicBool::new(false); lazy_static! { pub static ref Z_ORDER: AtomicCell = AtomicCell::new(ZOrder::Bottom); pub static ref STYLE: AtomicCell = AtomicCell::new(BorderStyle::System); + pub static ref IMPLEMENTATION: AtomicCell = + AtomicCell::new(BorderImplementation::Windows); pub static ref FOCUSED: AtomicU32 = AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(66, 165, 245)))); pub static ref UNFOCUSED: AtomicU32 = @@ -59,7 +64,7 @@ pub struct Notification; static CHANNEL: OnceLock<(Sender, Receiver)> = OnceLock::new(); pub fn channel() -> &'static (Sender, Receiver) { - CHANNEL.get_or_init(|| crossbeam_channel::bounded(20)) + CHANNEL.get_or_init(|| crossbeam_channel::bounded(50)) } fn event_tx() -> Sender { @@ -109,6 +114,15 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> { Ok(()) } +fn window_kind_colour(focus_kind: WindowKind) -> u32 { + match focus_kind { + WindowKind::Unfocused => UNFOCUSED.load(Ordering::SeqCst), + WindowKind::Single => FOCUSED.load(Ordering::SeqCst), + WindowKind::Stack => STACK.load(Ordering::SeqCst), + WindowKind::Monocle => MONOCLE.load(Ordering::SeqCst), + } +} + pub fn listen_for_notifications(wm: Arc>) { std::thread::spawn(move || loop { match handle_notifications(wm.clone()) { @@ -125,6 +139,7 @@ pub fn listen_for_notifications(wm: Arc>) { pub fn handle_notifications(wm: Arc>) -> color_eyre::Result<()> { tracing::info!("listening"); + BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst); let receiver = event_rx(); event_tx().send(Notification)?; @@ -141,179 +156,255 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result let pending_move_op = state.pending_move_op; drop(state); - let mut should_process_notification = true; + match IMPLEMENTATION.load() { + BorderImplementation::Windows => { + 'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() { + // Only operate on the focused workspace of each monitor + if let Some(ws) = m.focused_workspace() { + // Handle the monocle container separately + if let Some(monocle) = ws.monocle_container() { + let window_kind = if monitor_idx != focused_monitor_idx { + WindowKind::Unfocused + } else { + WindowKind::Monocle + }; - if monitors == previous_snapshot - // handle the window dragging edge case - && pending_move_op == previous_pending_move_op - { - should_process_notification = false; - } + monocle + .focused_window() + .copied() + .unwrap_or_default() + .set_accent(window_kind_colour(window_kind))?; - // handle the pause edge case - if is_paused && !previous_is_paused { - should_process_notification = true; - } + continue 'monitors; + } - // handle the unpause edge case - if previous_is_paused && !is_paused { - should_process_notification = true; - } + for (idx, c) in ws.containers().iter().enumerate() { + let window_kind = if idx != ws.focused_container_idx() + || monitor_idx != focused_monitor_idx + { + WindowKind::Unfocused + } else if c.windows().len() > 1 { + WindowKind::Stack + } else { + WindowKind::Single + }; - // handle the retile edge case - if !should_process_notification && BORDER_STATE.lock().is_empty() { - should_process_notification = true; - } + c.focused_window() + .copied() + .unwrap_or_default() + .set_accent(window_kind_colour(window_kind))?; + } + } + } + } + BorderImplementation::Komorebi => { + let mut should_process_notification = true; + + if monitors == previous_snapshot + // handle the window dragging edge case + && pending_move_op == previous_pending_move_op + { + should_process_notification = false; + } - if !should_process_notification { - tracing::trace!("monitor state matches latest snapshot, skipping notification"); - continue 'receiver; - } + // handle the pause edge case + if is_paused && !previous_is_paused { + should_process_notification = true; + } - let mut borders = BORDER_STATE.lock(); - let mut borders_monitors = BORDERS_MONITORS.lock(); - - // If borders are disabled - if !BORDER_ENABLED.load_consume() - // Or if the wm is paused - || is_paused - // Or if we are handling an alt-tab across workspaces - || ALT_TAB_HWND.load().is_some() - { - // Destroy the borders we know about - for (_, border) in borders.iter() { - border.destroy()?; - } + // handle the unpause edge case + if previous_is_paused && !is_paused { + should_process_notification = true; + } - borders.clear(); + // handle the retile edge case + if !should_process_notification && BORDER_STATE.lock().is_empty() { + should_process_notification = true; + } - previous_is_paused = is_paused; - continue 'receiver; - } + if !should_process_notification { + tracing::trace!("monitor state matches latest snapshot, skipping notification"); + continue 'receiver; + } - 'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() { - // Only operate on the focused workspace of each monitor - if let Some(ws) = m.focused_workspace() { - // Workspaces with tiling disabled don't have borders - if !ws.tile() { - let mut to_remove = vec![]; - for (id, border) in borders.iter() { - if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx { - border.destroy()?; - to_remove.push(id.clone()); - } + let mut borders = BORDER_STATE.lock(); + let mut borders_monitors = BORDERS_MONITORS.lock(); + + // If borders are disabled + if !BORDER_ENABLED.load_consume() + // Or if they are temporarily disabled + || BORDER_TEMPORARILY_DISABLED.load(Ordering::SeqCst) + // Or if the wm is paused + || is_paused + // Or if we are handling an alt-tab across workspaces + || ALT_TAB_HWND.load().is_some() + { + // Destroy the borders we know about + for (_, border) in borders.iter() { + border.destroy()?; } - for id in &to_remove { - borders.remove(id); - } + borders.clear(); - continue 'monitors; + previous_is_paused = is_paused; + continue 'receiver; } - // Handle the monocle container separately - if let Some(monocle) = ws.monocle_container() { - let border = match borders.entry(monocle.id().clone()) { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => { - if let Ok(border) = Border::create(monocle.id()) { - entry.insert(border) - } else { - continue 'monitors; + 'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() { + // Only operate on the focused workspace of each monitor + if let Some(ws) = m.focused_workspace() { + // Workspaces with tiling disabled don't have borders + if !ws.tile() { + let mut to_remove = vec![]; + for (id, border) in borders.iter() { + if borders_monitors.get(id).copied().unwrap_or_default() + == monitor_idx + { + border.destroy()?; + to_remove.push(id.clone()); + } } - } - }; - borders_monitors.insert(monocle.id().clone(), monitor_idx); + for id in &to_remove { + borders.remove(id); + } - { - let mut focus_state = FOCUS_STATE.lock(); - focus_state.insert( - border.hwnd, - if monitor_idx != focused_monitor_idx { - WindowKind::Unfocused - } else { - WindowKind::Monocle - }, - ); - } + continue 'monitors; + } - let rect = WindowsApi::window_rect( - monocle.focused_window().copied().unwrap_or_default().hwnd(), - )?; + // Handle the monocle container separately + if let Some(monocle) = ws.monocle_container() { + let border = match borders.entry(monocle.id().clone()) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + if let Ok(border) = Border::create(monocle.id()) { + entry.insert(border) + } else { + continue 'monitors; + } + } + }; - border.update(&rect, true)?; + borders_monitors.insert(monocle.id().clone(), monitor_idx); - let border_hwnd = border.hwnd; - let mut to_remove = vec![]; - for (id, b) in borders.iter() { - if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx - && border_hwnd != b.hwnd - { - b.destroy()?; - to_remove.push(id.clone()); - } - } + { + let mut focus_state = FOCUS_STATE.lock(); + focus_state.insert( + border.hwnd, + if monitor_idx != focused_monitor_idx { + WindowKind::Unfocused + } else { + WindowKind::Monocle + }, + ); + } - for id in &to_remove { - borders.remove(id); - } + let rect = WindowsApi::window_rect( + monocle.focused_window().copied().unwrap_or_default().hwnd(), + )?; - continue 'monitors; - } + border.update(&rect, true)?; + + let border_hwnd = border.hwnd; + let mut to_remove = vec![]; + for (id, b) in borders.iter() { + if borders_monitors.get(id).copied().unwrap_or_default() + == monitor_idx + && border_hwnd != b.hwnd + { + b.destroy()?; + to_remove.push(id.clone()); + } + } - let is_maximized = WindowsApi::is_zoomed(HWND( - WindowsApi::foreground_window().unwrap_or_default(), - )); + for id in &to_remove { + borders.remove(id); + } - if is_maximized { - let mut to_remove = vec![]; - for (id, border) in borders.iter() { - if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx { - border.destroy()?; - to_remove.push(id.clone()); + continue 'monitors; } - } - for id in &to_remove { - borders.remove(id); - } + let is_maximized = WindowsApi::is_zoomed(HWND( + WindowsApi::foreground_window().unwrap_or_default(), + )); + + if is_maximized { + let mut to_remove = vec![]; + for (id, border) in borders.iter() { + if borders_monitors.get(id).copied().unwrap_or_default() + == monitor_idx + { + border.destroy()?; + to_remove.push(id.clone()); + } + } - continue 'monitors; - } + for id in &to_remove { + borders.remove(id); + } - // Destroy any borders not associated with the focused workspace - let container_ids = ws - .containers() - .iter() - .map(|c| c.id().clone()) - .collect::>(); - - let mut to_remove = vec![]; - for (id, border) in borders.iter() { - if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx - && !container_ids.contains(id) - { - border.destroy()?; - to_remove.push(id.clone()); - } - } + continue 'monitors; + } - for id in &to_remove { - borders.remove(id); - } + // Destroy any borders not associated with the focused workspace + let container_ids = ws + .containers() + .iter() + .map(|c| c.id().clone()) + .collect::>(); + + let mut to_remove = vec![]; + for (id, border) in borders.iter() { + if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx + && !container_ids.contains(id) + { + border.destroy()?; + to_remove.push(id.clone()); + } + } + + for id in &to_remove { + borders.remove(id); + } + + for (idx, c) in ws.containers().iter().enumerate() { + // Update border when moving or resizing with mouse + if pending_move_op.is_some() && idx == ws.focused_container_idx() { + let restore_z_order = Z_ORDER.load(); + Z_ORDER.store(ZOrder::TopMost); + + let mut rect = WindowsApi::window_rect( + c.focused_window().copied().unwrap_or_default().hwnd(), + )?; + + while WindowsApi::lbutton_is_pressed() { + let border = match borders.entry(c.id().clone()) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + if let Ok(border) = Border::create(c.id()) { + entry.insert(border) + } else { + continue 'monitors; + } + } + }; + + let new_rect = WindowsApi::window_rect( + c.focused_window().copied().unwrap_or_default().hwnd(), + )?; + + if rect != new_rect { + rect = new_rect; + border.update(&rect, true)?; + } + } - for (idx, c) in ws.containers().iter().enumerate() { - // Update border when moving or resizing with mouse - if pending_move_op.is_some() && idx == ws.focused_container_idx() { - let restore_z_order = Z_ORDER.load(); - Z_ORDER.store(ZOrder::TopMost); + Z_ORDER.store(restore_z_order); - let mut rect = WindowsApi::window_rect( - c.focused_window().copied().unwrap_or_default().hwnd(), - )?; + continue 'monitors; + } - while WindowsApi::lbutton_is_pressed() { + // Get the border entry for this container from the map or create one let border = match borders.entry(c.id().clone()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { @@ -325,64 +416,39 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result } }; - let new_rect = WindowsApi::window_rect( - c.focused_window().copied().unwrap_or_default().hwnd(), - )?; - - if rect != new_rect { - rect = new_rect; - border.update(&rect, true)?; - } - } - - Z_ORDER.store(restore_z_order); + borders_monitors.insert(c.id().clone(), monitor_idx); - continue 'monitors; - } + #[allow(unused_assignments)] + let mut last_focus_state = None; - // Get the border entry for this container from the map or create one - let border = match borders.entry(c.id().clone()) { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => { - if let Ok(border) = Border::create(c.id()) { - entry.insert(border) + let new_focus_state = if idx != ws.focused_container_idx() + || monitor_idx != focused_monitor_idx + { + WindowKind::Unfocused + } else if c.windows().len() > 1 { + WindowKind::Stack } else { - continue 'monitors; + WindowKind::Single + }; + + // Update the focused state for all containers on this workspace + { + let mut focus_state = FOCUS_STATE.lock(); + last_focus_state = focus_state.insert(border.hwnd, new_focus_state); } - } - }; - - borders_monitors.insert(c.id().clone(), monitor_idx); - - #[allow(unused_assignments)] - let mut last_focus_state = None; - - let new_focus_state = if idx != ws.focused_container_idx() - || monitor_idx != focused_monitor_idx - { - WindowKind::Unfocused - } else if c.windows().len() > 1 { - WindowKind::Stack - } else { - WindowKind::Single - }; - - // Update the focused state for all containers on this workspace - { - let mut focus_state = FOCUS_STATE.lock(); - last_focus_state = focus_state.insert(border.hwnd, new_focus_state); - } - let rect = WindowsApi::window_rect( - c.focused_window().copied().unwrap_or_default().hwnd(), - )?; + let rect = WindowsApi::window_rect( + c.focused_window().copied().unwrap_or_default().hwnd(), + )?; - let should_invalidate = match last_focus_state { - None => true, - Some(last_focus_state) => last_focus_state != new_focus_state, - }; + let should_invalidate = match last_focus_state { + None => true, + Some(last_focus_state) => last_focus_state != new_focus_state, + }; - border.update(&rect, should_invalidate)?; + border.update(&rect, should_invalidate)?; + } + } } } } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index b917f7fa..f98d6556 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -27,6 +27,7 @@ use komorebi_core::config_generation::MatchingRule; use komorebi_core::config_generation::MatchingStrategy; use komorebi_core::ApplicationIdentifier; use komorebi_core::Axis; +use komorebi_core::BorderImplementation; use komorebi_core::FocusFollowsMouseImplementation; use komorebi_core::Layout; use komorebi_core::MoveBehaviour; @@ -39,6 +40,7 @@ use komorebi_core::WindowContainerBehaviour; use komorebi_core::WindowKind; use crate::border_manager; +use crate::border_manager::IMPLEMENTATION; use crate::border_manager::STYLE; use crate::colour::Rgb; use crate::current_virtual_desktop; @@ -1241,6 +1243,19 @@ impl WindowManager { SocketMessage::Border(enable) => { border_manager::BORDER_ENABLED.store(enable, Ordering::SeqCst); } + SocketMessage::BorderImplementation(implementation) => { + IMPLEMENTATION.store(implementation); + match IMPLEMENTATION.load() { + BorderImplementation::Komorebi => { + border_manager::destroy_all_borders()?; + } + BorderImplementation::Windows => { + self.remove_all_accents()?; + } + } + + border_manager::send_notification(); + } SocketMessage::BorderColour(kind, r, g, b) => match kind { WindowKind::Single => { border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index b4ac8a19..83f08232 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -1,5 +1,6 @@ use crate::border_manager; use crate::border_manager::ZOrder; +use crate::border_manager::IMPLEMENTATION; use crate::border_manager::STYLE; use crate::border_manager::Z_ORDER; use crate::colour::Colour; @@ -32,6 +33,7 @@ use crate::OBJECT_NAME_CHANGE_ON_LAUNCH; use crate::REGEX_IDENTIFIERS; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; use crate::WORKSPACE_RULES; +use komorebi_core::BorderImplementation; use komorebi_core::StackbarLabel; use komorebi_core::StackbarMode; @@ -279,6 +281,9 @@ pub struct StaticConfig { /// Active window border z-order (default: System) #[serde(skip_serializing_if = "Option::is_none")] pub border_z_order: Option, + /// Display an active window border (default: false) + #[serde(skip_serializing_if = "Option::is_none")] + pub border_implementation: Option, /// Add transparency to unfocused windows (default: false) #[serde(skip_serializing_if = "Option::is_none")] pub transparency: Option, @@ -483,6 +488,7 @@ impl From<&WindowManager> for StaticConfig { ), border_style: Option::from(STYLE.load()), border_z_order: Option::from(Z_ORDER.load()), + border_implementation: Option::from(IMPLEMENTATION.load()), default_workspace_padding: Option::from( DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst), ), @@ -557,6 +563,17 @@ impl StaticConfig { } STYLE.store(self.border_style.unwrap_or_default()); + IMPLEMENTATION.store(self.border_implementation.unwrap_or_default()); + match IMPLEMENTATION.load() { + BorderImplementation::Komorebi => { + border_manager::destroy_all_borders()?; + } + BorderImplementation::Windows => { + // TODO: figure out how to call wm.remove_all_accents here + } + } + + border_manager::send_notification(); transparency_manager::TRANSPARENCY_ENABLED .store(self.transparency.unwrap_or(false), Ordering::SeqCst); diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 9e513ca4..e0d87f77 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -275,6 +275,14 @@ impl Window { self.update_ex_style(&ex_style) } + pub fn set_accent(self, colour: u32) -> Result<()> { + WindowsApi::set_window_accent(self.hwnd, Some(colour)) + } + + pub fn remove_accent(self) -> Result<()> { + WindowsApi::set_window_accent(self.hwnd, None) + } + #[allow(dead_code)] pub fn update_style(self, style: &WindowStyle) -> Result<()> { WindowsApi::update_style(self.hwnd(), isize::try_from(style.bits())?) diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index cf1d2cfe..d9e67cf4 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -947,6 +947,8 @@ impl WindowManager { window.opaque()?; } + window.remove_accent()?; + window.restore(); } } @@ -956,6 +958,23 @@ impl WindowManager { Ok(()) } + #[tracing::instrument(skip(self))] + pub fn remove_all_accents(&mut self) -> Result<()> { + tracing::info!("removing all window accents"); + + for monitor in self.monitors_mut() { + for workspace in monitor.workspaces_mut() { + for containers in workspace.containers_mut() { + for window in containers.windows_mut() { + window.remove_accent()?; + } + } + } + } + + Ok(()) + } + #[tracing::instrument(skip(self))] fn handle_unmanaged_window_behaviour(&self) -> Result<()> { if matches!( diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index e044e6d0..db488627 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -21,7 +21,9 @@ use windows::Win32::Foundation::POINT; use windows::Win32::Foundation::WPARAM; use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute; use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute; +use windows::Win32::Graphics::Dwm::DWMWA_BORDER_COLOR; use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED; +use windows::Win32::Graphics::Dwm::DWMWA_COLOR_NONE; use windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS; use windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE; use windows::Win32::Graphics::Dwm::DWMWCP_ROUND; @@ -955,6 +957,19 @@ impl WindowsApi { .process() } + pub fn set_window_accent(hwnd: isize, color: Option) -> Result<()> { + let col_ref = COLORREF(color.unwrap_or(DWMWA_COLOR_NONE)); + unsafe { + DwmSetWindowAttribute( + HWND(hwnd), + DWMWA_BORDER_COLOR, + std::ptr::addr_of!(col_ref).cast(), + 4, + ) + } + .process() + } + pub fn create_border_window(name: PCWSTR, instance: HMODULE) -> Result { unsafe { let hwnd = CreateWindowExW( diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 545d8346..40022ace 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -684,6 +684,19 @@ struct BorderOffset { /// Desired offset of the window border offset: i32, } +#[derive(Parser)] +struct BorderStyle { + /// Desired border style + #[clap(value_enum)] + style: komorebi_core::BorderStyle, +} + +#[derive(Parser)] +struct BorderImplementation { + /// Desired border implementation + #[clap(value_enum)] + style: komorebi_core::BorderImplementation, +} #[derive(Parser)] #[allow(clippy::struct_excessive_bools)] @@ -1176,6 +1189,12 @@ enum SubCommand { #[clap(arg_required_else_help = true)] #[clap(alias = "active-window-border-offset")] BorderOffset(BorderOffset), + /// Set the border style + #[clap(arg_required_else_help = true)] + BorderStyle(BorderStyle), + /// Set the border implementation + #[clap(arg_required_else_help = true)] + BorderImplementation(BorderImplementation), /// Enable or disable transparency for unfocused windows #[clap(arg_required_else_help = true)] Transparency(Transparency), @@ -2269,6 +2288,12 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue SubCommand::BorderOffset(arg) => { send_message(&SocketMessage::BorderOffset(arg.offset).as_bytes()?)?; } + SubCommand::BorderStyle(arg) => { + send_message(&SocketMessage::BorderStyle(arg.style).as_bytes()?)?; + } + SubCommand::BorderImplementation(arg) => { + send_message(&SocketMessage::BorderImplementation(arg.style).as_bytes()?)?; + } SubCommand::Transparency(arg) => { send_message(&SocketMessage::Transparency(arg.boolean_state.into()).as_bytes()?)?; }