Skip to content

Commit

Permalink
feat(borders): track window movements + animations
Browse files Browse the repository at this point in the history
This commit introduces a number of changes to the border manager module
to enable borders to track the movements of windows as they are being
animated.

As part of these changes, the code paths for borders to track user
movement of windows have also been overhauled.

The biggest conceptual change introduced here is borrowed from
@lukeyou05's work on tacky-borders, where the primary event listener of
the komorebi process now forwards EVENT_OBJECT_LOCATIONCHANGE and
EVENT_OBJECT_DESTROY messages from application windows directly on to
their borders.

These events are handled directly in the border window callbacks,
outside of the main border manager module event processing loop.

In order to handle these events more performantly in the border window
callbacks, a number of state trackers have been added to the Border
struct.

When handling EVENT_OBJECT_NAMECHANGE, these values are read directly
from the struct, whereas when handling WM_PAINT, which is sent by the
system whenever we invalidate a border window, we update the state
values on the Border structs from the various atomic configuration
variables in the mod.rs file.

Another trick I borrowed from tacky-borders is to store a pointer to the
Border object alongside a border window whenever it is created with
CreateWindowExW, which can be accessed within the callback as
GWLP_USERDATA.

There is some unfortunate introduction of unsafe code to make this
happen, but the callback uses null checks to exit the callback early to
ensure (to the best of my ability) that there are no pointer
dereferencing issues once we start making border changes in the context
of the callback.

There are a few other Direct2D related optimizations throughout this
commit, mainly avoiding the recreation of objects like brush properties
and brushes.

Finally, the border_z_order option is now deprecated as the border
window is now tracking the z-ordering of the application window it is
associated with by default - this should resolve a whole host of subtle
border z-ordering issues, especially when dragging windows around using
the mouse.

This work would not have been possible without the guidance of
@lukeyou05, so if you like this feature, please make sure you thank him
too!
  • Loading branch information
LGUG2Z committed Dec 7, 2024
1 parent e6b5b78 commit ede0b23
Show file tree
Hide file tree
Showing 10 changed files with 394 additions and 248 deletions.
318 changes: 241 additions & 77 deletions komorebi/src/border_manager/border.rs

Large diffs are not rendered by default.

210 changes: 71 additions & 139 deletions komorebi/src/border_manager/mod.rs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions komorebi/src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@ pub enum BorderImplementation {
ValueEnum,
JsonSchema,
PartialEq,
Eq,
Hash,
)]
pub enum WindowKind {
Single,
Expand Down
6 changes: 6 additions & 0 deletions komorebi/src/core/rect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ impl From<Rect> 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<T>(&mut self, padding: T)
Expand Down
12 changes: 10 additions & 2 deletions komorebi/src/process_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?
}
Expand Down Expand Up @@ -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) {
Expand Down
7 changes: 3 additions & 4 deletions komorebi/src/static_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -297,7 +296,7 @@ pub struct StaticConfig {
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(alias = "active_window_border_style")]
pub border_style: Option<BorderStyle>,
/// 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<ZOrder>,
/// Active window border implementation (default: Komorebi)
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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),
Expand Down
6 changes: 0 additions & 6 deletions komorebi/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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();
}
Expand Down
29 changes: 17 additions & 12 deletions komorebi/src/windows_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -154,6 +152,7 @@ macro_rules! as_ptr {
};
}

use crate::border_manager::Border;
pub(crate) use as_ptr;

pub enum WindowsResult<T, E> {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -1090,10 +1095,14 @@ impl WindowsApi {
.process()
}

pub fn create_border_window(name: PCWSTR, instance: isize) -> Result<isize> {
pub fn create_border_window(
name: PCWSTR,
instance: isize,
border: *const Border,
) -> Result<isize> {
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,
Expand All @@ -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()
}
Expand Down
48 changes: 41 additions & 7 deletions komorebi/src/windows_callbacks.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
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;
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<Container>) };
Expand Down Expand Up @@ -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,
Expand All @@ -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;
}

Expand All @@ -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!(
Expand Down
4 changes: 3 additions & 1 deletion komorebi/src/winevent_listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,7 +33,7 @@ pub fn start() {
Some(windows_callbacks::win_event_hook),
0,
0,
0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS,
)
};

Expand Down

0 comments on commit ede0b23

Please sign in to comment.