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; - - if monitors == previous_snapshot - // handle the window dragging edge case - && pending_move_op == previous_pending_move_op - { - should_process_notification = false; - } - - // handle the pause edge case - if is_paused && !previous_is_paused { - should_process_notification = true; - } - - // handle the unpause edge case - if previous_is_paused && !is_paused { - should_process_notification = true; - } - - // handle the retile edge case - if !should_process_notification && BORDER_STATE.lock().is_empty() { - should_process_notification = true; - } - - if !should_process_notification { - tracing::trace!("monitor state matches latest snapshot, skipping notification"); - continue 'receiver; - } - - 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()?; - } - - borders.clear(); - - previous_is_paused = is_paused; - 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()); - } - } - - for id in &to_remove { - borders.remove(id); - } - - continue 'monitors; - } - - // 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; - } - } - }; - - borders_monitors.insert(monocle.id().clone(), monitor_idx); - - { - let mut focus_state = FOCUS_STATE.lock(); - focus_state.insert( - border.hwnd, - if monitor_idx != focused_monitor_idx { + 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 - }, - ); - } + }; - let rect = WindowsApi::window_rect( - monocle.focused_window().copied().unwrap_or_default().hwnd(), - )?; + monocle + .focused_window() + .copied() + .unwrap_or_default() + .set_accent(window_kind_colour(window_kind))?; - border.update(&rect, true)?; + continue 'monitors; + } - 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()); + 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 + }; + + c.focused_window() + .copied() + .unwrap_or_default() + .set_accent(window_kind_colour(window_kind))?; } } + } + } + BorderImplementation::Komorebi => { + let mut should_process_notification = true; - for id in &to_remove { - borders.remove(id); - } - - continue 'monitors; + if monitors == previous_snapshot + // handle the window dragging edge case + && pending_move_op == previous_pending_move_op + { + should_process_notification = false; } - 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()); - } - } - - for id in &to_remove { - borders.remove(id); - } - - continue 'monitors; + // handle the pause edge case + if is_paused && !previous_is_paused { + should_process_notification = true; } - // Destroy any borders not associated with the focused workspace - let container_ids = ws - .containers() - .iter() - .map(|c| c.id().clone()) - .collect::>(); + // handle the unpause edge case + if previous_is_paused && !is_paused { + should_process_notification = true; + } - 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) - { + // handle the retile edge case + if !should_process_notification && BORDER_STATE.lock().is_empty() { + should_process_notification = true; + } + + if !should_process_notification { + tracing::trace!("monitor state matches latest snapshot, skipping notification"); + continue 'receiver; + } + + 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()?; - to_remove.push(id.clone()); } + + borders.clear(); + + previous_is_paused = is_paused; + continue 'receiver; } - for id in &to_remove { - borders.remove(id); - } + '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()); + } + } - 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); + for id in &to_remove { + borders.remove(id); + } - let mut rect = WindowsApi::window_rect( - c.focused_window().copied().unwrap_or_default().hwnd(), - )?; + continue 'monitors; + } - while WindowsApi::lbutton_is_pressed() { + // 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; + } + } + }; + + borders_monitors.insert(monocle.id().clone(), monitor_idx); + + { + let mut focus_state = FOCUS_STATE.lock(); + focus_state.insert( + border.hwnd, + if monitor_idx != focused_monitor_idx { + WindowKind::Unfocused + } else { + WindowKind::Monocle + }, + ); + } + + let rect = WindowsApi::window_rect( + monocle.focused_window().copied().unwrap_or_default().hwnd(), + )?; + + 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()); + } + } + + for id in &to_remove { + borders.remove(id); + } + + continue 'monitors; + } + + 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()); + } + } + + for id in &to_remove { + borders.remove(id); + } + + continue 'monitors; + } + + // 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)?; + } + } + + Z_ORDER.store(restore_z_order); + + continue 'monitors; + } + + // 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( + 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(), )?; - if rect != new_rect { - rect = new_rect; - border.update(&rect, true)?; - } + let should_invalidate = match last_focus_state { + None => true, + Some(last_focus_state) => last_focus_state != new_focus_state, + }; + + border.update(&rect, should_invalidate)?; } - - Z_ORDER.store(restore_z_order); - - continue 'monitors; } - - // 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) - } else { - continue 'monitors; - } - } - }; - - 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 should_invalidate = match last_focus_state { - None => true, - Some(last_focus_state) => last_focus_state != new_focus_state, - }; - - border.update(&rect, should_invalidate)?; } } } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index b917f7fa..39ddfa2f 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 => { + self.remove_all_accents()?; + } + BorderImplementation::Windows => { + border_manager::destroy_all_borders()?; + } + } + + 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()?)?; }