Skip to content

Commit

Permalink
feat(borders): add border manager module
Browse files Browse the repository at this point in the history
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<Colour>.
  • Loading branch information
LGUG2Z committed May 12, 2024
1 parent 1b30561 commit 6a1ed3b
Show file tree
Hide file tree
Showing 17 changed files with 646 additions and 716 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions komorebi-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ pub enum WindowKind {
Single,
Stack,
Monocle,
Unfocused,
}

#[derive(
Expand Down
108 changes: 0 additions & 108 deletions komorebi/src/border.rs

This file was deleted.

204 changes: 204 additions & 0 deletions komorebi/src/border_manager/border.rs
Original file line number Diff line number Diff line change
@@ -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<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,
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),
}
}
}
}
Loading

0 comments on commit 6a1ed3b

Please sign in to comment.