From 0776ca15652383bb21f34bec6155776a6e01e5ca Mon Sep 17 00:00:00 2001 From: alex-ds13 <145657253+alex-ds13@users.noreply.github.com> Date: Fri, 28 Feb 2025 16:03:59 +0000 Subject: [PATCH] feat(border): cache borders on all workspaces This commit refactors the `border_manager` with the following changes: - Rework the way the borders are created and their pointer is sent to the window message handler. Now we also store the same pointer as a `Box` on the `border_manager`'s `BORDER_STATE`. This means that the borders we have are exactly the same ones that the border window itself is accessing so we can now store the border's info inside the `Border` struct and all of it will be accessible by the border window as well. This makes it so the "ACTUAL" border struct is the one created on the thread of the `Border::create()` function and when that thread finishes (after the border window closes) it will handle the drop of the border itself. However this means we need to be careful with our own stored `Box` since it will point to the same memory we can't let the compiler dropped them as usual or otherwise it would create heap corruption errors. So this commit creates a special function called `destroy_border()` to actually close the window border without dropping the border, since it will be later dropped by the thread that created it. - Remove `BORDERS_MONITORS`, `FOCUS_STATE` and `RENDER_TARGETS` arc mutexes, since now this info is stored on the border itself. - Change the `BORDER_STATE` to now map an id (container or window) to a `Box`. - Change the `WINDOWS_BORDERS` to now map a window hwnd to a border id. - Create new struct `BorderInfo` which as the border window hwnd and the border kind. This struct is what is now returned by the function `window_border()` which checks if some window as a border attached to it and if it does it returns this info. There is no need to clone the entire border. If in the future we need more info we can just add it to this struct. - Change the way we clear the `BORDER_STATE`. Like mentioned before we need to be sure we don't drop the `Box` when removing it, so now we use the `.drain` function to remove all the borders as an iterator and we call the `destroy_border()` on each border we are removing. - We now check if a border's `tracking_hwnd` has changed and if it does we simply update it instead of destroying the border and create a new one. - Create function `delete_border` so that we can remove a border properly from outside the `border_manager`. - Create function `hide_border` which spawns a new thread that searches if a window hwnd has a border attached to it and if it does it hides said border window. This function is called on every `window.hide()`. - Create function `show_border` which spawns a new thread that searches if a window hwnd has a border attached to it and if it does it restores said border window. This function is called on every `window.restore()`. - This commit also changes the previous `window.hide()` and `window.restore()` functions to be named: - `window.hide_with_border(hide_border: bool)`: this is the same function as before but adds a check at the end in case `hide_border` is true it calls `border_manager::hide_border()`. A new function was created with the same name as before `window.hide()` which by default calls this new function with `hide_border = true`. - `window.restore_with_border(restore_border: bool)`: this is the same function as before but adds a check at the end in case `hide_border` is true it calls `border_manager::hide_border()`. A new function was created with the same name as before `window.hide()` which by default calls this new function with `hide_border = true`. - This commit creates a new function on `Container` called `load_focused_window_ignore_borders()` which performs the same as `load_focused_window()` but it ignores the borders when hiding and restoring the windows. This function, along with the `hide_with_border(false)` and `restore_with_border(false)` are used on all functions related to changing focus on a stack since if we let the borders be hidden and restored when cycling or changing focus on a stack the border would flicker slightly, this prevents that. Ignore borders when clicking on the stackbar as well. (P.S. there might still be other places that I forgot to use these new functions, but if that is the case then what will happen is a simple flicker of the stack border...) - The `remove_window` from `Workspace` needs to call the `border_manager::delete_border()` so that wew make sure we remove that windows's border window as well if it exists. This is essential when enforcing workspace rules, otherwise the border would be left behind. - Lastly, but not least, now that we hide the borders windows along with their tracking window, we no longer remove the borders when swapping workspaces or when toggling monocle, etc. Instead we keep all borders of all workspaces cached and simply hide them. They are only removed when their tracking window is closed or cloaked on a stack (since on a stack we only keep one border for all the entire stack container). This means that when changing between workspaces we no longer see the borders showing up delayed after the windows show up. Now both the window and it's border show up as if they are one and the same. --- komorebi-client/src/lib.rs | 1 + komorebi/src/border_manager/border.rs | 40 +-- komorebi/src/border_manager/mod.rs | 338 +++++++++++----------- komorebi/src/container.rs | 11 + komorebi/src/core/mod.rs | 3 +- komorebi/src/stackbar_manager/stackbar.rs | 4 +- komorebi/src/window.rs | 27 +- komorebi/src/window_manager.rs | 4 +- komorebi/src/windows_api.rs | 2 +- komorebi/src/windows_callbacks.rs | 12 +- komorebi/src/workspace.rs | 3 + 11 files changed, 239 insertions(+), 206 deletions(-) diff --git a/komorebi-client/src/lib.rs b/komorebi-client/src/lib.rs index dea7c6a6..4614fc20 100644 --- a/komorebi-client/src/lib.rs +++ b/komorebi-client/src/lib.rs @@ -4,6 +4,7 @@ pub use komorebi::animation::prefix::AnimationPrefix; pub use komorebi::animation::PerAnimationPrefixConfig; pub use komorebi::asc::ApplicationSpecificConfiguration; +pub use komorebi::border_manager::BorderInfo; pub use komorebi::colour::Colour; pub use komorebi::colour::Rgb; pub use komorebi::config_generation::ApplicationConfiguration; diff --git a/komorebi/src/border_manager/border.rs b/komorebi/src/border_manager/border.rs index 9c99ae47..c156956c 100644 --- a/komorebi/src/border_manager/border.rs +++ b/komorebi/src/border_manager/border.rs @@ -3,8 +3,6 @@ use crate::border_manager::RenderTarget; use crate::border_manager::WindowKind; use crate::border_manager::BORDER_OFFSET; use crate::border_manager::BORDER_WIDTH; -use crate::border_manager::FOCUS_STATE; -use crate::border_manager::RENDER_TARGETS; use crate::border_manager::STYLE; use crate::core::BorderStyle; use crate::core::Rect; @@ -114,6 +112,8 @@ pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL { #[derive(Debug, Clone)] pub struct Border { pub hwnd: isize, + pub id: String, + pub monitor_idx: Option, pub render_target: OnceLock, pub tracking_hwnd: isize, pub window_rect: Rect, @@ -130,6 +130,8 @@ impl From for Border { fn from(value: isize) -> Self { Self { hwnd: value, + id: String::new(), + monitor_idx: None, render_target: OnceLock::new(), tracking_hwnd: 0, window_rect: Rect::default(), @@ -149,7 +151,11 @@ impl Border { HWND(windows_api::as_ptr!(self.hwnd)) } - pub fn create(id: &str, tracking_hwnd: isize) -> color_eyre::Result { + pub fn create( + id: &str, + tracking_hwnd: isize, + monitor_idx: usize, + ) -> color_eyre::Result> { let name: Vec = format!("komoborder-{id}\0").encode_utf16().collect(); let class_name = PCWSTR(name.as_ptr()); @@ -168,9 +174,12 @@ impl Border { let (border_sender, border_receiver) = mpsc::channel(); let instance = h_module.0 as isize; + let container_id = id.to_owned(); std::thread::spawn(move || -> color_eyre::Result<()> { let mut border = Self { hwnd: 0, + id: container_id, + monitor_idx: Some(monitor_idx), render_target: OnceLock::new(), tracking_hwnd, window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(), @@ -183,12 +192,15 @@ impl Border { brushes: HashMap::new(), }; - let border_pointer = std::ptr::addr_of!(border); + let border_pointer = &raw mut border; let hwnd = WindowsApi::create_border_window(PCWSTR(name.as_ptr()), instance, border_pointer)?; - border.hwnd = hwnd; - border_sender.send(border_pointer as isize)?; + let boxed = unsafe { + (*border_pointer).hwnd = hwnd; + Box::from_raw(border_pointer) + }; + border_sender.send(boxed)?; let mut msg: MSG = MSG::default(); @@ -207,8 +219,7 @@ impl Border { Ok(()) }); - let border_ref = border_receiver.recv()?; - let border = unsafe { &mut *(border_ref as *mut Border) }; + let mut border = border_receiver.recv()?; // I have literally no idea, apparently this is to get rid of the black pixels // around the edges of rounded corners? @lukeyou05 borrowed this from PowerToys @@ -292,17 +303,13 @@ impl Border { } }; - let mut render_targets = RENDER_TARGETS.lock(); - render_targets.insert(border.hwnd, RenderTarget(render_target)); - Ok(border.clone()) + Ok(border) }, Err(error) => Err(error.into()), } } pub fn destroy(&self) -> color_eyre::Result<()> { - let mut render_targets = RENDER_TARGETS.lock(); - render_targets.remove(&self.hwnd); WindowsApi::close_window(self.hwnd) } @@ -361,7 +368,7 @@ impl Border { return LRESULT(0); } - let reference_hwnd = lparam.0; + let reference_hwnd = (*border_pointer).tracking_hwnd; let old_rect = (*border_pointer).window_rect; let rect = WindowsApi::window_rect(reference_hwnd).unwrap_or_default(); @@ -475,11 +482,6 @@ impl Border { }); // Get window kind and color - (*border_pointer).window_kind = FOCUS_STATE - .lock() - .get(&(window.0 as isize)) - .copied() - .unwrap_or(WindowKind::Unfocused); let window_kind = (*border_pointer).window_kind; if let Some(brush) = (*border_pointer).brushes.get(&window_kind) { render_target.BeginDraw(); diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs index f0e80a34..0df4075d 100644 --- a/komorebi/src/border_manager/mod.rs +++ b/komorebi/src/border_manager/mod.rs @@ -5,6 +5,7 @@ use crate::core::BorderImplementation; use crate::core::BorderStyle; use crate::core::WindowKind; use crate::ring::Ring; +use crate::windows_api; use crate::workspace::WorkspaceLayer; use crate::workspace_reconciliator::ALT_TAB_HWND; use crate::Colour; @@ -31,6 +32,7 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::OnceLock; use strum::Display; +use windows::Win32::Foundation::HWND; use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget; pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8); @@ -54,11 +56,8 @@ lazy_static! { } lazy_static! { - static ref BORDERS_MONITORS: Mutex> = Mutex::new(HashMap::new()); - static ref BORDER_STATE: Mutex> = Mutex::new(HashMap::new()); - static ref WINDOWS_BORDERS: Mutex> = Mutex::new(HashMap::new()); - static ref FOCUS_STATE: Mutex> = Mutex::new(HashMap::new()); - static ref RENDER_TARGETS: Mutex> = Mutex::new(HashMap::new()); + static ref BORDER_STATE: Mutex>> = Mutex::new(HashMap::new()); + static ref WINDOWS_BORDERS: Mutex> = Mutex::new(HashMap::new()); } #[derive(Debug, Clone)] @@ -75,6 +74,18 @@ impl Deref for RenderTarget { pub struct Notification(pub Option); +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct BorderInfo { + pub border_hwnd: isize, + pub window_kind: WindowKind, +} + +impl BorderInfo { + pub fn hwnd(&self) -> HWND { + HWND(windows_api::as_ptr!(self.border_hwnd)) + } +} + static CHANNEL: OnceLock<(Sender, Receiver)> = OnceLock::new(); pub fn channel() -> &'static (Sender, Receiver) { @@ -89,8 +100,13 @@ fn event_rx() -> Receiver { channel().1.clone() } -pub fn window_border(hwnd: isize) -> Option { - WINDOWS_BORDERS.lock().get(&hwnd).cloned() +pub fn window_border(hwnd: isize) -> Option { + WINDOWS_BORDERS.lock().get(&hwnd).and_then(|id| { + BORDER_STATE.lock().get(id).map(|b| BorderInfo { + border_hwnd: b.hwnd, + window_kind: b.window_kind, + }) + }) } pub fn send_notification(hwnd: Option) { @@ -106,15 +122,11 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> { borders.iter().map(|b| b.1.hwnd).collect::>() ); - for (_, border) in borders.iter() { - let _ = border.destroy(); + for (_, border) in borders.drain() { + let _ = destroy_border(border); } - borders.clear(); - BORDERS_MONITORS.lock().clear(); WINDOWS_BORDERS.lock().clear(); - FOCUS_STATE.lock().clear(); - RENDER_TARGETS.lock().clear(); let mut remaining_hwnds = vec![]; @@ -127,7 +139,7 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> { tracing::info!("purging unknown borders: {:?}", remaining_hwnds); for hwnd in remaining_hwnds { - let _ = Border::from(hwnd).destroy(); + let _ = destroy_border(Box::new(Border::from(hwnd))); } } @@ -276,8 +288,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result // border. (fw != &foreground_window && window_border(*fw) - .map(|b| b.window_kind == WindowKind::Floating) - .unwrap_or_default()) + .is_some_and(|b| b.window_kind == WindowKind::Floating)) }); // when the focused window has an `Unfocused` border kind, usually this happens if @@ -309,9 +320,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result } let mut borders = BORDER_STATE.lock(); - let mut borders_monitors = BORDERS_MONITORS.lock(); let mut windows_borders = WINDOWS_BORDERS.lock(); - let mut focus_state = FOCUS_STATE.lock(); // If borders are disabled if !BORDER_ENABLED.load_consume() @@ -321,14 +330,11 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result || ALT_TAB_HWND.load().is_some() { // Destroy the borders we know about - for (_, border) in borders.iter() { - border.destroy()?; + for (_, border) in borders.drain() { + destroy_border(border)?; } - borders.clear(); - borders_monitors.clear(); windows_borders.clear(); - focus_state.clear(); previous_is_paused = is_paused; continue 'receiver; @@ -343,8 +349,6 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result remove_borders( &mut borders, &mut windows_borders, - &mut focus_state, - &mut borders_monitors, monitor_idx, |_, _| true, )?; @@ -355,12 +359,16 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result // Handle the monocle container separately if let Some(monocle) = ws.monocle_container() { let mut new_border = false; - let border = match borders.entry(monocle.id().clone()) { + let focused_window_hwnd = + monocle.focused_window().map(|w| w.hwnd).unwrap_or_default(); + let id = monocle.id().clone(); + let border = match borders.entry(id.clone()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { if let Ok(border) = Border::create( monocle.id(), - monocle.focused_window().copied().unwrap_or_default().hwnd, + focused_window_hwnd, + monitor_idx, ) { new_border = true; entry.insert(border) @@ -376,32 +384,41 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result WindowKind::Monocle }; border.window_kind = new_focus_state; - focus_state.insert(border.hwnd, new_focus_state); - let reference_hwnd = - monocle.focused_window().copied().unwrap_or_default().hwnd; + // Update the borders tracking_hwnd in case it changed and remove the + // old `tracking_hwnd` from `WINDOWS_BORDERS` if needed. + if border.tracking_hwnd != focused_window_hwnd { + if let Some(previous) = windows_borders.get(&border.tracking_hwnd) { + // Only remove the border from `windows_borders` if it + // still corresponds to the same border, if doesn't then + // it means it was already updated by another border for + // that window and in that case we don't want to remove it. + if previous == &id { + windows_borders.remove(&border.tracking_hwnd); + } + } + border.tracking_hwnd = focused_window_hwnd; + if !WindowsApi::is_window_visible(border.hwnd) { + WindowsApi::restore_window(border.hwnd); + } + } - let rect = WindowsApi::window_rect(reference_hwnd)?; + let rect = WindowsApi::window_rect(focused_window_hwnd)?; + border.window_rect = rect; if new_border { - border.set_position(&rect, reference_hwnd)?; + border.set_position(&rect, focused_window_hwnd)?; } border.invalidate(); - borders_monitors.insert(monocle.id().clone(), monitor_idx); - windows_borders.insert( - monocle.focused_window().cloned().unwrap_or_default().hwnd, - border.clone(), - ); + windows_borders.insert(focused_window_hwnd, id); let border_hwnd = border.hwnd; // Remove all borders on this monitor except monocle remove_borders( &mut borders, &mut windows_borders, - &mut focus_state, - &mut borders_monitors, monitor_idx, |_, b| border_hwnd != b.hwnd, )?; @@ -420,8 +437,6 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result remove_borders( &mut borders, &mut windows_borders, - &mut focus_state, - &mut borders_monitors, monitor_idx, |_, _| true, )?; @@ -444,56 +459,22 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result remove_borders( &mut borders, &mut windows_borders, - &mut focus_state, - &mut borders_monitors, monitor_idx, |id, _| !container_and_floating_window_ids.contains(id), )?; 'containers: for (idx, c) in ws.containers().iter().enumerate() { - // In case this container is a stack we need to check it's - // unfocused windows to remove any attached border - let is_stack = c.windows().len() > 1; - if is_stack { - let focused_window_idx = c.focused_window_idx(); - let potential_stacked_border_handles = c - .windows() - .iter() - .enumerate() - .flat_map(|(i, w)| { - if i != focused_window_idx { - windows_borders.get(&w.hwnd).map(|b| b.hwnd) - } else { - None - } - }) - .collect::>(); - - if !potential_stacked_border_handles.is_empty() { - tracing::debug!( - "purging stacked borders: {:?}", - potential_stacked_border_handles - ); - remove_borders( - &mut borders, - &mut windows_borders, - &mut focus_state, - &mut borders_monitors, - monitor_idx, - |_, b| potential_stacked_border_handles.contains(&b.hwnd), - )?; - } - } - let focused_window_hwnd = c.focused_window().map(|w| w.hwnd).unwrap_or_default(); + let id = c.id().clone(); // Get the border entry for this container from the map or create one let mut new_border = false; - let border = match borders.entry(c.id().clone()) { + let border = match borders.entry(id.clone()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { - if let Ok(border) = Border::create(c.id(), focused_window_hwnd) + if let Ok(border) = + Border::create(c.id(), focused_window_hwnd, monitor_idx) { new_border = true; entry.insert(border) @@ -503,8 +484,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result } }; - #[allow(unused_assignments)] - let mut last_focus_state = None; + let last_focus_state = border.window_kind; let new_focus_state = if idx != ws.focused_container_idx() || monitor_idx != focused_monitor_idx @@ -516,40 +496,24 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result } else { WindowKind::Single }; + border.window_kind = new_focus_state; - last_focus_state = focus_state.get(&border.hwnd).copied(); - - // If this container's border was previously tracking a different - // window, then we need to destroy that border and create a new one - // tracking the correct window. + // Update the borders `tracking_hwnd` in case it changed and remove the + // old `tracking_hwnd` from `WINDOWS_BORDERS` if needed. if border.tracking_hwnd != focused_window_hwnd { - // Create new border - if let Ok(b) = Border::create( - c.id(), - c.focused_window().copied().unwrap_or_default().hwnd, - ) { - // Destroy previously stacked border window and remove its hwnd - // and tracking_hwnd. - border.destroy()?; - focus_state.remove(&border.hwnd); - if let Some(previous) = - windows_borders.get(&border.tracking_hwnd) - { - // Only remove the border from `windows_borders` if it - // still is the same border, if it isn't then it means it - // was already updated by another border for that window - // and in that case we don't want to remove it. - if previous.hwnd == border.hwnd { - windows_borders.remove(&border.tracking_hwnd); - } + if let Some(previous) = windows_borders.get(&border.tracking_hwnd) { + // Only remove the border from `windows_borders` if it + // still corresponds to the same border, if doesn't then + // it means it was already updated by another border for + // that window and in that case we don't want to remove it. + if previous == &id { + windows_borders.remove(&border.tracking_hwnd); } - - // Replace with new border - new_border = true; - *border = b; - } else { - continue 'monitors; + } + border.tracking_hwnd = focused_window_hwnd; + if !WindowsApi::is_window_visible(border.hwnd) { + WindowsApi::restore_window(border.hwnd); } } @@ -559,51 +523,38 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result let rect = match WindowsApi::window_rect(focused_window_hwnd) { Ok(rect) => rect, Err(_) => { - remove_border( - c.id(), - &mut borders, - &mut windows_borders, - &mut focus_state, - &mut borders_monitors, - )?; + remove_border(c.id(), &mut borders, &mut windows_borders)?; continue 'containers; } }; + border.window_rect = rect; let layer_changed = previous_layer != workspace_layer; - let should_invalidate = match last_focus_state { - None => true, - Some(last_focus_state) => { - (last_focus_state != new_focus_state) || layer_changed - } - }; - - if new_border || should_invalidate { - border.set_position(&rect, focused_window_hwnd)?; - } + let should_invalidate = new_border + || (last_focus_state != new_focus_state) + || layer_changed; if should_invalidate { + border.set_position(&rect, focused_window_hwnd)?; border.invalidate(); } - borders_monitors.insert(c.id().clone(), monitor_idx); - windows_borders.insert( - c.focused_window().cloned().unwrap_or_default().hwnd, - border.clone(), - ); - focus_state.insert(border.hwnd, new_focus_state); + windows_borders.insert(focused_window_hwnd, id); } { for window in ws.floating_windows() { let mut new_border = false; - let border = match borders.entry(window.hwnd.to_string()) { + let id = window.hwnd.to_string(); + let border = match borders.entry(id.clone()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { - if let Ok(border) = - Border::create(&window.hwnd.to_string(), window.hwnd) - { + if let Ok(border) = Border::create( + &window.hwnd.to_string(), + window.hwnd, + monitor_idx, + ) { new_border = true; entry.insert(border) } else { @@ -612,39 +563,31 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result } }; - #[allow(unused_assignments)] - let mut last_focus_state = None; - let mut new_focus_state = WindowKind::Unfocused; + let last_focus_state = border.window_kind; - if foreground_window == window.hwnd { - new_focus_state = WindowKind::Floating; - } + let new_focus_state = if foreground_window == window.hwnd { + WindowKind::Floating + } else { + WindowKind::Unfocused + }; border.window_kind = new_focus_state; - last_focus_state = focus_state.get(&border.hwnd).copied(); let rect = WindowsApi::window_rect(window.hwnd)?; + border.window_rect = rect; let layer_changed = previous_layer != workspace_layer; - let should_invalidate = match last_focus_state { - None => true, - Some(last_focus_state) => { - last_focus_state != new_focus_state || layer_changed - } - }; - - if new_border { - border.set_position(&rect, window.hwnd)?; - } + let should_invalidate = new_border + || (last_focus_state != new_focus_state) + || layer_changed; if should_invalidate { + border.set_position(&rect, window.hwnd)?; border.invalidate(); } - borders_monitors.insert(window.hwnd.to_string(), monitor_idx); - windows_borders.insert(window.hwnd, border.clone()); - focus_state.insert(border.hwnd, new_focus_state); + windows_borders.insert(window.hwnd, id); } } } @@ -667,24 +610,27 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result /// the container id and the border and returns a bool, if true that border /// will be removed. fn remove_borders( - borders: &mut HashMap, - windows_borders: &mut HashMap, - focus_state: &mut HashMap, - borders_monitors: &mut HashMap, + borders: &mut HashMap>, + windows_borders: &mut HashMap, monitor_idx: usize, condition: impl Fn(&String, &Border) -> bool, ) -> color_eyre::Result<()> { let mut to_remove = vec![]; for (id, border) in borders.iter() { - if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx + // if border is on this monitor + if border.monitor_idx.is_some_and(|idx| idx == monitor_idx) + // and the condition applies && condition(id, border) + // and the border is visible (we don't remove hidden borders) + && WindowsApi::is_window_visible(border.hwnd) { + // we mark it to be removed to_remove.push(id.clone()); } } for id in &to_remove { - remove_border(id, borders, windows_borders, focus_state, borders_monitors)?; + remove_border(id, borders, windows_borders)?; } Ok(()) @@ -693,21 +639,71 @@ fn remove_borders( /// Removes the border with `id` and all its related info from all maps fn remove_border( id: &str, - borders: &mut HashMap, - windows_borders: &mut HashMap, - focus_state: &mut HashMap, - borders_monitors: &mut HashMap, + borders: &mut HashMap>, + windows_borders: &mut HashMap, ) -> color_eyre::Result<()> { if let Some(removed_border) = borders.remove(id) { - removed_border.destroy()?; windows_borders.remove(&removed_border.tracking_hwnd); - focus_state.remove(&removed_border.hwnd); + destroy_border(removed_border)?; } - borders_monitors.remove(id); Ok(()) } +/// IMPORTANT: BEWARE when changing this function. We need to make sure that we don't let the +/// `Box` be dropped normally. We need to turn the `Box` into the raw pointer and use that +/// pointer to call the `.destroy()` funtion of the border so it closes the window. This way the +/// `Box` is consumed and the pointer is dropped like a normal `Copy` number instead of trying to +/// drop the struct it points to. The actual border is owned by the thread that created the window +/// and once the window closes that thread gets out of its loop, finishes and properly disposes of +/// the border. +fn destroy_border(border: Box) -> color_eyre::Result<()> { + let raw_pointer = Box::into_raw(border); + unsafe { + (*raw_pointer).destroy()?; + } + Ok(()) +} + +/// Removes the border around window with `tracking_hwnd` if it exists +pub fn delete_border(tracking_hwnd: isize) { + std::thread::spawn(move || { + let id = { + WINDOWS_BORDERS + .lock() + .get(&tracking_hwnd) + .cloned() + .unwrap_or_default() + }; + + let mut borders = BORDER_STATE.lock(); + let mut windows_borders = WINDOWS_BORDERS.lock(); + + if let Err(error) = remove_border(&id, &mut borders, &mut windows_borders) { + tracing::error!("Failed to delete border: {}", error); + } + }); +} + +/// Shows the border around window with `tracking_hwnd` if it exists +pub fn show_border(tracking_hwnd: isize) { + std::thread::spawn(move || { + if let Some(border_info) = window_border(tracking_hwnd) { + WindowsApi::restore_window(border_info.border_hwnd); + } + }); +} + +/// Hides the border around window with `tracking_hwnd` if it exists, unless the border kind is a +/// `Stack` border. +pub fn hide_border(tracking_hwnd: isize) { + std::thread::spawn(move || { + if let Some(border_info) = window_border(tracking_hwnd) { + WindowsApi::hide_window(border_info.border_hwnd); + } + }); +} + #[derive(Debug, Copy, Clone, Display, Serialize, Deserialize, PartialEq)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub enum ZOrder { diff --git a/komorebi/src/container.rs b/komorebi/src/container.rs index 7dbf98ca..c53acb32 100644 --- a/komorebi/src/container.rs +++ b/komorebi/src/container.rs @@ -63,6 +63,17 @@ impl Container { } } + pub fn load_focused_window_ignore_borders(&mut self) { + let focused_idx = self.focused_window_idx(); + for (i, window) in self.windows_mut().iter_mut().enumerate() { + if i == focused_idx { + window.restore_with_border(false); + } else { + window.hide_with_border(false); + } + } + } + pub fn hwnd_from_exe(&self, exe: &str) -> Option { for window in self.windows() { if let Ok(window_exe) = window.exe() { diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index 49297f97..59260318 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -291,13 +291,14 @@ pub enum BorderImplementation { } #[derive( - Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq, Eq, Hash, + Copy, Clone, Debug,Default, Serialize, Deserialize, Display, EnumString, ValueEnum, PartialEq, Eq, Hash, )] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub enum WindowKind { Single, Stack, Monocle, + #[default] Unfocused, Floating, } diff --git a/komorebi/src/stackbar_manager/stackbar.rs b/komorebi/src/stackbar_manager/stackbar.rs index a03f120e..2e547ebe 100644 --- a/komorebi/src/stackbar_manager/stackbar.rs +++ b/komorebi/src/stackbar_manager/stackbar.rs @@ -350,7 +350,7 @@ impl Stackbar { } // Restore the window corresponding to the tab we have clicked - window.restore(); + window.restore_with_border(false); if let Err(err) = window.focus(false) { tracing::error!( "stackbar WMLBUTTONDOWN focus error: hwnd {} ({})", @@ -361,7 +361,7 @@ impl Stackbar { } else { // Hide any windows in the stack that don't correspond to the window // we have clicked - window.hide(); + window.hide_with_border(false); } } } diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index d2367d33..df61dd43 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -10,6 +10,7 @@ use crate::animation::ANIMATION_ENABLED_PER_ANIMATION; use crate::animation::ANIMATION_MANAGER; use crate::animation::ANIMATION_STYLE_GLOBAL; use crate::animation::ANIMATION_STYLE_PER_ANIMATION; +use crate::border_manager; use crate::com::SetCloak; use crate::core::config_generation::IdWithIdentifier; use crate::core::config_generation::MatchingRule; @@ -476,7 +477,7 @@ impl Window { WindowsApi::is_window_visible(self.hwnd) } - pub fn hide(self) { + pub fn hide_with_border(self, hide_border: bool) { let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock(); if !programmatically_hidden_hwnds.contains(&self.hwnd) { programmatically_hidden_hwnds.push(self.hwnd); @@ -488,9 +489,16 @@ impl Window { HidingBehaviour::Minimize => WindowsApi::minimize_window(self.hwnd), HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 2), } + if hide_border { + border_manager::hide_border(self.hwnd); + } } - pub fn restore(self) { + pub fn hide(self) { + self.hide_with_border(true); + } + + pub fn restore_with_border(self, restore_border: bool) { let mut programmatically_hidden_hwnds = HIDDEN_HWNDS.lock(); if let Some(idx) = programmatically_hidden_hwnds .iter() @@ -506,6 +514,13 @@ impl Window { } HidingBehaviour::Cloak => SetCloak(self.hwnd(), 1, 0), } + if restore_border { + border_manager::show_border(self.hwnd); + } + } + + pub fn restore(self) { + self.restore_with_border(true); } pub fn minimize(self) { @@ -738,8 +753,8 @@ impl Window { /// it raises it as well. pub fn raise(self) -> Result<()> { WindowsApi::raise_window(self.hwnd)?; - if let Some(border) = crate::border_manager::window_border(self.hwnd) { - WindowsApi::raise_window(border.hwnd)?; + if let Some(border_info) = crate::border_manager::window_border(self.hwnd) { + WindowsApi::raise_window(border_info.border_hwnd)?; } Ok(()) } @@ -750,8 +765,8 @@ impl Window { /// it lowers it as well. pub fn lower(self) -> Result<()> { WindowsApi::lower_window(self.hwnd)?; - if let Some(border) = crate::border_manager::window_border(self.hwnd) { - WindowsApi::lower_window(border.hwnd)?; + if let Some(border_info) = crate::border_manager::window_border(self.hwnd) { + WindowsApi::lower_window(border_info.border_hwnd)?; } Ok(()) } diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index fce739fb..1bd92b7d 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -2530,7 +2530,7 @@ impl WindowManager { let next_idx = direction.next_idx(current_idx, len); container.focus_window(next_idx); - container.load_focused_window(); + container.load_focused_window_ignore_borders(); self.update_focused_workspace(self.mouse_follows_focus, true) } @@ -2563,7 +2563,7 @@ impl WindowManager { container.windows_mut().swap(current_idx, next_idx); container.focus_window(next_idx); - container.load_focused_window(); + container.load_focused_window_ignore_borders(); self.update_focused_workspace(self.mouse_follows_focus, true) } diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index 6ffd1508..7b7ab78c 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -1160,7 +1160,7 @@ impl WindowsApi { pub fn create_border_window( name: PCWSTR, instance: isize, - border: *const Border, + border: *mut Border, ) -> Result { unsafe { CreateWindowExW( diff --git a/komorebi/src/windows_callbacks.rs b/komorebi/src/windows_callbacks.rs index a5786cc8..a608a8fa 100644 --- a/komorebi/src/windows_callbacks.rs +++ b/komorebi/src/windows_callbacks.rs @@ -105,12 +105,16 @@ pub extern "system" fn win_event_hook( WinEvent::ObjectLocationChange | WinEvent::ObjectDestroy ) && !has_filtered_style(hwnd) { - let border_window = border_manager::window_border(hwnd.0 as isize); + let border_info = border_manager::window_border(hwnd.0 as isize); - if let Some(border) = border_window { + if let Some(border_info) = border_info { unsafe { - let _ = - SendNotifyMessageW(border.hwnd(), event, WPARAM(0), LPARAM(hwnd.0 as isize)); + let _ = SendNotifyMessageW( + border_info.hwnd(), + event, + WPARAM(0), + LPARAM(hwnd.0 as isize), + ); } } } diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index db2c9f20..68e7cca5 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -13,6 +13,7 @@ use getset::Setters; use serde::Deserialize; use serde::Serialize; +use crate::border_manager; use crate::core::Axis; use crate::core::CustomLayout; use crate::core::CycleDirection; @@ -846,6 +847,8 @@ impl Workspace { } pub fn remove_window(&mut self, hwnd: isize) -> Result<()> { + border_manager::delete_border(hwnd); + if self.floating_windows().iter().any(|w| w.hwnd == hwnd) { self.floating_windows_mut().retain(|w| w.hwnd != hwnd); return Ok(());