diff --git a/komorebi/src/border_manager/border.rs b/komorebi/src/border_manager/border.rs index 305c188a..a367ee9f 100644 --- a/komorebi/src/border_manager/border.rs +++ b/komorebi/src/border_manager/border.rs @@ -5,16 +5,18 @@ 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::border_manager::Z_ORDER; use crate::core::BorderStyle; use crate::core::Rect; use crate::windows_api; use crate::WindowsApi; use crate::WINDOWS_11; +use color_eyre::eyre::anyhow; +use std::collections::HashMap; use std::ops::Deref; use std::sync::atomic::Ordering; use std::sync::mpsc; use std::sync::LazyLock; +use std::sync::OnceLock; use windows::Foundation::Numerics::Matrix3x2; use windows::Win32::Foundation::BOOL; use windows::Win32::Foundation::FALSE; @@ -30,6 +32,8 @@ use windows::Win32::Graphics::Direct2D::Common::D2D_RECT_F; use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_U; use windows::Win32::Graphics::Direct2D::D2D1CreateFactory; use windows::Win32::Graphics::Direct2D::ID2D1Factory; +use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget; +use windows::Win32::Graphics::Direct2D::ID2D1SolidColorBrush; use windows::Win32::Graphics::Direct2D::D2D1_ANTIALIAS_MODE_PER_PRIMITIVE; use windows::Win32::Graphics::Direct2D::D2D1_BRUSH_PROPERTIES; use windows::Win32::Graphics::Direct2D::D2D1_FACTORY_TYPE_MULTI_THREADED; @@ -43,22 +47,26 @@ use windows::Win32::Graphics::Dwm::DWM_BB_BLURREGION; use windows::Win32::Graphics::Dwm::DWM_BB_ENABLE; use windows::Win32::Graphics::Dwm::DWM_BLURBEHIND; use windows::Win32::Graphics::Dxgi::Common::DXGI_FORMAT_UNKNOWN; -use windows::Win32::Graphics::Gdi::BeginPaint; use windows::Win32::Graphics::Gdi::CreateRectRgn; -use windows::Win32::Graphics::Gdi::EndPaint; use windows::Win32::Graphics::Gdi::InvalidateRect; -use windows::Win32::Graphics::Gdi::PAINTSTRUCT; +use windows::Win32::Graphics::Gdi::ValidateRect; use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW; use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW; use windows::Win32::UI::WindowsAndMessaging::GetMessageW; use windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics; +use windows::Win32::UI::WindowsAndMessaging::GetWindowLongPtrW; use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage; +use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW; use windows::Win32::UI::WindowsAndMessaging::TranslateMessage; +use windows::Win32::UI::WindowsAndMessaging::CREATESTRUCTW; +use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_DESTROY; +use windows::Win32::UI::WindowsAndMessaging::EVENT_OBJECT_LOCATIONCHANGE; +use windows::Win32::UI::WindowsAndMessaging::GWLP_USERDATA; use windows::Win32::UI::WindowsAndMessaging::MSG; use windows::Win32::UI::WindowsAndMessaging::SM_CXVIRTUALSCREEN; +use windows::Win32::UI::WindowsAndMessaging::WM_CREATE; use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY; use windows::Win32::UI::WindowsAndMessaging::WM_PAINT; -use windows::Win32::UI::WindowsAndMessaging::WM_SIZE; use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW; use windows_core::PCWSTR; @@ -89,14 +97,36 @@ pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL { true.into() } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Border { pub hwnd: isize, + pub render_target: OnceLock, + pub tracking_hwnd: isize, + pub window_rect: Rect, + pub window_kind: WindowKind, + pub style: BorderStyle, + pub width: i32, + pub offset: i32, + pub brush_properties: D2D1_BRUSH_PROPERTIES, + pub rounded_rect: D2D1_ROUNDED_RECT, + pub brushes: HashMap, } impl From for Border { fn from(value: isize) -> Self { - Self { hwnd: value } + Self { + hwnd: value, + render_target: OnceLock::new(), + tracking_hwnd: 0, + window_rect: Rect::default(), + window_kind: WindowKind::Unfocused, + style: STYLE.load(), + width: BORDER_WIDTH.load(Ordering::Relaxed), + offset: BORDER_OFFSET.load(Ordering::Relaxed), + brush_properties: D2D1_BRUSH_PROPERTIES::default(), + rounded_rect: D2D1_ROUNDED_RECT::default(), + brushes: HashMap::new(), + } } } @@ -105,7 +135,7 @@ impl Border { HWND(windows_api::as_ptr!(self.hwnd)) } - pub fn create(id: &str) -> color_eyre::Result { + pub fn create(id: &str, tracking_hwnd: isize) -> color_eyre::Result { let name: Vec = format!("komoborder-{id}\0").encode_utf16().collect(); let class_name = PCWSTR(name.as_ptr()); @@ -121,12 +151,30 @@ impl Border { let _ = WindowsApi::register_class_w(&window_class); - let (hwnd_sender, hwnd_receiver) = mpsc::channel(); + let (border_sender, border_receiver) = mpsc::channel(); let instance = h_module.0 as isize; std::thread::spawn(move || -> color_eyre::Result<()> { - let hwnd = WindowsApi::create_border_window(PCWSTR(name.as_ptr()), instance)?; - hwnd_sender.send(hwnd)?; + let mut border = Self { + hwnd: 0, + render_target: OnceLock::new(), + tracking_hwnd, + window_rect: WindowsApi::window_rect(tracking_hwnd).unwrap_or_default(), + window_kind: WindowKind::Unfocused, + style: STYLE.load(), + width: BORDER_WIDTH.load(Ordering::Relaxed), + offset: BORDER_OFFSET.load(Ordering::Relaxed), + brush_properties: Default::default(), + rounded_rect: Default::default(), + brushes: HashMap::new(), + }; + + let border_pointer = std::ptr::addr_of!(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 mut msg: MSG = MSG::default(); @@ -145,8 +193,8 @@ impl Border { Ok(()) }); - let hwnd = hwnd_receiver.recv()?; - let border = Self { hwnd }; + let border_ref = border_receiver.recv()?; + let border = unsafe { &mut *(border_ref as *mut Border) }; // 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 @@ -167,7 +215,7 @@ impl Border { } let hwnd_render_target_properties = D2D1_HWND_RENDER_TARGET_PROPERTIES { - hwnd: HWND(windows_api::as_ptr!(hwnd)), + hwnd: HWND(windows_api::as_ptr!(border.hwnd)), pixelSize: Default::default(), presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY, }; @@ -188,10 +236,47 @@ impl Border { .CreateHwndRenderTarget(&render_target_properties, &hwnd_render_target_properties) } { Ok(render_target) => unsafe { + border.brush_properties = *BRUSH_PROPERTIES.deref(); + for window_kind in [ + WindowKind::Single, + WindowKind::Stack, + WindowKind::Monocle, + WindowKind::Unfocused, + WindowKind::Floating, + ] { + let color = window_kind_colour(window_kind); + let color = D2D1_COLOR_F { + r: ((color & 0xFF) as f32) / 255.0, + g: (((color >> 8) & 0xFF) as f32) / 255.0, + b: (((color >> 16) & 0xFF) as f32) / 255.0, + a: 1.0, + }; + + if let Ok(brush) = + render_target.CreateSolidColorBrush(&color, Some(&border.brush_properties)) + { + border.brushes.insert(window_kind, brush); + } + } + render_target.SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + + if border.render_target.set(render_target.clone()).is_err() { + return Err(anyhow!("could not store border render target")); + } + + border.rounded_rect = { + let radius = 8.0 + border.width as f32 / 2.0; + D2D1_ROUNDED_RECT { + rect: Default::default(), + radiusX: radius, + radiusY: radius, + } + }; + let mut render_targets = RENDER_TARGETS.lock(); - render_targets.insert(hwnd, render_target); - Ok(border) + render_targets.insert(border.hwnd, render_target); + Ok(border.clone()) }, Err(error) => Err(error.into()), } @@ -203,27 +288,17 @@ impl Border { WindowsApi::close_window(self.hwnd) } - pub fn update(&self, rect: &Rect, should_invalidate: bool) -> color_eyre::Result<()> { - // Make adjustments to the border + pub fn set_position(&self, rect: &Rect, reference_hwnd: isize) -> color_eyre::Result<()> { let mut rect = *rect; - rect.add_margin(BORDER_WIDTH.load(Ordering::Relaxed)); - rect.add_padding(-BORDER_OFFSET.load(Ordering::Relaxed)); + rect.add_margin(self.width); + rect.add_padding(-self.offset); - // Update the position of the border if required - // This effectively handles WM_MOVE - // Also if I remove this no borders render at all lol - if !WindowsApi::window_rect(self.hwnd)?.eq(&rect) { - WindowsApi::set_border_pos(self.hwnd, &rect, Z_ORDER.load().into())?; - } - - // Invalidate the rect to trigger the callback to update colours etc. - if should_invalidate { - self.invalidate(); - } + WindowsApi::set_border_pos(self.hwnd, &rect, reference_hwnd)?; Ok(()) } + // this triggers WM_PAINT in the callback below pub fn invalidate(&self) { let _ = unsafe { InvalidateRect(self.hwnd(), None, false) }; } @@ -236,50 +311,73 @@ impl Border { ) -> LRESULT { unsafe { match message { - WM_SIZE | WM_PAINT => { - if let Ok(rect) = WindowsApi::window_rect(window.0 as isize) { - let render_targets = RENDER_TARGETS.lock(); - if let Some(render_target) = render_targets.get(&(window.0 as isize)) { - let pixel_size = D2D_SIZE_U { - width: rect.right as u32, - height: rect.bottom as u32, - }; + WM_CREATE => { + let mut border_pointer: *mut Border = + GetWindowLongPtrW(window, GWLP_USERDATA) as _; - let border_width = BORDER_WIDTH.load(Ordering::SeqCst); - let border_offset = BORDER_OFFSET.load(Ordering::SeqCst); + if border_pointer.is_null() { + let create_struct: *mut CREATESTRUCTW = lparam.0 as *mut _; + border_pointer = (*create_struct).lpCreateParams as *mut _; + SetWindowLongPtrW(window, GWLP_USERDATA, border_pointer as _); + } - let rect = D2D_RECT_F { + LRESULT(0) + } + EVENT_OBJECT_DESTROY => { + let border_pointer: *mut Border = GetWindowLongPtrW(window, GWLP_USERDATA) as _; + + if border_pointer.is_null() { + return LRESULT(0); + } + + // we don't actually want to destroy the window here, just hide it for quicker + // visual feedback to the user; the actual destruction will be handled by the + // core border manager loop + WindowsApi::hide_window(window.0 as isize); + LRESULT(0) + } + EVENT_OBJECT_LOCATIONCHANGE => { + let border_pointer: *mut Border = GetWindowLongPtrW(window, GWLP_USERDATA) as _; + + if border_pointer.is_null() { + return LRESULT(0); + } + + let reference_hwnd = lparam.0; + + let old_rect = (*border_pointer).window_rect; + let rect = WindowsApi::window_rect(reference_hwnd).unwrap_or_default(); + + (*border_pointer).window_rect = rect; + + if let Err(error) = (*border_pointer).set_position(&rect, reference_hwnd) { + tracing::error!("failed to update border position {error}"); + } + + if !rect.is_same_size_as(&old_rect) { + if let Some(render_target) = (*border_pointer).render_target.get() { + let border_width = (*border_pointer).width; + let border_offset = (*border_pointer).offset; + + (*border_pointer).rounded_rect.rect = D2D_RECT_F { left: (border_width / 2 - border_offset) as f32, top: (border_width / 2 - border_offset) as f32, right: (rect.right - border_width / 2 + border_offset) as f32, bottom: (rect.bottom - border_width / 2 + border_offset) as f32, }; - let _ = render_target.Resize(&pixel_size); + let _ = render_target.Resize(&D2D_SIZE_U { + width: rect.right as u32, + height: rect.bottom as u32, + }); - // Get window kind and color - let window_kind = FOCUS_STATE - .lock() - .get(&(window.0 as isize)) - .copied() - .unwrap_or(WindowKind::Unfocused); - - let color = window_kind_colour(window_kind); - let color = D2D1_COLOR_F { - r: ((color & 0xFF) as f32) / 255.0, - g: (((color >> 8) & 0xFF) as f32) / 255.0, - b: (((color >> 16) & 0xFF) as f32) / 255.0, - a: 1.0, - }; - - if let Ok(brush) = render_target - .CreateSolidColorBrush(&color, Some(BRUSH_PROPERTIES.deref())) - { + let window_kind = (*border_pointer).window_kind; + if let Some(brush) = (*border_pointer).brushes.get(&window_kind) { render_target.BeginDraw(); render_target.Clear(None); // Calculate border radius based on style - let style = match STYLE.load() { + let style = match (*border_pointer).style { BorderStyle::System => { if *WINDOWS_11 { BorderStyle::Rounded @@ -293,31 +391,17 @@ impl Border { match style { BorderStyle::Rounded => { - let radius = 8.0 + border_width as f32 / 2.0; - let rounded_rect = D2D1_ROUNDED_RECT { - rect, - radiusX: radius, - radiusY: radius, - }; - render_target.DrawRoundedRectangle( - &rounded_rect, - &brush, + &(*border_pointer).rounded_rect, + brush, border_width as f32, None, ); } BorderStyle::Square => { - let rect = D2D_RECT_F { - left: rect.left, - top: rect.top, - right: rect.right, - bottom: rect.bottom, - }; - render_target.DrawRectangle( - &rect, - &brush, + &(*border_pointer).rounded_rect.rect, + brush, border_width as f32, None, ); @@ -326,17 +410,97 @@ impl Border { } let _ = render_target.EndDraw(None, None); - - // If we don't do this we'll get spammed with WM_PAINT according to Raymond Chen - // https://stackoverflow.com/questions/41783234/why-does-my-call-to-d2d1rendertargetdrawtext-result-in-a-wm-paint-being-se#comment70756781_41783234 - let _ = BeginPaint(window, &mut PAINTSTRUCT::default()); - let _ = EndPaint(window, &PAINTSTRUCT::default()); } } } + + LRESULT(0) + } + WM_PAINT => { + if let Ok(rect) = WindowsApi::window_rect(window.0 as isize) { + let border_pointer: *mut Border = + GetWindowLongPtrW(window, GWLP_USERDATA) as _; + + if border_pointer.is_null() { + return LRESULT(0); + } + + if let Some(render_target) = (*border_pointer).render_target.get() { + (*border_pointer).width = BORDER_WIDTH.load(Ordering::Relaxed); + (*border_pointer).offset = BORDER_OFFSET.load(Ordering::Relaxed); + + let border_width = (*border_pointer).width; + let border_offset = (*border_pointer).offset; + + (*border_pointer).rounded_rect.rect = D2D_RECT_F { + left: (border_width / 2 - border_offset) as f32, + top: (border_width / 2 - border_offset) as f32, + right: (rect.right - border_width / 2 + border_offset) as f32, + bottom: (rect.bottom - border_width / 2 + border_offset) as f32, + }; + + let _ = render_target.Resize(&D2D_SIZE_U { + width: rect.right as u32, + height: rect.bottom as u32, + }); + + // 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(); + render_target.Clear(None); + + (*border_pointer).style = STYLE.load(); + + // Calculate border radius based on style + let style = match (*border_pointer).style { + BorderStyle::System => { + if *WINDOWS_11 { + BorderStyle::Rounded + } else { + BorderStyle::Square + } + } + BorderStyle::Rounded => BorderStyle::Rounded, + BorderStyle::Square => BorderStyle::Square, + }; + + match style { + BorderStyle::Rounded => { + render_target.DrawRoundedRectangle( + &(*border_pointer).rounded_rect, + brush, + border_width as f32, + None, + ); + } + BorderStyle::Square => { + render_target.DrawRectangle( + &(*border_pointer).rounded_rect.rect, + brush, + border_width as f32, + None, + ); + } + _ => {} + } + + let _ = render_target.EndDraw(None, None); + } + } + } + let _ = ValidateRect(window, None); LRESULT(0) } WM_DESTROY => { + SetWindowLongPtrW(window, GWLP_USERDATA, 0); PostQuitMessage(0); LRESULT(0) } diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs index 2af2dd9a..6d2f087b 100644 --- a/komorebi/src/border_manager/mod.rs +++ b/komorebi/src/border_manager/mod.rs @@ -11,7 +11,7 @@ use crate::Rgb; use crate::WindowManager; use crate::WindowsApi; use border::border_hwnds; -use border::Border; +pub use border::Border; use crossbeam_channel::Receiver; use crossbeam_channel::Sender; use crossbeam_utils::atomic::AtomicCell; @@ -30,18 +30,13 @@ use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::OnceLock; use windows::Win32::Graphics::Direct2D::ID2D1HwndRenderTarget; -use windows::Win32::System::Threading::GetCurrentThread; -use windows::Win32::System::Threading::SetThreadPriority; -use windows::Win32::System::Threading::THREAD_PRIORITY_TIME_CRITICAL; 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::Komorebi); @@ -59,6 +54,7 @@ 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()); @@ -80,6 +76,10 @@ fn event_rx() -> Receiver { channel().1.clone() } +pub fn window_border(hwnd: isize) -> Option { + WINDOWS_BORDERS.lock().get(&hwnd).cloned() +} + pub fn send_notification(hwnd: Option) { if event_tx().try_send(Notification(hwnd)).is_err() { tracing::warn!("channel is full; dropping notification") @@ -122,31 +122,22 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> { 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), - WindowKind::Floating => FLOATING.load(Ordering::SeqCst), + WindowKind::Unfocused => UNFOCUSED.load(Ordering::Relaxed), + WindowKind::Single => FOCUSED.load(Ordering::Relaxed), + WindowKind::Stack => STACK.load(Ordering::Relaxed), + WindowKind::Monocle => MONOCLE.load(Ordering::Relaxed), + WindowKind::Floating => FLOATING.load(Ordering::Relaxed), } } pub fn listen_for_notifications(wm: Arc>) { - std::thread::spawn(move || { - unsafe { - if let Err(error) = SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) - { - tracing::error!("{error}"); + std::thread::spawn(move || loop { + match handle_notifications(wm.clone()) { + Ok(()) => { + tracing::warn!("restarting finished thread"); } - } - - loop { - match handle_notifications(wm.clone()) { - Ok(()) => { - tracing::warn!("restarting finished thread"); - } - Err(error) => { - tracing::warn!("restarting failed thread: {}", error); - } + Err(error) => { + tracing::warn!("restarting failed thread: {}", error); } } }); @@ -155,7 +146,6 @@ 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(None))?; @@ -172,7 +162,6 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result let focused_workspace_idx = state.monitors.elements()[focused_monitor_idx].focused_workspace_idx(); let monitors = state.monitors.clone(); - let weak_pending_move_op = Arc::downgrade(&state.pending_move_op); let pending_move_op = *state.pending_move_op; let floating_window_hwnds = state.monitors.elements()[focused_monitor_idx].workspaces() [focused_workspace_idx] @@ -271,11 +260,10 @@ 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(); // 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 @@ -287,6 +275,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result } borders.clear(); + windows_borders.clear(); previous_is_paused = is_paused; continue 'receiver; @@ -316,10 +305,15 @@ 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()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { - if let Ok(border) = Border::create(monocle.id()) { + if let Ok(border) = Border::create( + monocle.id(), + monocle.focused_window().copied().unwrap_or_default().hwnd, + ) { + new_border = true; entry.insert(border) } else { continue 'monitors; @@ -328,6 +322,10 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result }; borders_monitors.insert(monocle.id().clone(), monitor_idx); + windows_borders.insert( + monocle.focused_window().cloned().unwrap_or_default().hwnd, + border.clone(), + ); { let mut focus_state = FOCUS_STATE.lock(); @@ -341,11 +339,16 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result ); } - let rect = WindowsApi::window_rect( - monocle.focused_window().copied().unwrap_or_default().hwnd, - )?; + let reference_hwnd = + monocle.focused_window().copied().unwrap_or_default().hwnd; - border.update(&rect, true)?; + let rect = WindowsApi::window_rect(reference_hwnd)?; + + if new_border { + border.set_position(&rect, reference_hwnd)?; + } + + border.invalidate(); let border_hwnd = border.hwnd; let mut to_remove = vec![]; @@ -414,65 +417,16 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result } for (idx, c) in ws.containers().iter().enumerate() { - let hwnd = c.focused_window().copied().unwrap_or_default().hwnd; - let notification_hwnd = notification.0.unwrap_or_default(); - - // Update border when moving or resizing with mouse - if pending_move_op.is_some() - && idx == ws.focused_container_idx() - && hwnd == notification_hwnd - { - 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, - )?; - - // We create a new variable to track the actual pending move op so - // that the other variable `pending_move_op` still holds the - // pending move info so that when the move ends we know on the next - // notification that the previous pending move and pending move are - // different (because a move just finished) and still handle the - // notification. If otherwise we updated the pending_move_op here - // directly then the next pending move after finish would be the - // same because we had already updated it here. - let mut sync_pending_move_op = - weak_pending_move_op.upgrade().and_then(|p| *p); - while sync_pending_move_op.is_some() { - sync_pending_move_op = - weak_pending_move_op.upgrade().and_then(|p| *p); - 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, false)?; - } - } - - Z_ORDER.store(restore_z_order); - - continue 'monitors; - } - // 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()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { - if let Ok(border) = Border::create(c.id()) { + if let Ok(border) = Border::create( + c.id(), + c.focused_window().copied().unwrap_or_default().hwnd, + ) { + new_border = true; entry.insert(border) } else { continue 'monitors; @@ -481,6 +435,10 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result }; borders_monitors.insert(c.id().clone(), monitor_idx); + windows_borders.insert( + c.focused_window().cloned().unwrap_or_default().hwnd, + border.clone(), + ); #[allow(unused_assignments)] let mut last_focus_state = None; @@ -501,66 +459,35 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result 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 reference_hwnd = + c.focused_window().copied().unwrap_or_default().hwnd; + + let rect = WindowsApi::window_rect(reference_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)?; + if new_border { + border.set_position(&rect, reference_hwnd)?; + } + + if should_invalidate { + border.invalidate(); + } } { - let restore_z_order = Z_ORDER.load(); - Z_ORDER.store(ZOrder::TopMost); - 'windows: for window in ws.floating_windows() { - let hwnd = window.hwnd; - let notification_hwnd = notification.0.unwrap_or_default(); - - if pending_move_op.is_some() && hwnd == notification_hwnd { - let mut rect = WindowsApi::window_rect(hwnd)?; - - // Check comment above for containers move - let mut sync_pending_move_op = - weak_pending_move_op.upgrade().and_then(|p| *p); - while sync_pending_move_op.is_some() { - sync_pending_move_op = - weak_pending_move_op.upgrade().and_then(|p| *p); - let border = match borders.entry(hwnd.to_string()) { - Entry::Occupied(entry) => entry.into_mut(), - Entry::Vacant(entry) => { - if let Ok(border) = - Border::create(&hwnd.to_string()) - { - entry.insert(border) - } else { - continue 'monitors; - } - } - }; - - let new_rect = WindowsApi::window_rect(hwnd)?; - - if rect != new_rect { - rect = new_rect; - border.update(&rect, true)?; - } - } - - Z_ORDER.store(restore_z_order); - - continue 'monitors; - } - + let mut new_border = false; let border = match borders.entry(window.hwnd.to_string()) { Entry::Occupied(entry) => entry.into_mut(), Entry::Vacant(entry) => { - if let Ok(border) = Border::create(&window.hwnd.to_string()) + if let Ok(border) = + Border::create(&window.hwnd.to_string(), window.hwnd) { + new_border = true; entry.insert(border) } else { continue 'monitors; @@ -569,6 +496,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result }; borders_monitors.insert(window.hwnd.to_string(), monitor_idx); + windows_borders.insert(window.hwnd, border.clone()); let mut should_destroy = false; @@ -607,10 +535,14 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result Some(last_focus_state) => last_focus_state != new_focus_state, }; - border.update(&rect, should_invalidate)?; - } + if new_border { + border.set_position(&rect, window.hwnd)?; + } - Z_ORDER.store(restore_z_order); + if should_invalidate { + border.invalidate(); + } + } } } } diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index a2803cec..d4e14966 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -306,6 +306,8 @@ pub enum BorderImplementation { ValueEnum, JsonSchema, PartialEq, + Eq, + Hash, )] pub enum WindowKind { Single, diff --git a/komorebi/src/core/rect.rs b/komorebi/src/core/rect.rs index 1378dc1d..cc7b0492 100644 --- a/komorebi/src/core/rect.rs +++ b/komorebi/src/core/rect.rs @@ -37,6 +37,12 @@ impl From for RECT { } } +impl Rect { + pub fn is_same_size_as(&self, rhs: &Self) -> bool { + self.right == rhs.right && self.bottom == rhs.bottom + } +} + impl Rect { /// decrease the size of self by the padding amount. pub fn add_padding(&mut self, padding: T) diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 13a70feb..45615413 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -670,12 +670,10 @@ impl WindowManager { self.update_focused_workspace(self.mouse_follows_focus, true)?; } SocketMessage::Retile => { - border_manager::BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst); border_manager::destroy_all_borders()?; self.retile_all(false)? } SocketMessage::RetileWithResizeDimensions => { - border_manager::BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst); border_manager::destroy_all_borders()?; self.retile_all(true)? } @@ -1518,6 +1516,16 @@ impl WindowManager { } SocketMessage::Border(enable) => { border_manager::BORDER_ENABLED.store(enable, Ordering::SeqCst); + if !enable { + match IMPLEMENTATION.load() { + BorderImplementation::Komorebi => { + border_manager::destroy_all_borders()?; + } + BorderImplementation::Windows => { + self.remove_all_accents()?; + } + } + } } SocketMessage::BorderImplementation(implementation) => { if !*WINDOWS_11 && matches!(implementation, BorderImplementation::Windows) { diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 6b8d3468..533037a7 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -11,7 +11,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; use crate::core::BorderImplementation; use crate::core::StackbarLabel; @@ -297,7 +296,7 @@ pub struct StaticConfig { #[serde(skip_serializing_if = "Option::is_none")] #[serde(alias = "active_window_border_style")] pub border_style: Option, - /// Active window border z-order (default: System) + /// DEPRECATED from v0.1.31: no longer required #[serde(skip_serializing_if = "Option::is_none")] pub border_z_order: Option, /// Active window border implementation (default: Komorebi) @@ -497,7 +496,7 @@ impl StaticConfig { } pub fn deprecated(raw: &str) { - let deprecated_options = ["invisible_borders"]; + let deprecated_options = ["invisible_borders", "border_z_order"]; let deprecated_variants = vec![ ("Hide", "window_hiding_behaviour", "Cloak"), ("Minimize", "window_hiding_behaviour", "Cloak"), @@ -600,7 +599,7 @@ impl From<&WindowManager> for StaticConfig { ), transparency_ignore_rules: None, border_style: Option::from(STYLE.load()), - border_z_order: Option::from(Z_ORDER.load()), + border_z_order: None, border_implementation: Option::from(IMPLEMENTATION.load()), default_workspace_padding: Option::from( DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst), diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 9740b560..84a8f275 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -10,7 +10,6 @@ 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::focus_manager; use crate::stackbar_manager; @@ -193,9 +192,6 @@ impl RenderDispatcher for MovementRenderDispatcher { } fn pre_render(&self) -> Result<()> { - border_manager::BORDER_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst); - border_manager::send_notification(Some(self.hwnd)); - stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst); stackbar_manager::send_notification(); @@ -224,10 +220,8 @@ impl RenderDispatcher for MovementRenderDispatcher { focus_manager::send_notification(self.hwnd) } - border_manager::BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst); stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst); - border_manager::send_notification(Some(self.hwnd)); stackbar_manager::send_notification(); transparency_manager::send_notification(); } diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index be9e8ef3..9469492e 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -106,7 +106,6 @@ use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE; use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT; use windows::Win32::UI::WindowsAndMessaging::HWND_TOP; use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA; -use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY; use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS; use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD; use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE; @@ -129,7 +128,6 @@ use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE; use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW; use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC; use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED; -use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED; use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE; use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW; use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOPMOST; @@ -154,6 +152,7 @@ macro_rules! as_ptr { }; } +use crate::border_manager::Border; pub(crate) use as_ptr; pub enum WindowsResult { @@ -453,7 +452,13 @@ impl WindowsApi { } pub fn set_border_pos(hwnd: isize, layout: &Rect, position: isize) -> Result<()> { - let flags = { SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE }; + let flags = { + SetWindowPosition::NO_SEND_CHANGING + | SetWindowPosition::NO_ACTIVATE + | SetWindowPosition::NO_REDRAW + | SetWindowPosition::SHOW_WINDOW + }; + Self::set_window_pos( HWND(as_ptr!(hwnd)), layout, @@ -1090,10 +1095,14 @@ impl WindowsApi { .process() } - pub fn create_border_window(name: PCWSTR, instance: isize) -> Result { + pub fn create_border_window( + name: PCWSTR, + instance: isize, + border: *const Border, + ) -> Result { unsafe { - let hwnd = CreateWindowExW( - WS_EX_TOOLWINDOW | WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_NOACTIVATE, + CreateWindowExW( + WS_EX_TOOLWINDOW | WS_EX_TOPMOST | WS_EX_NOACTIVATE, name, name, WS_POPUP | WS_SYSMENU, @@ -1104,12 +1113,8 @@ impl WindowsApi { None, None, HINSTANCE(as_ptr!(instance)), - None, - )?; - - SetLayeredWindowAttributes(hwnd, COLORREF(0), 0, LWA_COLORKEY)?; - - hwnd + Some(border as _), + )? } .process() } diff --git a/komorebi/src/windows_callbacks.rs b/komorebi/src/windows_callbacks.rs index c7551fa5..5c3f2a96 100644 --- a/komorebi/src/windows_callbacks.rs +++ b/komorebi/src/windows_callbacks.rs @@ -1,10 +1,6 @@ use std::collections::VecDeque; -use windows::Win32::Foundation::BOOL; -use windows::Win32::Foundation::HWND; -use windows::Win32::Foundation::LPARAM; -use windows::Win32::UI::Accessibility::HWINEVENTHOOK; - +use crate::border_manager; use crate::container::Container; use crate::window::RuleDebug; use crate::window::Window; @@ -12,6 +8,19 @@ use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::winevent::WinEvent; use crate::winevent_listener; +use windows::Win32::Foundation::BOOL; +use windows::Win32::Foundation::HWND; +use windows::Win32::Foundation::LPARAM; +use windows::Win32::Foundation::WPARAM; +use windows::Win32::UI::Accessibility::HWINEVENTHOOK; +use windows::Win32::UI::WindowsAndMessaging::GetWindowLongW; +use windows::Win32::UI::WindowsAndMessaging::SendNotifyMessageW; +use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE; +use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE; +use windows::Win32::UI::WindowsAndMessaging::OBJID_WINDOW; +use windows::Win32::UI::WindowsAndMessaging::WS_CHILD; +use windows::Win32::UI::WindowsAndMessaging::WS_EX_NOACTIVATE; +use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW; pub extern "system" fn enum_window(hwnd: HWND, lparam: LPARAM) -> BOOL { let containers = unsafe { &mut *(lparam.0 as *mut VecDeque) }; @@ -60,6 +69,15 @@ pub extern "system" fn alt_tab_windows(hwnd: HWND, lparam: LPARAM) -> BOOL { true.into() } +fn has_filtered_style(hwnd: HWND) -> bool { + let style = unsafe { GetWindowLongW(hwnd, GWL_STYLE) as u32 }; + let ex_style = unsafe { GetWindowLongW(hwnd, GWL_EXSTYLE) as u32 }; + + style & WS_CHILD.0 != 0 + || ex_style & WS_EX_TOOLWINDOW.0 != 0 + || ex_style & WS_EX_NOACTIVATE.0 != 0 +} + pub extern "system" fn win_event_hook( _h_win_event_hook: HWINEVENTHOOK, event: u32, @@ -69,8 +87,7 @@ pub extern "system" fn win_event_hook( _id_event_thread: u32, _dwms_event_time: u32, ) { - // OBJID_WINDOW - if id_object != 0 { + if id_object != OBJID_WINDOW.0 { return; } @@ -81,6 +98,23 @@ pub extern "system" fn win_event_hook( Err(_) => return, }; + // this forwards the message to the window's border when it moves or is destroyed + // see border_manager/border.rs + if matches!( + winevent, + WinEvent::ObjectLocationChange | WinEvent::ObjectDestroy + ) && !has_filtered_style(hwnd) + { + let border_window = border_manager::window_border(hwnd.0 as isize); + + if let Some(border) = border_window { + unsafe { + let _ = + SendNotifyMessageW(border.hwnd(), event, WPARAM(0), LPARAM(hwnd.0 as isize)); + } + } + } + let event_type = match WindowManagerEvent::from_win_event(winevent, window) { None => { tracing::trace!( diff --git a/komorebi/src/winevent_listener.rs b/komorebi/src/winevent_listener.rs index 2bbfc456..1187fff3 100644 --- a/komorebi/src/winevent_listener.rs +++ b/komorebi/src/winevent_listener.rs @@ -11,6 +11,8 @@ use windows::Win32::UI::WindowsAndMessaging::TranslateMessage; use windows::Win32::UI::WindowsAndMessaging::EVENT_MAX; use windows::Win32::UI::WindowsAndMessaging::EVENT_MIN; use windows::Win32::UI::WindowsAndMessaging::MSG; +use windows::Win32::UI::WindowsAndMessaging::WINEVENT_OUTOFCONTEXT; +use windows::Win32::UI::WindowsAndMessaging::WINEVENT_SKIPOWNPROCESS; use crate::window_manager_event::WindowManagerEvent; use crate::windows_callbacks; @@ -31,7 +33,7 @@ pub fn start() { Some(windows_callbacks::win_event_hook), 0, 0, - 0, + WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS, ) };