Compare commits

..

4 Commits

Author SHA1 Message Date
LGUG2Z
1897e1845f feat(borders): permashow for floating windows
This commit leverages the z-ordering tracking of a border's target
window to enable permanently showing borders for floating windows while
applying different colours for focus state.
2024-12-09 08:23:03 -08:00
alex-ds13
192af6751b fix(border): stop removing borders on wrong monitors 2024-12-09 07:57:53 -08:00
LGUG2Z
4f306e5bfd docs(schema): update all json schemas 2024-12-07 16:43:28 -08:00
LGUG2Z
ede0b23bb4 feat(borders): track window movements + animations
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!
2024-12-07 12:10:07 -08:00
3 changed files with 1079 additions and 80 deletions

View File

@@ -305,7 +305,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
// Handle the monocle container separately
if let Some(monocle) = ws.monocle_container() {
let mut new_border = false;
let border = match borders.entry(monocle.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
@@ -313,7 +312,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
monocle.id(),
monocle.focused_window().copied().unwrap_or_default().hwnd,
) {
new_border = true;
entry.insert(border)
} else {
continue 'monitors;
@@ -344,9 +342,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
let rect = WindowsApi::window_rect(reference_hwnd)?;
if new_border {
border.set_position(&rect, reference_hwnd)?;
}
border.set_position(&rect, reference_hwnd)?;
border.invalidate();
@@ -369,9 +365,11 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
continue 'monitors;
}
let is_maximized = WindowsApi::is_zoomed(
WindowsApi::foreground_window().unwrap_or_default(),
);
let foreground_hwnd = WindowsApi::foreground_window().unwrap_or_default();
let foreground_monitor_id =
WindowsApi::monitor_from_window(foreground_hwnd);
let is_maximized = foreground_monitor_id == m.id()
&& WindowsApi::is_zoomed(foreground_hwnd);
if is_maximized {
let mut to_remove = vec![];
@@ -418,7 +416,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
for (idx, c) in ws.containers().iter().enumerate() {
// Get the border entry for this container from the map or create one
let mut new_border = false;
let border = match borders.entry(c.id().clone()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
@@ -426,7 +423,6 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
c.id(),
c.focused_window().copied().unwrap_or_default().hwnd,
) {
new_border = true;
entry.insert(border)
} else {
continue 'monitors;
@@ -469,9 +465,7 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
Some(last_focus_state) => last_focus_state != new_focus_state,
};
if new_border {
border.set_position(&rect, reference_hwnd)?;
}
border.set_position(&rect, reference_hwnd)?;
if should_invalidate {
border.invalidate();
@@ -479,15 +473,13 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
}
{
'windows: for window in ws.floating_windows() {
let mut new_border = false;
for window in ws.floating_windows() {
let border = match borders.entry(window.hwnd.to_string()) {
Entry::Occupied(entry) => entry.into_mut(),
Entry::Vacant(entry) => {
if let Ok(border) =
Border::create(&window.hwnd.to_string(), window.hwnd)
{
new_border = true;
entry.insert(border)
} else {
continue 'monitors;
@@ -498,30 +490,23 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
borders_monitors.insert(window.hwnd.to_string(), monitor_idx);
windows_borders.insert(window.hwnd, border.clone());
let mut should_destroy = false;
if let Some(notification_hwnd) = notification.0 {
if notification_hwnd != window.hwnd {
should_destroy = true;
}
}
let mut is_focused = false;
if WindowsApi::foreground_window().unwrap_or_default()
!= window.hwnd
== window.hwnd
{
should_destroy = true;
}
if should_destroy {
border.destroy()?;
borders.remove(&window.hwnd.to_string());
borders_monitors.remove(&window.hwnd.to_string());
continue 'windows;
is_focused = true;
}
#[allow(unused_assignments)]
let mut last_focus_state = None;
let new_focus_state = WindowKind::Floating;
let new_focus_state = if is_focused {
WindowKind::Floating
} else {
WindowKind::Unfocused
};
// Update the focused state for all containers on this workspace
{
let mut focus_state = FOCUS_STATE.lock();
last_focus_state =
@@ -535,9 +520,9 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
Some(last_focus_state) => last_focus_state != new_focus_state,
};
if new_border {
border.set_position(&rect, window.hwnd)?;
}
// this has to be sent to match the z-order when clicking on
// a floating window to focus it
border.set_position(&rect, window.hwnd)?;
if should_invalidate {
border.invalidate();

File diff suppressed because it is too large Load Diff

View File

@@ -13,13 +13,35 @@
"properties": {
"duration": {
"description": "Set the animation duration in ms (default: 250)",
"type": "integer",
"format": "uint64",
"minimum": 0.0
"anyOf": [
{
"type": "object",
"additionalProperties": {
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
},
{
"type": "integer",
"format": "uint64",
"minimum": 0.0
}
]
},
"enabled": {
"description": "Enable or disable animations (default: false)",
"type": "boolean"
"anyOf": [
{
"type": "object",
"additionalProperties": {
"type": "boolean"
}
},
{
"type": "boolean"
}
]
},
"fps": {
"description": "Set the animation FPS (default: 60)",
@@ -29,38 +51,80 @@
},
"style": {
"description": "Set the animation style (default: Linear)",
"type": "string",
"enum": [
"Linear",
"EaseInSine",
"EaseOutSine",
"EaseInOutSine",
"EaseInQuad",
"EaseOutQuad",
"EaseInOutQuad",
"EaseInCubic",
"EaseInOutCubic",
"EaseInQuart",
"EaseOutQuart",
"EaseInOutQuart",
"EaseInQuint",
"EaseOutQuint",
"EaseInOutQuint",
"EaseInExpo",
"EaseOutExpo",
"EaseInOutExpo",
"EaseInCirc",
"EaseOutCirc",
"EaseInOutCirc",
"EaseInBack",
"EaseOutBack",
"EaseInOutBack",
"EaseInElastic",
"EaseOutElastic",
"EaseInOutElastic",
"EaseInBounce",
"EaseOutBounce",
"EaseInOutBounce"
"anyOf": [
{
"type": "object",
"additionalProperties": {
"type": "string",
"enum": [
"Linear",
"EaseInSine",
"EaseOutSine",
"EaseInOutSine",
"EaseInQuad",
"EaseOutQuad",
"EaseInOutQuad",
"EaseInCubic",
"EaseInOutCubic",
"EaseInQuart",
"EaseOutQuart",
"EaseInOutQuart",
"EaseInQuint",
"EaseOutQuint",
"EaseInOutQuint",
"EaseInExpo",
"EaseOutExpo",
"EaseInOutExpo",
"EaseInCirc",
"EaseOutCirc",
"EaseInOutCirc",
"EaseInBack",
"EaseOutBack",
"EaseInOutBack",
"EaseInElastic",
"EaseOutElastic",
"EaseInOutElastic",
"EaseInBounce",
"EaseOutBounce",
"EaseInOutBounce"
]
}
},
{
"type": "string",
"enum": [
"Linear",
"EaseInSine",
"EaseOutSine",
"EaseInOutSine",
"EaseInQuad",
"EaseOutQuad",
"EaseInOutQuad",
"EaseInCubic",
"EaseInOutCubic",
"EaseInQuart",
"EaseOutQuart",
"EaseInOutQuart",
"EaseInQuint",
"EaseOutQuint",
"EaseInOutQuint",
"EaseInExpo",
"EaseOutExpo",
"EaseInOutExpo",
"EaseInCirc",
"EaseOutCirc",
"EaseInOutCirc",
"EaseInBack",
"EaseOutBack",
"EaseInOutBack",
"EaseInElastic",
"EaseOutElastic",
"EaseInOutElastic",
"EaseInBounce",
"EaseOutBounce",
"EaseInOutBounce"
]
}
]
}
}
@@ -420,7 +484,7 @@
"format": "int32"
},
"border_z_order": {
"description": "Active window border z-order (default: System)",
"description": "DEPRECATED from v0.1.31: no longer required",
"type": "string",
"enum": [
"Top",
@@ -579,7 +643,7 @@
}
},
"focus_follows_mouse": {
"description": "END OF LIFE FEATURE: Determine focus follows mouse implementation (default: None)",
"description": "END OF LIFE FEATURE: Use https://github.com/LGUG2Z/masir instead",
"oneOf": [
{
"description": "A custom FFM implementation (slightly more CPU-intensive)",