mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-17 14:29:51 +02:00
This commit makes it so a floating window only has the floating border when it is focused, if not it has the `Unfocused` border. It also makes the 'focused_container' have the `Unfocused` border when it is not the foreground window, for example when we have a floating window focused instead. This commit also changes the border's `window_kind` so that the stored borders actually have that value so we can check it later (This value wasn't being updated). This commit also makes it so we properly invalidate the borders in the situations discussed above (for example when changing focus to/from a floating window we need the floating window border to update its ZOrder as well as the previously focused window). Lastly this commit, changes the `WM_PAINT` code part of the border so that it now sets the position of border so that the border's ZOrder updates to it's tracking window ZOrder.
524 lines
22 KiB
Rust
524 lines
22 KiB
Rust
use crate::border_manager::window_kind_colour;
|
|
use crate::border_manager::WindowKind;
|
|
use crate::border_manager::BORDER_OFFSET;
|
|
use crate::border_manager::BORDER_WIDTH;
|
|
use crate::border_manager::FOCUS_STATE;
|
|
use crate::border_manager::RENDER_TARGETS;
|
|
use crate::border_manager::STYLE;
|
|
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;
|
|
use windows::Win32::Foundation::HWND;
|
|
use windows::Win32::Foundation::LPARAM;
|
|
use windows::Win32::Foundation::LRESULT;
|
|
use windows::Win32::Foundation::TRUE;
|
|
use windows::Win32::Foundation::WPARAM;
|
|
use windows::Win32::Graphics::Direct2D::Common::D2D1_ALPHA_MODE_PREMULTIPLIED;
|
|
use windows::Win32::Graphics::Direct2D::Common::D2D1_COLOR_F;
|
|
use windows::Win32::Graphics::Direct2D::Common::D2D1_PIXEL_FORMAT;
|
|
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;
|
|
use windows::Win32::Graphics::Direct2D::D2D1_HWND_RENDER_TARGET_PROPERTIES;
|
|
use windows::Win32::Graphics::Direct2D::D2D1_PRESENT_OPTIONS_IMMEDIATELY;
|
|
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_PROPERTIES;
|
|
use windows::Win32::Graphics::Direct2D::D2D1_RENDER_TARGET_TYPE_DEFAULT;
|
|
use windows::Win32::Graphics::Direct2D::D2D1_ROUNDED_RECT;
|
|
use windows::Win32::Graphics::Dwm::DwmEnableBlurBehindWindow;
|
|
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::CreateRectRgn;
|
|
use windows::Win32::Graphics::Gdi::InvalidateRect;
|
|
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::WNDCLASSW;
|
|
use windows_core::PCWSTR;
|
|
|
|
#[allow(clippy::expect_used)]
|
|
static RENDER_FACTORY: LazyLock<ID2D1Factory> = unsafe {
|
|
LazyLock::new(|| {
|
|
D2D1CreateFactory::<ID2D1Factory>(D2D1_FACTORY_TYPE_MULTI_THREADED, None)
|
|
.expect("creating RENDER_FACTORY failed")
|
|
})
|
|
};
|
|
|
|
static BRUSH_PROPERTIES: LazyLock<D2D1_BRUSH_PROPERTIES> =
|
|
LazyLock::new(|| D2D1_BRUSH_PROPERTIES {
|
|
opacity: 1.0,
|
|
transform: Matrix3x2::identity(),
|
|
});
|
|
|
|
pub extern "system" fn border_hwnds(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
|
let hwnds = unsafe { &mut *(lparam.0 as *mut Vec<isize>) };
|
|
let hwnd = hwnd.0 as isize;
|
|
|
|
if let Ok(class) = WindowsApi::real_window_class_w(hwnd) {
|
|
if class.starts_with("komoborder") {
|
|
hwnds.push(hwnd);
|
|
}
|
|
}
|
|
|
|
true.into()
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Border {
|
|
pub hwnd: isize,
|
|
pub render_target: OnceLock<ID2D1HwndRenderTarget>,
|
|
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<WindowKind, ID2D1SolidColorBrush>,
|
|
}
|
|
|
|
impl From<isize> for Border {
|
|
fn from(value: isize) -> Self {
|
|
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(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Border {
|
|
pub const fn hwnd(&self) -> HWND {
|
|
HWND(windows_api::as_ptr!(self.hwnd))
|
|
}
|
|
|
|
pub fn create(id: &str, tracking_hwnd: isize) -> color_eyre::Result<Self> {
|
|
let name: Vec<u16> = 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,
|
|
lpfnWndProc: Some(Self::callback),
|
|
hbrBackground: WindowsApi::create_solid_brush(0),
|
|
..Default::default()
|
|
};
|
|
|
|
let _ = WindowsApi::register_class_w(&window_class);
|
|
|
|
let (border_sender, border_receiver) = mpsc::channel();
|
|
|
|
let instance = h_module.0 as isize;
|
|
std::thread::spawn(move || -> color_eyre::Result<()> {
|
|
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();
|
|
|
|
loop {
|
|
unsafe {
|
|
if !GetMessageW(&mut msg, HWND::default(), 0, 0).as_bool() {
|
|
tracing::debug!("border window event processing thread shutdown");
|
|
break;
|
|
};
|
|
// TODO: error handling
|
|
let _ = TranslateMessage(&msg);
|
|
DispatchMessageW(&msg);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
});
|
|
|
|
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
|
|
unsafe {
|
|
let pos: i32 = -GetSystemMetrics(SM_CXVIRTUALSCREEN) - 8;
|
|
let hrgn = CreateRectRgn(pos, 0, pos + 1, 1);
|
|
let mut bh: DWM_BLURBEHIND = Default::default();
|
|
if !hrgn.is_invalid() {
|
|
bh = DWM_BLURBEHIND {
|
|
dwFlags: DWM_BB_ENABLE | DWM_BB_BLURREGION,
|
|
fEnable: TRUE,
|
|
hRgnBlur: hrgn,
|
|
fTransitionOnMaximized: FALSE,
|
|
};
|
|
}
|
|
|
|
let _ = DwmEnableBlurBehindWindow(border.hwnd(), &bh);
|
|
}
|
|
|
|
let hwnd_render_target_properties = D2D1_HWND_RENDER_TARGET_PROPERTIES {
|
|
hwnd: HWND(windows_api::as_ptr!(border.hwnd)),
|
|
pixelSize: Default::default(),
|
|
presentOptions: D2D1_PRESENT_OPTIONS_IMMEDIATELY,
|
|
};
|
|
|
|
let render_target_properties = D2D1_RENDER_TARGET_PROPERTIES {
|
|
r#type: D2D1_RENDER_TARGET_TYPE_DEFAULT,
|
|
pixelFormat: D2D1_PIXEL_FORMAT {
|
|
format: DXGI_FORMAT_UNKNOWN,
|
|
alphaMode: D2D1_ALPHA_MODE_PREMULTIPLIED,
|
|
},
|
|
dpiX: 96.0,
|
|
dpiY: 96.0,
|
|
..Default::default()
|
|
};
|
|
|
|
match unsafe {
|
|
RENDER_FACTORY
|
|
.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(border.hwnd, render_target);
|
|
Ok(border.clone())
|
|
},
|
|
Err(error) => Err(error.into()),
|
|
}
|
|
}
|
|
|
|
pub fn destroy(&self) -> color_eyre::Result<()> {
|
|
let mut render_targets = RENDER_TARGETS.lock();
|
|
render_targets.remove(&self.hwnd);
|
|
WindowsApi::close_window(self.hwnd)
|
|
}
|
|
|
|
pub fn set_position(&self, rect: &Rect, reference_hwnd: isize) -> color_eyre::Result<()> {
|
|
let mut rect = *rect;
|
|
rect.add_margin(self.width);
|
|
rect.add_padding(-self.offset);
|
|
|
|
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) };
|
|
}
|
|
|
|
pub extern "system" fn callback(
|
|
window: HWND,
|
|
message: u32,
|
|
wparam: WPARAM,
|
|
lparam: LPARAM,
|
|
) -> LRESULT {
|
|
unsafe {
|
|
match message {
|
|
WM_CREATE => {
|
|
let mut border_pointer: *mut Border =
|
|
GetWindowLongPtrW(window, GWLP_USERDATA) as _;
|
|
|
|
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 _);
|
|
}
|
|
|
|
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(&D2D_SIZE_U {
|
|
width: rect.right as u32,
|
|
height: rect.bottom as u32,
|
|
});
|
|
|
|
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 (*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);
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
let reference_hwnd = (*border_pointer).tracking_hwnd;
|
|
|
|
// Update position to update the ZOrder
|
|
let border_window_rect = (*border_pointer).window_rect;
|
|
|
|
tracing::trace!("updating border position");
|
|
if let Err(error) =
|
|
(*border_pointer).set_position(&border_window_rect, reference_hwnd)
|
|
{
|
|
tracing::error!("failed to update border position {error}");
|
|
}
|
|
|
|
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)
|
|
}
|
|
_ => DefWindowProcW(window, message, wparam, lparam),
|
|
}
|
|
}
|
|
}
|
|
}
|