From 6a1ed3bcaa8d1f6e75535778fc5bd51d9cc3c019 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Fri, 10 May 2024 16:56:28 -0700 Subject: [PATCH] feat(borders): add border manager module This commit removes all border-related code from process_command, process_event etc. and centralizes it in the new border_manager module. Instead of trying to figure out where in process_event and process_command we should make border-related changes, a notification gets sent to a channel that border_manager listens to whenever an event or command has finished processing. The border_manager listener, upon receiving a notification, acquires a lock on the WindowManager instance and updates borders for the focused workspace on every monitor; this allows us to centralize all edge case handling within the border_manager listener's loop. Borders on workspaces that lose focus are now destroyed and recreated when those workspaces regain focus, instead of trying to share individual border instances across workspaces. A number of common edge cases that have been addressed in this commit are: * Paused window manager * Floating workspaces * Maximized windows * Fullscreen videos * Monocle containers * Ghost borders on workspace switching * Incorrect focused window border colours Global state related to borders has also been moved into the border_manager module, which also tracks the state of border objects (BORDER_STATE), their rects (RECT_STATE) and their focus kinds (FOCUS_STATE). This allows us to now track multiple borders per-container, enabling unfocused border windows for the first time. Additionally, the Z-Order for border windows is now also configurable. ActiveWindowBorderColours has been expanded to include Unfocused, but in order to not introduce a breaking configuration change for end users, all members of this struct have been made Option. --- Cargo.toml | 2 + komorebi-core/src/lib.rs | 1 + komorebi/src/border.rs | 108 --------- komorebi/src/border_manager/border.rs | 204 +++++++++++++++++ komorebi/src/border_manager/mod.rs | 302 ++++++++++++++++++++++++++ komorebi/src/colour.rs | 2 +- komorebi/src/hidden.rs | 3 +- komorebi/src/lib.rs | 21 +- komorebi/src/main.rs | 3 + komorebi/src/process_command.rs | 265 +++------------------- komorebi/src/process_event.rs | 146 ++----------- komorebi/src/stackbar.rs | 5 +- komorebi/src/static_config.rs | 109 +++++----- komorebi/src/window_manager.rs | 56 ++--- komorebi/src/windows_api.rs | 51 +---- komorebi/src/windows_callbacks.rs | 78 ------- komorebi/src/workspace.rs | 4 +- 17 files changed, 645 insertions(+), 715 deletions(-) delete mode 100644 komorebi/src/border.rs create mode 100644 komorebi/src/border_manager/border.rs create mode 100644 komorebi/src/border_manager/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 160ec357..a03dfea9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ dirs = "5" color-eyre = "0.6" serde_json = { package = "serde_json_lenient", version = "0.1" } sysinfo = "0.30" +serde = { version = "1", features = ["derive"] } +uds_windows = "1" [workspace.dependencies.windows] version = "0.54" diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 51315e3f..743d714e 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -218,6 +218,7 @@ pub enum WindowKind { Single, Stack, Monocle, + Unfocused, } #[derive( diff --git a/komorebi/src/border.rs b/komorebi/src/border.rs deleted file mode 100644 index 49c8f3da..00000000 --- a/komorebi/src/border.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::sync::atomic::Ordering; - -use color_eyre::Result; -use windows::core::PCWSTR; -use windows::Win32::Foundation::HWND; -use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW; -use windows::Win32::UI::WindowsAndMessaging::FindWindowW; -use windows::Win32::UI::WindowsAndMessaging::GetMessageW; -use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW; -use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW; -use windows::Win32::UI::WindowsAndMessaging::MSG; -use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW; - -use crate::window::Window; -use crate::windows_callbacks; -use crate::WindowsApi; -use crate::BORDER_HWND; -use crate::BORDER_OFFSET; -use crate::BORDER_RECT; -use crate::BORDER_WIDTH; -use crate::TRANSPARENCY_COLOUR; - -#[derive(Debug, Clone, Copy)] -pub struct Border { - pub(crate) hwnd: isize, -} - -impl From for Border { - fn from(hwnd: isize) -> Self { - Self { hwnd } - } -} - -impl Border { - pub const fn hwnd(self) -> HWND { - HWND(self.hwnd) - } - - pub fn create(name: &str) -> Result<()> { - let name: Vec = format!("{name}\0").encode_utf16().collect(); - let instance = WindowsApi::module_handle_w()?; - let class_name = PCWSTR(name.as_ptr()); - let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR); - let window_class = WNDCLASSW { - hInstance: instance.into(), - lpszClassName: class_name, - style: CS_HREDRAW | CS_VREDRAW, - lpfnWndProc: Some(windows_callbacks::border_window), - hbrBackground: brush, - ..Default::default() - }; - - let _atom = WindowsApi::register_class_w(&window_class)?; - - let name_cl = name.clone(); - std::thread::spawn(move || -> Result<()> { - let hwnd = WindowsApi::create_border_window(PCWSTR(name_cl.as_ptr()), instance)?; - let border = Self::from(hwnd); - - let mut message = MSG::default(); - - unsafe { - while GetMessageW(&mut message, border.hwnd(), 0, 0).into() { - DispatchMessageW(&message); - } - } - - Ok(()) - }); - - let mut hwnd = HWND(0); - while hwnd == HWND(0) { - hwnd = unsafe { FindWindowW(PCWSTR(name.as_ptr()), PCWSTR::null()) }; - } - - BORDER_HWND.store(hwnd.0, Ordering::SeqCst); - - Ok(()) - } - - pub fn hide(self) -> Result<()> { - if self.hwnd == 0 { - Ok(()) - } else { - WindowsApi::hide_border_window(self.hwnd()) - } - } - - pub fn set_position(self, window: Window, activate: bool) -> Result<()> { - if self.hwnd == 0 { - Ok(()) - } else { - if !WindowsApi::is_window(self.hwnd()) { - Self::create("komorebi-border-window")?; - } - - let mut rect = WindowsApi::window_rect(window.hwnd())?; - rect.add_padding(-BORDER_OFFSET.load(Ordering::SeqCst)); - - let border_width = BORDER_WIDTH.load(Ordering::SeqCst); - rect.add_margin(border_width); - - *BORDER_RECT.lock() = rect; - - WindowsApi::position_border_window(self.hwnd(), &rect, activate) - } - } -} diff --git a/komorebi/src/border_manager/border.rs b/komorebi/src/border_manager/border.rs new file mode 100644 index 00000000..37d12b77 --- /dev/null +++ b/komorebi/src/border_manager/border.rs @@ -0,0 +1,204 @@ +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::RECT_STATE; +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; + +use komorebi_core::ActiveWindowBorderStyle; +use komorebi_core::Rect; + +use std::sync::atomic::Ordering; +use std::sync::mpsc; +use std::time::Duration; +use windows::core::PCWSTR; +use windows::Win32::Foundation::COLORREF; +use windows::Win32::Foundation::HWND; +use windows::Win32::Foundation::LPARAM; +use windows::Win32::Foundation::LRESULT; +use windows::Win32::Foundation::WPARAM; +use windows::Win32::Graphics::Gdi::BeginPaint; +use windows::Win32::Graphics::Gdi::CreatePen; +use windows::Win32::Graphics::Gdi::EndPaint; +use windows::Win32::Graphics::Gdi::InvalidateRect; +use windows::Win32::Graphics::Gdi::Rectangle; +use windows::Win32::Graphics::Gdi::RoundRect; +use windows::Win32::Graphics::Gdi::SelectObject; +use windows::Win32::Graphics::Gdi::ValidateRect; +use windows::Win32::Graphics::Gdi::PAINTSTRUCT; +use windows::Win32::Graphics::Gdi::PS_INSIDEFRAME; +use windows::Win32::Graphics::Gdi::PS_SOLID; +use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW; +use windows::Win32::UI::WindowsAndMessaging::DispatchMessageW; +use windows::Win32::UI::WindowsAndMessaging::GetMessageW; +use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage; +use windows::Win32::UI::WindowsAndMessaging::TranslateMessage; +use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW; +use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW; +use windows::Win32::UI::WindowsAndMessaging::MSG; +use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY; +use windows::Win32::UI::WindowsAndMessaging::WM_PAINT; +use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW; + +pub struct Border { + pub hwnd: isize, +} + +impl Border { + pub const fn hwnd(&self) -> HWND { + HWND(self.hwnd) + } + + pub fn create(id: &str) -> color_eyre::Result { + let name: Vec = format!("komoborder-{id}\0").encode_utf16().collect(); + let class_name = PCWSTR(name.as_ptr()); + + let h_module = WindowsApi::module_handle_w()?; + + let window_class = WNDCLASSW { + hInstance: h_module.into(), + lpszClassName: class_name, + style: CS_HREDRAW | CS_VREDRAW, + lpfnWndProc: Some(Self::callback), + hbrBackground: WindowsApi::create_solid_brush(0), + ..Default::default() + }; + + let _ = WindowsApi::register_class_w(&window_class); + + let (hwnd_sender, hwnd_receiver) = mpsc::channel(); + + std::thread::spawn(move || -> color_eyre::Result<()> { + let hwnd = WindowsApi::create_border_window(PCWSTR(name.as_ptr()), h_module)?; + hwnd_sender.send(hwnd)?; + + let mut message = MSG::default(); + unsafe { + while GetMessageW(&mut message, HWND(hwnd), 0, 0).into() { + TranslateMessage(&message); + DispatchMessageW(&message); + std::thread::sleep(Duration::from_millis(10)); + } + } + + Ok(()) + }); + + Ok(Self { + hwnd: hwnd_receiver.recv()?, + }) + } + + pub fn destroy(&self) -> color_eyre::Result<()> { + WindowsApi::destroy_window(self.hwnd()) + } + + pub fn update(&self, rect: &Rect) -> color_eyre::Result<()> { + // Make adjustments to the border + let mut rect = *rect; + rect.add_margin(BORDER_WIDTH.load(Ordering::SeqCst)); + rect.add_padding(-BORDER_OFFSET.load(Ordering::SeqCst)); + + // Store the border rect so that it can be used by the callback + { + let mut rects = RECT_STATE.lock(); + rects.insert(self.hwnd, rect); + } + + // Update the position of the border + WindowsApi::set_border_pos(self.hwnd(), &rect, HWND((*Z_ORDER.lock()).into()))?; + + // Invalidate the rect to trigger the callback to update colours etc. + self.invalidate(); + + Ok(()) + } + + pub fn invalidate(&self) { + let _ = unsafe { InvalidateRect(self.hwnd(), None, false) }; + } + + pub extern "system" fn callback( + window: HWND, + message: u32, + wparam: WPARAM, + lparam: LPARAM, + ) -> LRESULT { + unsafe { + match message { + WM_PAINT => { + let rects = RECT_STATE.lock(); + + // With the rect that we stored in Self::update + if let Some(rect) = rects.get(&window.0).copied() { + // Grab the focus kind for this border + let focus_kind = { + FOCUS_STATE + .lock() + .get(&window.0) + .copied() + .unwrap_or(WindowKind::Unfocused) + }; + + // Set up the brush to draw the border + let mut ps = PAINTSTRUCT::default(); + let hdc = BeginPaint(window, &mut ps); + 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), + }), + ); + + let hbrush = WindowsApi::create_solid_brush(0); + + // Draw the border + SelectObject(hdc, hpen); + SelectObject(hdc, hbrush); + // TODO(raggi): this is approximately the correct curvature for + // the top left of a Windows 11 window (DWMWCP_DEFAULT), but + // often the bottom right has a different shape. Furthermore if + // the window was made with DWMWCP_ROUNDSMALL then this is the + // wrong size. In the future we should read the DWM properties + // of windows and attempt to match appropriately. + match *STYLE.lock() { + ActiveWindowBorderStyle::System => { + if *WINDOWS_11 { + RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20); + } else { + Rectangle(hdc, 0, 0, rect.right, rect.bottom); + } + } + ActiveWindowBorderStyle::Rounded => { + RoundRect(hdc, 0, 0, rect.right, rect.bottom, 20, 20); + } + ActiveWindowBorderStyle::Square => { + Rectangle(hdc, 0, 0, rect.right, rect.bottom); + } + } + EndPaint(window, &ps); + ValidateRect(window, None); + } + + LRESULT(0) + } + WM_DESTROY => { + PostQuitMessage(0); + LRESULT(0) + } + _ => DefWindowProcW(window, message, wparam, lparam), + } + } + } +} diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs new file mode 100644 index 00000000..ca508adf --- /dev/null +++ b/komorebi/src/border_manager/mod.rs @@ -0,0 +1,302 @@ +mod border; + +use crossbeam_channel::Receiver; +use crossbeam_channel::Sender; +use crossbeam_utils::atomic::AtomicConsume; +use komorebi_core::ActiveWindowBorderStyle; +use lazy_static::lazy_static; +use parking_lot::Mutex; +use schemars::JsonSchema; +use serde::Deserialize; +use serde::Serialize; +use std::collections::HashMap; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::AtomicI32; +use std::sync::atomic::AtomicU32; +use std::sync::Arc; +use std::sync::OnceLock; +use windows::Win32::Foundation::HWND; + +use crate::Colour; +use crate::Rect; +use crate::Rgb; +use crate::WindowManager; +use crate::WindowsApi; +use border::Border; +use komorebi_core::WindowKind; + +pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8); +pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1); + +pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(true); + +lazy_static! { + pub static ref Z_ORDER: Arc> = Arc::new(Mutex::new(ZOrder::Bottom)); + pub static ref STYLE: Arc> = + Arc::new(Mutex::new(ActiveWindowBorderStyle::System)); + pub static ref FOCUSED: AtomicU32 = + AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(66, 165, 245)))); + pub static ref UNFOCUSED: AtomicU32 = + AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(128, 128, 128)))); + pub static ref MONOCLE: AtomicU32 = + AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(255, 51, 153)))); + pub static ref STACK: AtomicU32 = AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(0, 165, 66)))); +} + +lazy_static! { + static ref BORDERS_MONITORS: Mutex> = Mutex::new(HashMap::new()); + static ref BORDER_STATE: Mutex> = Mutex::new(HashMap::new()); + static ref RECT_STATE: Mutex> = Mutex::new(HashMap::new()); + static ref FOCUS_STATE: Mutex> = Mutex::new(HashMap::new()); +} + +pub struct Notification; + +static CHANNEL: OnceLock<(Sender, Receiver)> = OnceLock::new(); + +pub fn channel() -> &'static (Sender, Receiver) { + CHANNEL.get_or_init(crossbeam_channel::unbounded) +} + +pub fn event_tx() -> Sender { + channel().0.clone() +} + +pub fn event_rx() -> Receiver { + channel().1.clone() +} + +pub fn destroy_all_borders() -> color_eyre::Result<()> { + let mut borders = BORDER_STATE.lock(); + for (_, border) in borders.iter() { + border.destroy()?; + } + + borders.clear(); + RECT_STATE.lock().clear(); + BORDERS_MONITORS.lock().clear(); + FOCUS_STATE.lock().clear(); + + Ok(()) +} + +pub fn listen_for_notifications(wm: Arc>) { + tracing::info!("listening"); + let receiver = event_rx(); + + std::thread::spawn(move || -> color_eyre::Result<()> { + 'receiver: for _ in receiver { + let mut borders = BORDER_STATE.lock(); + let mut borders_monitors = BORDERS_MONITORS.lock(); + + // Check the wm state every time we receive a notification + let state = wm.lock(); + + if !BORDER_ENABLED.load_consume() || state.is_paused { + if !borders.is_empty() { + for (_, border) in borders.iter() { + border.destroy()?; + } + + borders.clear(); + } + + continue 'receiver; + } + + let focused_monitor_idx = state.focused_monitor_idx(); + + for (monitor_idx, m) in state.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 'receiver; + } + + // Handle the monocle container separately + if let Some(monocle) = ws.monocle_container() { + 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); + } + + let border = borders.entry(monocle.id().clone()).or_insert_with(|| { + Border::create(monocle.id()).expect("border creation failed") + }); + + borders_monitors.insert(monocle.id().clone(), monitor_idx); + + { + let mut focus_state = FOCUS_STATE.lock(); + focus_state.insert(border.hwnd, WindowKind::Monocle); + } + + let rect = WindowsApi::window_rect( + monocle + .focused_window() + .expect("monocle container has no focused window") + .hwnd(), + )?; + + border.update(&rect)?; + continue 'receiver; + } + + 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 'receiver; + } + + // 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 { + if !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 state.pending_move_op.is_some() && idx == ws.focused_container_idx() { + let restore_z_order = *Z_ORDER.lock(); + *Z_ORDER.lock() = ZOrder::TopMost; + + let mut rect = WindowsApi::window_rect( + c.focused_window() + .expect("container has no focused window") + .hwnd(), + )?; + + while WindowsApi::lbutton_is_pressed() { + let border = borders.entry(c.id().clone()).or_insert_with(|| { + Border::create(c.id()).expect("border creation failed") + }); + + let new_rect = WindowsApi::window_rect( + c.focused_window() + .expect("container has no focused window") + .hwnd(), + )?; + + if rect != new_rect { + rect = new_rect; + border.update(&rect)?; + } + } + + *Z_ORDER.lock() = restore_z_order; + + continue 'receiver; + } + + // Get the border entry for this container from the map or create one + let border = borders.entry(c.id().clone()).or_insert_with(|| { + Border::create(c.id()).expect("border creation failed") + }); + + borders_monitors.insert(c.id().clone(), monitor_idx); + + // Update the focused state for all containers on this workspace + { + let mut focus_state = FOCUS_STATE.lock(); + focus_state.insert( + border.hwnd, + if idx != ws.focused_container_idx() + || monitor_idx != focused_monitor_idx + { + WindowKind::Unfocused + } else { + if c.windows().len() > 1 { + WindowKind::Stack + } else { + WindowKind::Single + } + }, + ); + } + + let rect = WindowsApi::window_rect( + c.focused_window() + .expect("container has no focused window") + .hwnd(), + )?; + + border.update(&rect)?; + } + } + } + } + + Ok(()) + }); +} + +#[derive(Debug, Copy, Clone, Serialize, Deserialize, JsonSchema)] +pub enum ZOrder { + Top, + NoTopMost, + Bottom, + TopMost, +} + +impl Into for ZOrder { + fn into(self) -> isize { + match self { + ZOrder::Top => 0, + ZOrder::NoTopMost => -2, + ZOrder::Bottom => 1, + ZOrder::TopMost => -1, + } + } +} diff --git a/komorebi/src/colour.rs b/komorebi/src/colour.rs index 092b4309..7428b004 100644 --- a/komorebi/src/colour.rs +++ b/komorebi/src/colour.rs @@ -65,7 +65,7 @@ pub struct Rgb { } impl Rgb { - pub fn new(r: u32, g: u32, b: u32) -> Self { + pub const fn new(r: u32, g: u32, b: u32) -> Self { Self { r, g, b } } } diff --git a/komorebi/src/hidden.rs b/komorebi/src/hidden.rs index 2e35c3b1..a3799111 100644 --- a/komorebi/src/hidden.rs +++ b/komorebi/src/hidden.rs @@ -14,7 +14,6 @@ use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW; use crate::windows_callbacks; use crate::WindowsApi; use crate::HIDDEN_HWND; -use crate::TRANSPARENCY_COLOUR; #[derive(Debug, Clone, Copy)] pub struct Hidden { @@ -36,7 +35,7 @@ impl Hidden { let name: Vec = format!("{name}\0").encode_utf16().collect(); let instance = WindowsApi::module_handle_w()?; let class_name = PCWSTR(name.as_ptr()); - let brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR); + let brush = WindowsApi::create_solid_brush(0); let window_class = WNDCLASSW { hInstance: instance.into(), lpszClassName: class_name, diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index ea36ca94..3877bf7c 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -1,4 +1,4 @@ -pub mod border; +pub mod border_manager; pub mod com; #[macro_use] pub mod ring; @@ -53,7 +53,6 @@ use color_eyre::Result; use komorebi_core::config_generation::IdWithIdentifier; use komorebi_core::config_generation::MatchingRule; use komorebi_core::config_generation::MatchingStrategy; -use komorebi_core::ActiveWindowBorderStyle; use komorebi_core::ApplicationIdentifier; use komorebi_core::HidingBehaviour; use komorebi_core::Rect; @@ -197,13 +196,6 @@ lazy_static! { ) }; - static ref ACTIVE_WINDOW_BORDER_STYLE: Arc> = - Arc::new(Mutex::new(ActiveWindowBorderStyle::System)); - - static ref BORDER_RECT: Arc> = - Arc::new(Mutex::new(Rect::default())); - - // Use app-specific titlebar removal options where possible // eg. Windows Terminal, IntelliJ IDEA, Firefox static ref NO_TITLEBAR: Arc>> = Arc::new(Mutex::new(vec![])); @@ -220,18 +212,7 @@ pub static DEFAULT_CONTAINER_PADDING: AtomicI32 = AtomicI32::new(10); pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false); pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false); pub static SESSION_ID: AtomicU32 = AtomicU32::new(0); -pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(false); -pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0); -pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false); -pub static BORDER_COLOUR_SINGLE: AtomicU32 = AtomicU32::new(0); -pub static BORDER_COLOUR_STACK: AtomicU32 = AtomicU32::new(0); -pub static BORDER_COLOUR_MONOCLE: AtomicU32 = AtomicU32::new(0); -pub static BORDER_COLOUR_CURRENT: AtomicU32 = AtomicU32::new(0); -pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8); -pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1); -// 0 0 0 aka pure black, I doubt anyone will want this as a border colour -pub const TRANSPARENCY_COLOUR: u32 = 0; pub static REMOVE_TITLEBARS: AtomicBool = AtomicBool::new(false); pub static HIDDEN_HWND: AtomicIsize = AtomicIsize::new(0); diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index ca39ea9b..c7734b33 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -23,6 +23,7 @@ use tracing_appender::non_blocking::WorkerGuard; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::EnvFilter; +use komorebi::border_manager; use komorebi::hidden::Hidden; use komorebi::load_configuration; use komorebi::process_command::listen_for_commands; @@ -253,6 +254,8 @@ fn main() -> Result<()> { listen_for_movements(wm.clone()); } + border_manager::listen_for_notifications(wm.clone()); + let (ctrlc_sender, ctrlc_receiver) = crossbeam_channel::bounded(1); ctrlc::set_handler(move || { ctrlc_sender diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 9ec5fdf2..57d54600 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -13,7 +13,6 @@ use std::sync::Arc; use std::time::Duration; use color_eyre::eyre::anyhow; -use color_eyre::eyre::bail; use color_eyre::Result; use miow::pipe::connect; use net2::TcpStreamExt; @@ -39,7 +38,8 @@ use komorebi_core::StateQuery; use komorebi_core::WindowContainerBehaviour; use komorebi_core::WindowKind; -use crate::border::Border; +use crate::border_manager; +use crate::border_manager::STYLE; use crate::colour::Rgb; use crate::current_virtual_desktop; use crate::notify_subscribers; @@ -52,16 +52,6 @@ use crate::windows_api::WindowsApi; use crate::GlobalState; use crate::Notification; use crate::NotificationEvent; -use crate::ACTIVE_WINDOW_BORDER_STYLE; -use crate::BORDER_COLOUR_CURRENT; -use crate::BORDER_COLOUR_MONOCLE; -use crate::BORDER_COLOUR_SINGLE; -use crate::BORDER_COLOUR_STACK; -use crate::BORDER_ENABLED; -use crate::BORDER_HIDDEN; -use crate::BORDER_HWND; -use crate::BORDER_OFFSET; -use crate::BORDER_WIDTH; use crate::CUSTOM_FFM; use crate::DATA_DIR; use crate::DISPLAY_INDEX_PREFERENCES; @@ -175,21 +165,6 @@ impl WindowManager { } } - match message { - SocketMessage::CycleFocusMonitor(_) - | SocketMessage::CycleFocusWorkspace(_) - | SocketMessage::FocusMonitorNumber(_) - | SocketMessage::FocusMonitorWorkspaceNumber(_, _) - | SocketMessage::FocusWorkspaceNumber(_) => { - if self.focused_workspace()?.visible_windows().is_empty() { - let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - border.hide()?; - BORDER_HIDDEN.store(true, Ordering::SeqCst); - } - } - _ => {} - }; - match message { SocketMessage::CycleFocusWorkspace(_) | SocketMessage::FocusWorkspaceNumber(_) => { if let Some(monitor) = self.focused_monitor_mut() { @@ -517,7 +492,10 @@ impl WindowManager { self.focus_monitor(monitor_idx)?; self.update_focused_workspace(self.mouse_follows_focus, true)?; } - SocketMessage::Retile => self.retile_all(false)?, + SocketMessage::Retile => { + border_manager::destroy_all_borders()?; + self.retile_all(false)? + } SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?, SocketMessage::ChangeLayout(layout) => self.change_workspace_layout_default(layout)?, SocketMessage::CycleLayout(direction) => self.cycle_layout(direction)?, @@ -640,10 +618,6 @@ impl WindowManager { ); self.focus_workspace(workspace_idx)?; - - if BORDER_ENABLED.load(Ordering::SeqCst) { - self.show_border()?; - }; } SocketMessage::FocusLastWorkspace => { // This is to ensure that even on an empty workspace on a secondary monitor, the @@ -667,10 +641,6 @@ impl WindowManager { self.focused_monitor_mut() .ok_or_else(|| anyhow!("there is no monitor"))? .set_last_focused_workspace(Option::from(idx)); - - if BORDER_ENABLED.load(Ordering::SeqCst) { - self.show_border()?; - }; } SocketMessage::FocusWorkspaceNumber(workspace_idx) => { // This is to ensure that even on an empty workspace on a secondary monitor, the @@ -681,10 +651,6 @@ impl WindowManager { } self.focus_workspace(workspace_idx)?; - - if BORDER_ENABLED.load(Ordering::SeqCst) { - self.show_border()?; - }; } SocketMessage::FocusWorkspaceNumbers(workspace_idx) => { // This is to ensure that even on an empty workspace on a secondary monitor, the @@ -704,10 +670,6 @@ impl WindowManager { } self.focus_workspace(workspace_idx)?; - - if BORDER_ENABLED.load(Ordering::SeqCst) { - self.show_border()?; - }; } SocketMessage::FocusMonitorWorkspaceNumber(monitor_idx, workspace_idx) => { self.focus_monitor(monitor_idx)?; @@ -720,10 +682,6 @@ impl WindowManager { self.focus_monitor(monitor_idx)?; self.focus_workspace(workspace_idx)?; } - - if BORDER_ENABLED.load(Ordering::SeqCst) { - self.show_border()?; - }; } SocketMessage::Stop => { tracing::info!( @@ -1238,47 +1196,31 @@ impl WindowManager { self.unmanaged_window_operation_behaviour = behaviour; } SocketMessage::ActiveWindowBorder(enable) => { - if enable { - if BORDER_HWND.load(Ordering::SeqCst) == 0 { - Border::create("komorebi-border-window")?; - } - - BORDER_ENABLED.store(true, Ordering::SeqCst); - self.show_border()?; - } else { - BORDER_ENABLED.store(false, Ordering::SeqCst); - self.hide_border()?; - } + border_manager::BORDER_ENABLED.store(enable, Ordering::SeqCst); } - SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => { - match kind { - WindowKind::Single => { - BORDER_COLOUR_SINGLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); - BORDER_COLOUR_CURRENT.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); - } - WindowKind::Stack => { - BORDER_COLOUR_STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); - } - WindowKind::Monocle => { - BORDER_COLOUR_MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); - } + SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => match kind { + WindowKind::Single => { + border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); } - - WindowsApi::invalidate_border_rect()?; - } + WindowKind::Stack => { + border_manager::STACK.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + } + WindowKind::Monocle => { + border_manager::MONOCLE.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + } + WindowKind::Unfocused => { + border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + } + }, SocketMessage::ActiveWindowBorderStyle(style) => { - let mut active_window_border_style = ACTIVE_WINDOW_BORDER_STYLE.lock(); + let mut active_window_border_style = STYLE.lock(); *active_window_border_style = style; - - WindowsApi::invalidate_border_rect()?; } SocketMessage::BorderWidth(width) => { - BORDER_WIDTH.store(width, Ordering::SeqCst); - WindowsApi::invalidate_border_rect()?; + border_manager::BORDER_WIDTH.store(width, Ordering::SeqCst); } SocketMessage::BorderOffset(offset) => { - BORDER_OFFSET.store(offset, Ordering::SeqCst); - WindowsApi::invalidate_border_rect()?; + border_manager::BORDER_OFFSET.store(offset, Ordering::SeqCst); } SocketMessage::StackbarMode(mode) => { let mut stackbar_mode = STACKBAR_MODE.lock(); @@ -1370,155 +1312,14 @@ impl WindowManager { | SocketMessage::IdentifyBorderOverflowApplication(_, _) => {} }; - match message { - SocketMessage::ToggleMonocle => { - let current = BORDER_COLOUR_CURRENT.load(Ordering::SeqCst); - let monocle = BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst); - - if monocle != 0 { - if current == monocle { - BORDER_COLOUR_CURRENT.store( - BORDER_COLOUR_SINGLE.load(Ordering::SeqCst), - Ordering::SeqCst, - ); - } else { - BORDER_COLOUR_CURRENT.store( - BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst), - Ordering::SeqCst, - ); - } - } - } - SocketMessage::StackWindow(_) => { - let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst); - if stack != 0 { - BORDER_COLOUR_CURRENT - .store(BORDER_COLOUR_STACK.load(Ordering::SeqCst), Ordering::SeqCst); - } - } - SocketMessage::UnstackWindow => { - BORDER_COLOUR_CURRENT.store( - BORDER_COLOUR_SINGLE.load(Ordering::SeqCst), - Ordering::SeqCst, - ); - } - _ => {} - } - - match message { - SocketMessage::ChangeLayout(_) - | SocketMessage::CycleLayout(_) - | SocketMessage::ChangeLayoutCustom(_) - | SocketMessage::FlipLayout(_) - | SocketMessage::ManageFocusedWindow - | SocketMessage::MoveWorkspaceToMonitorNumber(_) - | SocketMessage::MoveContainerToMonitorNumber(_) - | SocketMessage::MoveContainerToWorkspaceNumber(_) - | SocketMessage::MoveContainerToMonitorWorkspaceNumber(_, _) - | SocketMessage::MoveContainerToNamedWorkspace(_) - | SocketMessage::ResizeWindowEdge(_, _) - | SocketMessage::ResizeWindowAxis(_, _) - | SocketMessage::ToggleFloat - | SocketMessage::ToggleMonocle - | SocketMessage::ToggleMaximize - | SocketMessage::Promote - | SocketMessage::PromoteFocus - | SocketMessage::StackWindow(_) - | SocketMessage::UnstackWindow - | SocketMessage::Retile - // Adding this one so that changes can be seen instantly after - // modifying the active window border offset - | SocketMessage::BorderOffset(_) - // Adding this one because sometimes EVENT_SYSTEM_FOREGROUND isn't - // getting sent on FocusWindow, meaning the border won't be set - // when processing events - | SocketMessage::FocusWindow(_) - | SocketMessage::InvisibleBorders(_) - | SocketMessage::WorkAreaOffset(_) - | SocketMessage::CycleMoveWindow(_) - | SocketMessage::MoveWindow(_) - | SocketMessage::CycleFocusMonitor(_) - | SocketMessage::CycleFocusWorkspace(_) - | SocketMessage::FocusMonitorNumber(_) - | SocketMessage::FocusMonitorWorkspaceNumber(_, _) - | SocketMessage::FocusWorkspaceNumber(_) => { - // The foreground window might be de-activating if we've just - // set it as a result of our own actions, so wait until the new - // one returns. This particularly happens when switching monitors. - // - // TODO(raggi): re-evaluate this branch. I checked the - // suggestion from the comment above, that we don't get - // EVENT_SYSTEM_FOREGROUND, but if I print out trace events I - // see that we do. - // XXX(raggi) We drop FocusChange events though for windows that - // we're not managing, so that's one of the ways that the border - // window gets stuck. We should stop overloading `should_manage` - // as an event filter, and separately filter events that we want - // to handle, and windows that we want to handle, as some events - // must be handled even if we're not managing the target window. - let mut attempts = 0; - let foreground = loop { - match WindowsApi::foreground_window() { - Ok(foreground) => break foreground, - Err(_) => { - std::thread::sleep(std::time::Duration::from_millis(10)); - attempts+=1; - if attempts == 10 { - bail!("failed to get foreground window after 100ms") - } - } - }; - }; - let foreground_window = Window { hwnd: foreground }; - - let monocle = BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst); - if monocle != 0 && self.focused_workspace()?.monocle_container().is_some() { - BORDER_COLOUR_CURRENT.store( - monocle, - Ordering::SeqCst, - ); - } - - // it is not acceptable to fail here; we need to be able to send the event to - // subscribers - if self.focused_container().is_ok() { - let stack = BORDER_COLOUR_STACK.load(Ordering::SeqCst); - if stack != 0 && self.focused_container()?.windows().len() > 1 { - BORDER_COLOUR_CURRENT - .store(stack, Ordering::SeqCst); - } - } - - let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - border.set_position(foreground_window, false)?; - } - SocketMessage::TogglePause => { - let is_paused = self.is_paused; - let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - - if is_paused { - border.hide()?; - } else { - let focused = self.focused_window()?; - border.set_position(*focused, true)?; - focused.focus(false)?; - } - } - SocketMessage::ToggleTiling | SocketMessage::WorkspaceTiling(..) => { - let tiling_enabled = *self.focused_workspace_mut()?.tile(); - let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - - if tiling_enabled { - let focused = self.focused_window()?; - border.set_position(*focused, true)?; - focused.focus(false)?; - } else { - border.hide()?; - } - } - _ => {} + let notification = Notification { + event: NotificationEvent::Socket(message.clone()), + state: self.as_ref().into(), }; + notify_subscribers(&serde_json::to_string(¬ification)?)?; + border_manager::event_tx().send(border_manager::Notification)?; + tracing::info!("processed"); Ok(()) } @@ -1594,10 +1395,6 @@ pub fn read_commands_uds(wm: &Arc>, mut stream: UnixStream) } wm.process_command(message.clone(), &mut stream)?; - notify_subscribers(&serde_json::to_string(&Notification { - event: NotificationEvent::Socket(message.clone()), - state: wm.as_ref().into(), - })?)?; } Ok(()) @@ -1644,10 +1441,6 @@ pub fn read_commands_tcp( } wm.process_command(message.clone(), &mut *stream)?; - notify_subscribers(&serde_json::to_string(&Notification { - event: NotificationEvent::Socket(message.clone()), - state: wm.as_ref().into(), - })?)?; } } } diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 9d42d341..c81f497f 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -11,7 +11,9 @@ use komorebi_core::Rect; use komorebi_core::Sizing; use komorebi_core::WindowContainerBehaviour; -use crate::border::Border; +use crate::border_manager; +use crate::border_manager::BORDER_OFFSET; +use crate::border_manager::BORDER_WIDTH; use crate::current_virtual_desktop; use crate::notify_subscribers; use crate::window::should_act; @@ -19,17 +21,9 @@ use crate::window::RuleDebug; use crate::window_manager::WindowManager; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; +use crate::winevent::WinEvent; use crate::Notification; use crate::NotificationEvent; -use crate::BORDER_COLOUR_CURRENT; -use crate::BORDER_COLOUR_MONOCLE; -use crate::BORDER_COLOUR_SINGLE; -use crate::BORDER_COLOUR_STACK; -use crate::BORDER_ENABLED; -use crate::BORDER_HIDDEN; -use crate::BORDER_HWND; -use crate::BORDER_OFFSET; -use crate::BORDER_WIDTH; use crate::DATA_DIR; use crate::HIDDEN_HWNDS; use crate::REGEX_IDENTIFIERS; @@ -71,28 +65,6 @@ impl WindowManager { let should_manage = event.window().should_manage(Some(event), &mut rule_debug)?; - // Hide or reposition the window based on whether the target is managed. - if BORDER_ENABLED.load(Ordering::SeqCst) { - if let WindowManagerEvent::FocusChange(_, window) = event { - let border_window = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - - if should_manage { - border_window.set_position(window, true)?; - } else { - let mut stackbar = false; - if let Ok(class) = window.class() { - if class == "komorebi_stackbar" { - stackbar = true; - } - } - - if !stackbar { - border_window.hide()?; - } - } - } - } - // All event handlers below this point should only be processed if the event is // related to a window that should be managed by the WindowManager. if !should_manage && !matches!(event, WindowManagerEvent::DisplayChange(_)) { @@ -598,106 +570,11 @@ impl WindowManager { | WindowManagerEvent::Cloak(..) => {} }; - if !self.focused_workspace()?.tile() { - let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - border.hide()?; - BORDER_HIDDEN.store(true, Ordering::SeqCst); - } - - if *self.focused_workspace()?.tile() && BORDER_ENABLED.load(Ordering::SeqCst) { - match event { - WindowManagerEvent::MoveResizeStart(_, _) => { - let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - border.hide()?; - BORDER_HIDDEN.store(true, Ordering::SeqCst); - } - WindowManagerEvent::MoveResizeEnd(_, window) - | WindowManagerEvent::Show(_, window) - | WindowManagerEvent::FocusChange(_, window) - | WindowManagerEvent::Hide(_, window) - | WindowManagerEvent::Uncloak(_, window) - | WindowManagerEvent::Minimize(_, window) => { - let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - let mut target_window = None; - let mut target_window_is_monocle = false; - if self - .focused_workspace()? - .floating_windows() - .iter() - .any(|w| w.hwnd == window.hwnd) - { - target_window = Option::from(window); - WindowsApi::raise_window(border.hwnd())?; - }; - - if let Some(monocle_container) = self.focused_workspace()?.monocle_container() { - if let Some(window) = monocle_container.focused_window() { - target_window = Option::from(*window); - target_window_is_monocle = true; - } - } - - if target_window.is_none() { - match self.focused_container() { - // if there is no focused container, the desktop is empty - Err(..) => { - WindowsApi::hide_border_window(border.hwnd())?; - } - Ok(container) => { - if !(matches!(event, WindowManagerEvent::Minimize(_, _)) - && container.windows().len() == 1) - { - let container_size = self.focused_container()?.windows().len(); - target_window = Option::from(*self.focused_window()?); - - if target_window_is_monocle { - BORDER_COLOUR_CURRENT.store( - BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst), - Ordering::SeqCst, - ); - } else if container_size > 1 { - BORDER_COLOUR_CURRENT.store( - BORDER_COLOUR_STACK.load(Ordering::SeqCst), - Ordering::SeqCst, - ); - } else { - BORDER_COLOUR_CURRENT.store( - BORDER_COLOUR_SINGLE.load(Ordering::SeqCst), - Ordering::SeqCst, - ); - } - } - } - } - } - - if let Some(target_window) = target_window { - let activate = BORDER_HIDDEN.load(Ordering::SeqCst); - - WindowsApi::invalidate_border_rect()?; - border.set_position(target_window, activate)?; - - if activate { - BORDER_HIDDEN.store(false, Ordering::SeqCst); - } - } - } - _ => {} - } - } - // If we unmanaged a window, it shouldn't be immediately hidden behind managed windows if let WindowManagerEvent::Unmanage(window) = event { window.center(&self.focused_monitor_work_area()?)?; } - // If there are no more windows on the workspace, we shouldn't show the border window - if self.focused_workspace()?.containers().is_empty() { - let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - border.hide()?; - BORDER_HIDDEN.store(true, Ordering::SeqCst); - } - tracing::trace!("updating list of known hwnds"); let mut known_hwnds = vec![]; for monitor in self.monitors() { @@ -718,10 +595,21 @@ impl WindowManager { .open(hwnd_json)?; serde_json::to_writer_pretty(&file, &known_hwnds)?; - notify_subscribers(&serde_json::to_string(&Notification { + + let notification = Notification { event: NotificationEvent::WindowManager(event), state: self.as_ref().into(), - })?)?; + }; + + // Avoid unnecessary updates, this fires every single time you interact + // with something on JetBrains IDEs + if !matches!( + event, + WindowManagerEvent::Show(WinEvent::ObjectNameChange, _) + ) { + notify_subscribers(&serde_json::to_string(¬ification)?)?; + border_manager::event_tx().send(border_manager::Notification)?; + } tracing::info!("processed: {}", event.window().to_string()); Ok(()) diff --git a/komorebi/src/stackbar.rs b/komorebi/src/stackbar.rs index c60f3265..71378e3a 100644 --- a/komorebi/src/stackbar.rs +++ b/komorebi/src/stackbar.rs @@ -61,7 +61,6 @@ use crate::STACKBAR_TAB_BACKGROUND_COLOUR; use crate::STACKBAR_TAB_HEIGHT; use crate::STACKBAR_TAB_WIDTH; use crate::STACKBAR_UNFOCUSED_TEXT_COLOUR; -use crate::TRANSPARENCY_COLOUR; use crate::WINDOWS_BY_BAR_HWNDS; #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] @@ -129,7 +128,7 @@ impl Stackbar { lpfnWndProc: Some(Self::window_proc), hInstance: h_module.into(), lpszClassName: class_name, - hbrBackground: WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR), + hbrBackground: WindowsApi::create_solid_brush(0), ..Default::default() }; @@ -157,7 +156,7 @@ impl Stackbar { None, ); - SetLayeredWindowAttributes(hwnd, COLORREF(TRANSPARENCY_COLOUR), 0, LWA_COLORKEY)?; + SetLayeredWindowAttributes(hwnd, COLORREF(0), 0, LWA_COLORKEY)?; hwnd_sender.send(hwnd)?; let mut msg = MSG::default(); diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 46a6fb1b..b718d320 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -1,4 +1,7 @@ -use crate::border::Border; +use crate::border_manager; +use crate::border_manager::ZOrder; +use crate::border_manager::STYLE; +use crate::border_manager::Z_ORDER; use crate::colour::Colour; use crate::current_virtual_desktop; use crate::monitor::Monitor; @@ -7,15 +10,6 @@ use crate::window_manager::WindowManager; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::workspace::Workspace; -use crate::ACTIVE_WINDOW_BORDER_STYLE; -use crate::BORDER_COLOUR_CURRENT; -use crate::BORDER_COLOUR_MONOCLE; -use crate::BORDER_COLOUR_SINGLE; -use crate::BORDER_COLOUR_STACK; -use crate::BORDER_ENABLED; -use crate::BORDER_HWND; -use crate::BORDER_OFFSET; -use crate::BORDER_WIDTH; use crate::DATA_DIR; use crate::DEFAULT_CONTAINER_PADDING; use crate::DEFAULT_WORKSPACE_PADDING; @@ -77,11 +71,13 @@ use uds_windows::UnixStream; #[derive(Debug, Serialize, Deserialize, JsonSchema)] pub struct ActiveWindowBorderColours { /// Border colour when the container contains a single window - pub single: Colour, + pub single: Option, /// Border colour when the container contains multiple windows - pub stack: Colour, + pub stack: Option, /// Border colour when the container is in monocle mode - pub monocle: Colour, + pub monocle: Option, + /// Border colour when the container is unfocused + pub unfocused: Option, } #[derive(Debug, Serialize, Deserialize, JsonSchema)] @@ -265,6 +261,9 @@ pub struct StaticConfig { /// Active window border style (default: System) #[serde(skip_serializing_if = "Option::is_none")] pub active_window_border_style: Option, + /// Active window border z-order (default: System) + #[serde(skip_serializing_if = "Option::is_none")] + pub active_window_border_z_order: Option, /// Global default workspace padding (default: 10) #[serde(skip_serializing_if = "Option::is_none")] pub default_workspace_padding: Option, @@ -384,21 +383,16 @@ impl From<&WindowManager> for StaticConfig { } } - let border_colours = if BORDER_COLOUR_SINGLE.load(Ordering::SeqCst) == 0 { + let border_colours = if border_manager::FOCUSED.load(Ordering::SeqCst) == 0 { None } else { Option::from(ActiveWindowBorderColours { - single: Colour::from(BORDER_COLOUR_SINGLE.load(Ordering::SeqCst)), - stack: Colour::from(if BORDER_COLOUR_STACK.load(Ordering::SeqCst) == 0 { - BORDER_COLOUR_SINGLE.load(Ordering::SeqCst) - } else { - BORDER_COLOUR_STACK.load(Ordering::SeqCst) - }), - monocle: Colour::from(if BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst) == 0 { - BORDER_COLOUR_SINGLE.load(Ordering::SeqCst) - } else { - BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst) - }), + single: Option::from(Colour::from(border_manager::FOCUSED.load(Ordering::SeqCst))), + stack: Option::from(Colour::from(border_manager::STACK.load(Ordering::SeqCst))), + monocle: Option::from(Colour::from(border_manager::MONOCLE.load(Ordering::SeqCst))), + unfocused: Option::from(Colour::from( + border_manager::UNFOCUSED.load(Ordering::SeqCst), + )), }) }; @@ -413,11 +407,14 @@ impl From<&WindowManager> for StaticConfig { focus_follows_mouse: value.focus_follows_mouse, mouse_follows_focus: Option::from(value.mouse_follows_focus), app_specific_configuration_path: None, - border_width: Option::from(BORDER_WIDTH.load(Ordering::SeqCst)), - border_offset: Option::from(BORDER_OFFSET.load(Ordering::SeqCst)), - active_window_border: Option::from(BORDER_ENABLED.load(Ordering::SeqCst)), + border_width: Option::from(border_manager::BORDER_WIDTH.load(Ordering::SeqCst)), + border_offset: Option::from(border_manager::BORDER_OFFSET.load(Ordering::SeqCst)), + active_window_border: Option::from( + border_manager::BORDER_ENABLED.load(Ordering::SeqCst), + ), active_window_border_colours: border_colours, - active_window_border_style: Option::from(*ACTIVE_WINDOW_BORDER_STYLE.lock()), + active_window_border_style: Option::from(*STYLE.lock()), + active_window_border_z_order: Option::from(*Z_ORDER.lock()), default_workspace_padding: Option::from( DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst), ), @@ -466,26 +463,33 @@ impl StaticConfig { DEFAULT_WORKSPACE_PADDING.store(workspace, Ordering::SeqCst); } - self.border_width.map_or_else( - || { - BORDER_WIDTH.store(8, Ordering::SeqCst); - }, - |width| { - BORDER_WIDTH.store(width, Ordering::SeqCst); - }, - ); + border_manager::BORDER_WIDTH.store(self.border_width.unwrap_or(8), Ordering::SeqCst); + border_manager::BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst); - BORDER_OFFSET.store(self.border_offset.unwrap_or(-1), Ordering::SeqCst); + if let Some(enabled) = &self.active_window_border { + border_manager::BORDER_ENABLED.store(*enabled, Ordering::SeqCst); + } if let Some(colours) = &self.active_window_border_colours { - BORDER_COLOUR_SINGLE.store(u32::from(colours.single), Ordering::SeqCst); - BORDER_COLOUR_CURRENT.store(u32::from(colours.single), Ordering::SeqCst); - BORDER_COLOUR_STACK.store(u32::from(colours.stack), Ordering::SeqCst); - BORDER_COLOUR_MONOCLE.store(u32::from(colours.monocle), Ordering::SeqCst); + if let Some(single) = colours.single { + border_manager::FOCUSED.store(u32::from(single), Ordering::SeqCst); + } + + if let Some(stack) = colours.stack { + border_manager::STACK.store(u32::from(stack), Ordering::SeqCst); + } + + if let Some(monocle) = colours.monocle { + border_manager::MONOCLE.store(u32::from(monocle), Ordering::SeqCst); + } + + if let Some(unfocused) = colours.unfocused { + border_manager::UNFOCUSED.store(u32::from(unfocused), Ordering::SeqCst); + } } let active_window_border_style = self.active_window_border_style.unwrap_or_default(); - *ACTIVE_WINDOW_BORDER_STYLE.lock() = active_window_border_style; + *STYLE.lock() = active_window_border_style; let mut float_identifiers = FLOAT_IDENTIFIERS.lock(); let mut regex_identifiers = REGEX_IDENTIFIERS.lock(); @@ -718,12 +722,7 @@ impl StaticConfig { } if value.active_window_border == Some(true) { - if BORDER_HWND.load(Ordering::SeqCst) == 0 { - Border::create("komorebi-border-window")?; - } - - BORDER_ENABLED.store(true, Ordering::SeqCst); - wm.show_border()?; + border_manager::BORDER_ENABLED.store(true, Ordering::SeqCst); } Ok(()) @@ -777,16 +776,8 @@ impl StaticConfig { } } - if value.active_window_border == Some(true) { - if BORDER_HWND.load(Ordering::SeqCst) == 0 { - Border::create("komorebi-border-window")?; - } - - BORDER_ENABLED.store(true, Ordering::SeqCst); - wm.show_border()?; - } else { - BORDER_ENABLED.store(false, Ordering::SeqCst); - wm.hide_border()?; + if let Some(enabled) = value.active_window_border { + border_manager::BORDER_ENABLED.store(enabled, Ordering::SeqCst); } if let Some(val) = value.window_container_behaviour { diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 6ac5dc35..c5540153 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -39,7 +39,8 @@ use komorebi_core::Rect; use komorebi_core::Sizing; use komorebi_core::WindowContainerBehaviour; -use crate::border::Border; +use crate::border_manager; +use crate::border_manager::STYLE; use crate::container::Container; use crate::current_virtual_desktop; use crate::load_configuration; @@ -55,14 +56,6 @@ use crate::ActiveWindowBorderColours; use crate::Colour; use crate::Rgb; use crate::WorkspaceRule; -use crate::ACTIVE_WINDOW_BORDER_STYLE; -use crate::BORDER_COLOUR_MONOCLE; -use crate::BORDER_COLOUR_SINGLE; -use crate::BORDER_COLOUR_STACK; -use crate::BORDER_ENABLED; -use crate::BORDER_HWND; -use crate::BORDER_OFFSET; -use crate::BORDER_WIDTH; use crate::CUSTOM_FFM; use crate::DATA_DIR; use crate::DISPLAY_INDEX_PREFERENCES; @@ -153,15 +146,24 @@ pub struct GlobalState { impl Default for GlobalState { fn default() -> Self { Self { - active_window_border_enabled: BORDER_ENABLED.load(Ordering::SeqCst), + active_window_border_enabled: border_manager::BORDER_ENABLED.load(Ordering::SeqCst), active_window_border_colours: ActiveWindowBorderColours { - single: Colour::Rgb(Rgb::from(BORDER_COLOUR_SINGLE.load(Ordering::SeqCst))), - stack: Colour::Rgb(Rgb::from(BORDER_COLOUR_STACK.load(Ordering::SeqCst))), - monocle: Colour::Rgb(Rgb::from(BORDER_COLOUR_MONOCLE.load(Ordering::SeqCst))), + single: Option::from(Colour::Rgb(Rgb::from( + border_manager::FOCUSED.load(Ordering::SeqCst), + ))), + stack: Option::from(Colour::Rgb(Rgb::from( + border_manager::STACK.load(Ordering::SeqCst), + ))), + monocle: Option::from(Colour::Rgb(Rgb::from( + border_manager::MONOCLE.load(Ordering::SeqCst), + ))), + unfocused: Option::from(Colour::Rgb(Rgb::from( + border_manager::UNFOCUSED.load(Ordering::SeqCst), + ))), }, - active_window_border_style: *ACTIVE_WINDOW_BORDER_STYLE.lock(), - border_offset: BORDER_OFFSET.load(Ordering::SeqCst), - border_width: BORDER_WIDTH.load(Ordering::SeqCst), + active_window_border_style: *STYLE.lock(), + border_offset: border_manager::BORDER_OFFSET.load(Ordering::SeqCst), + border_width: border_manager::BORDER_WIDTH.load(Ordering::SeqCst), stackbar_mode: *STACKBAR_MODE.lock(), stackbar_focused_text_colour: Colour::Rgb(Rgb::from( STACKBAR_FOCUSED_TEXT_COLOUR.load(Ordering::SeqCst), @@ -286,28 +288,6 @@ impl WindowManager { WindowsApi::load_workspace_information(&mut self.monitors) } - #[tracing::instrument(skip(self))] - pub fn show_border(&self) -> Result<()> { - if self.focused_container().is_ok() { - let foreground = WindowsApi::foreground_window()?; - let foreground_window = Window { hwnd: foreground }; - - let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - border.set_position(foreground_window, true)?; - WindowsApi::invalidate_border_rect()?; - } - - Ok(()) - } - - #[tracing::instrument(skip(self))] - pub fn hide_border(&self) -> Result<()> { - let focused = self.focused_window()?; - let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - border.hide()?; - focused.focus(false) - } - #[tracing::instrument] pub fn reload_configuration() { tracing::info!("reloading configuration"); diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index aa223ad4..8f0c735d 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -2,7 +2,6 @@ use std::collections::VecDeque; use std::convert::TryFrom; use std::ffi::c_void; use std::mem::size_of; -use std::sync::atomic::Ordering; use color_eyre::eyre::anyhow; use color_eyre::eyre::Error; @@ -34,7 +33,6 @@ use windows::Win32::Graphics::Gdi::CreateSolidBrush; use windows::Win32::Graphics::Gdi::EnumDisplayDevicesW; use windows::Win32::Graphics::Gdi::EnumDisplayMonitors; use windows::Win32::Graphics::Gdi::GetMonitorInfoW; -use windows::Win32::Graphics::Gdi::InvalidateRect; use windows::Win32::Graphics::Gdi::MonitorFromPoint; use windows::Win32::Graphics::Gdi::MonitorFromWindow; use windows::Win32::Graphics::Gdi::Rectangle; @@ -101,7 +99,6 @@ use windows::Win32::UI::WindowsAndMessaging::EDD_GET_DEVICE_INTERFACE_NAME; use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE; use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE; use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT; -use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM; use windows::Win32::UI::WindowsAndMessaging::HWND_TOP; use windows::Win32::UI::WindowsAndMessaging::LWA_ALPHA; use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY; @@ -124,6 +121,7 @@ use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_ACTION; use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS; use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX; use windows::Win32::UI::WindowsAndMessaging::WM_CLOSE; +use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY; use windows::Win32::UI::WindowsAndMessaging::WNDCLASSW; use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC; use windows::Win32::UI::WindowsAndMessaging::WS_DISABLED; @@ -142,8 +140,6 @@ use crate::monitor::Monitor; use crate::ring::Ring; use crate::set_window_position::SetWindowPosition; use crate::windows_callbacks; -use crate::BORDER_HWND; -use crate::TRANSPARENCY_COLOUR; pub enum WindowsResult { Err(E), @@ -395,33 +391,9 @@ impl WindowsApi { Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits()) } - pub fn position_border_window(hwnd: HWND, layout: &Rect, activate: bool) -> Result<()> { - let flags = if activate { - SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE - } else { - SetWindowPosition::NO_ACTIVATE - }; - - // TODO(raggi): This leaves the window behind the active window, which - // can result e.g. single pixel window borders being invisible in the - // case of opaque window borders (e.g. EPIC Games Launcher). Ideally - // we'd be able to pass a parent window to place ourselves just in front - // of, however the SetWindowPos API explicitly ignores that parameter - // unless the window being positioned is being activated - and we don't - // want to activate the border window here. We can hopefully find a - // better workaround in the future. - // The trade-off chosen prevents the border window from sitting over the - // top of other pop-up dialogs such as a file picker dialog from - // Firefox. When adjusting this in the future, it's important to check - // those dialog cases. - Self::set_window_pos(hwnd, layout, HWND_TOP, flags.bits()) - } - - pub fn hide_border_window(hwnd: HWND) -> Result<()> { - let flags = SetWindowPosition::HIDE_WINDOW; - - let position = HWND_BOTTOM; - Self::set_window_pos(hwnd, &Rect::default(), position, flags.bits()) + pub fn set_border_pos(hwnd: HWND, layout: &Rect, position: HWND) -> Result<()> { + let flags = { SetWindowPosition::SHOW_WINDOW | SetWindowPosition::NO_ACTIVATE }; + Self::set_window_pos(hwnd, layout, position, flags.bits()) } /// set_window_pos calls SetWindowPos without any accounting for Window decorations. @@ -461,6 +433,13 @@ impl WindowsApi { } } + pub fn destroy_window(hwnd: HWND) -> Result<()> { + match Self::post_message(hwnd, WM_DESTROY, WPARAM(0), LPARAM(0)) { + Ok(()) => Ok(()), + Err(_) => Err(anyhow!("could not close window")), + } + } + pub fn hide_window(hwnd: HWND) { Self::show_window(hwnd, SW_HIDE); } @@ -963,7 +942,7 @@ impl WindowsApi { None, ); - SetLayeredWindowAttributes(hwnd, COLORREF(TRANSPARENCY_COLOUR), 0, LWA_COLORKEY)?; + SetLayeredWindowAttributes(hwnd, COLORREF(0), 0, LWA_COLORKEY)?; hwnd } @@ -1000,12 +979,6 @@ impl WindowsApi { .process() } - pub fn invalidate_border_rect() -> Result<()> { - unsafe { InvalidateRect(HWND(BORDER_HWND.load(Ordering::SeqCst)), None, false) } - .ok() - .process() - } - pub fn alt_is_pressed() -> bool { let state = unsafe { GetKeyState(i32::from(VK_MENU.0)) }; #[allow(clippy::cast_sign_loss)] diff --git a/komorebi/src/windows_callbacks.rs b/komorebi/src/windows_callbacks.rs index f1bac55a..42ce5862 100644 --- a/komorebi/src/windows_callbacks.rs +++ b/komorebi/src/windows_callbacks.rs @@ -1,36 +1,21 @@ use std::collections::VecDeque; -use std::sync::atomic::Ordering; use widestring::U16CStr; use windows::Win32::Foundation::BOOL; -use windows::Win32::Foundation::COLORREF; use windows::Win32::Foundation::HWND; use windows::Win32::Foundation::LPARAM; use windows::Win32::Foundation::LRESULT; use windows::Win32::Foundation::RECT; use windows::Win32::Foundation::WPARAM; -use windows::Win32::Graphics::Gdi::BeginPaint; -use windows::Win32::Graphics::Gdi::CreatePen; -use windows::Win32::Graphics::Gdi::EndPaint; -use windows::Win32::Graphics::Gdi::Rectangle; -use windows::Win32::Graphics::Gdi::RoundRect; -use windows::Win32::Graphics::Gdi::SelectObject; -use windows::Win32::Graphics::Gdi::ValidateRect; use windows::Win32::Graphics::Gdi::HDC; use windows::Win32::Graphics::Gdi::HMONITOR; -use windows::Win32::Graphics::Gdi::PAINTSTRUCT; -use windows::Win32::Graphics::Gdi::PS_INSIDEFRAME; -use windows::Win32::Graphics::Gdi::PS_SOLID; use windows::Win32::UI::Accessibility::HWINEVENTHOOK; use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW; -use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage; use windows::Win32::UI::WindowsAndMessaging::DBT_DEVNODES_CHANGED; use windows::Win32::UI::WindowsAndMessaging::SPI_ICONVERTICALSPACING; use windows::Win32::UI::WindowsAndMessaging::SPI_SETWORKAREA; -use windows::Win32::UI::WindowsAndMessaging::WM_DESTROY; use windows::Win32::UI::WindowsAndMessaging::WM_DEVICECHANGE; use windows::Win32::UI::WindowsAndMessaging::WM_DISPLAYCHANGE; -use windows::Win32::UI::WindowsAndMessaging::WM_PAINT; use windows::Win32::UI::WindowsAndMessaging::WM_SETTINGCHANGE; use crate::container::Container; @@ -42,15 +27,8 @@ use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::winevent::WinEvent; use crate::winevent_listener; -use crate::ACTIVE_WINDOW_BORDER_STYLE; -use crate::BORDER_COLOUR_CURRENT; -use crate::BORDER_RECT; -use crate::BORDER_WIDTH; use crate::DISPLAY_INDEX_PREFERENCES; use crate::MONITOR_INDEX_PREFERENCES; -use crate::TRANSPARENCY_COLOUR; -use crate::WINDOWS_11; -use komorebi_core::ActiveWindowBorderStyle; pub extern "system" fn valid_display_monitors( hmonitor: HMONITOR, @@ -204,62 +182,6 @@ pub extern "system" fn win_event_hook( .expect("could not send message on winevent_listener::event_tx"); } -pub extern "system" fn border_window( - window: HWND, - message: u32, - wparam: WPARAM, - lparam: LPARAM, -) -> LRESULT { - unsafe { - match message { - WM_PAINT => { - let border_rect = *BORDER_RECT.lock(); - let mut ps = PAINTSTRUCT::default(); - let hdc = BeginPaint(window, &mut ps); - let hpen = CreatePen( - PS_SOLID | PS_INSIDEFRAME, - BORDER_WIDTH.load(Ordering::SeqCst), - COLORREF(BORDER_COLOUR_CURRENT.load(Ordering::SeqCst)), - ); - let hbrush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR); - - SelectObject(hdc, hpen); - SelectObject(hdc, hbrush); - // TODO(raggi): this is approximately the correct curvature for - // the top left of a Windows 11 window (DWMWCP_DEFAULT), but - // often the bottom right has a different shape. Furthermore if - // the window was made with DWMWCP_ROUNDSMALL then this is the - // wrong size. In the future we should read the DWM properties - // of windows and attempt to match appropriately. - match *ACTIVE_WINDOW_BORDER_STYLE.lock() { - ActiveWindowBorderStyle::System => { - if *WINDOWS_11 { - RoundRect(hdc, 0, 0, border_rect.right, border_rect.bottom, 20, 20); - } else { - Rectangle(hdc, 0, 0, border_rect.right, border_rect.bottom); - } - } - ActiveWindowBorderStyle::Rounded => { - RoundRect(hdc, 0, 0, border_rect.right, border_rect.bottom, 20, 20); - } - ActiveWindowBorderStyle::Square => { - Rectangle(hdc, 0, 0, border_rect.right, border_rect.bottom); - } - } - EndPaint(window, &ps); - ValidateRect(window, None); - - LRESULT(0) - } - WM_DESTROY => { - PostQuitMessage(0); - LRESULT(0) - } - _ => DefWindowProcW(window, message, wparam, lparam), - } - } -} - pub extern "system" fn hidden_window( window: HWND, message: u32, diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 6b09b41e..df9387e3 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -20,14 +20,14 @@ use komorebi_core::Layout; use komorebi_core::OperationDirection; use komorebi_core::Rect; +use crate::border_manager::BORDER_OFFSET; +use crate::border_manager::BORDER_WIDTH; use crate::container::Container; use crate::ring::Ring; use crate::static_config::WorkspaceConfig; use crate::window::Window; use crate::window::WindowDetails; use crate::windows_api::WindowsApi; -use crate::BORDER_OFFSET; -use crate::BORDER_WIDTH; use crate::DEFAULT_CONTAINER_PADDING; use crate::DEFAULT_WORKSPACE_PADDING; use crate::INITIAL_CONFIGURATION_LOADED;