From f1ee5ea194c2fda9b9052e44949d6786e54a7b50 Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Tue, 14 Sep 2021 21:16:50 -0700 Subject: [PATCH] feat(wm): make invisible borders configurable Following the changes I witnessed in the invisible window border size following an OS update, this commit makes the invisible border offset configurable via a new komorebic command 'invisible-borders'. When sending a new set of invisible border offset dimensions via komorebic, a full retile across all monitors will take place after the new values have been set. The default values have been set to what is currently correct for my machine, and will likely be updated again in the same way in the future if further changes occur in subsequent OS updates. This commit also updates some dependencies to their latest releases, and removes from the CI workflow a line that attempts to delete the rustup-init.exe binary after installation which has been causing builds to fail. resolve #35 --- .github/workflows/windows.yaml | 1 - Cargo.lock | 20 +++++++------- README.md | 2 ++ komorebi-core/src/lib.rs | 1 + komorebi-core/src/rect.rs | 3 ++- komorebi.sample.ahk | 3 +++ komorebi.sample.with.lib.ahk | 3 +++ komorebi/src/monitor.rs | 4 +-- komorebi/src/process_command.rs | 22 +++++---------- komorebi/src/process_event.rs | 24 +++++++---------- komorebi/src/window.rs | 48 +++++++++------------------------ komorebi/src/window_manager.rs | 39 ++++++++++++++++++++++++--- komorebi/src/workspace.rs | 6 ++--- komorebic.lib.sample.ahk | 4 +++ komorebic/src/main.rs | 27 +++++++++++++++++++ 15 files changed, 121 insertions(+), 86 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 5262ef6d..a01996fc 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -66,7 +66,6 @@ jobs: $ProgressPreference = "SilentlyContinue" Invoke-WebRequest https://win.rustup.rs/ -OutFile rustup-init.exe .\rustup-init.exe -y --default-host=x86_64-pc-windows-msvc --profile=minimal - del rustup-init.exe shell: powershell - name: Ensure stable toolchain is up to date run: rustup update stable diff --git a/Cargo.lock b/Cargo.lock index 486d7952..631bd9d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1101,9 +1101,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.67" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f9e390c27c3c0ce8bc5d725f6e4d30a29d26659494aa4b17535f7522c5c950" +checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8" dependencies = [ "itoa", "ryu", @@ -1244,9 +1244,9 @@ dependencies = [ [[package]] name = "tracing" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09adeb8c97449311ccd28a427f96fb563e7fd31aabf994189879d9da2394b89d" +checksum = "c2ba9ab62b7d6497a8638dfda5e5c4fb3b2d5a7fca4118f2b96151c8ef1a437e" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", @@ -1267,9 +1267,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2" +checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77" dependencies = [ "proc-macro2", "quote", @@ -1278,9 +1278,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ca517f43f0fb96e0c3072ed5c275fe5eece87e8cb52f4a77b69226d3b1c9df8" +checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf" dependencies = [ "lazy_static", ] @@ -1318,9 +1318,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.2.20" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cbe87a2fa7e35900ce5de20220a582a9483a7063811defce79d7cbd59d4cfe" +checksum = "62af966210b88ad5776ee3ba12d5f35b8d6a2b2a12168f3080cf02b814d7376b" dependencies = [ "ansi_term", "chrono", diff --git a/README.md b/README.md index 1d5474b5..dbacb2d8 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,7 @@ send-to-workspace Send the focused window to the specified workspace focus-monitor Focus the specified monitor focus-workspace Focus the specified workspace on the focused monitor new-workspace Create and append a new workspace on the focused monitor +invisible-borders Set the invisible border dimensions around each window adjust-container-padding Adjust container padding on the focused workspace adjust-workspace-padding Adjust workspace padding on the focused workspace change-layout Set the layout on the focused workspace @@ -277,6 +278,7 @@ used [is available here](komorebi.sample.with.lib.ahk). - [x] Additional manage rules based on exe name and window class - [x] Identify applications which overflow their borders by exe name and class - [x] Identify 'close/minimize to tray' applications by exe name and class +- [x] Configure and compensate for the size of Windows 10's invisible borders - [x] Toggle floating windows - [x] Toggle monocle window - [x] Toggle native maximization diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index c4cd6e1e..f34dbeca 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -62,6 +62,7 @@ pub enum SocketMessage { // Configuration ReloadConfiguration, WatchConfiguration(bool), + InvisibleBorders(Rect), WorkspaceRule(ApplicationIdentifier, String, usize, usize), FloatRule(ApplicationIdentifier, String), ManageRule(ApplicationIdentifier, String), diff --git a/komorebi-core/src/rect.rs b/komorebi-core/src/rect.rs index e2c68f71..ff2cfc42 100644 --- a/komorebi-core/src/rect.rs +++ b/komorebi-core/src/rect.rs @@ -1,8 +1,9 @@ +use serde::Deserialize; use serde::Serialize; use bindings::Windows::Win32::Foundation::RECT; -#[derive(Debug, Default, Clone, Copy, Serialize, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)] pub struct Rect { pub left: i32, pub top: i32, diff --git a/komorebi.sample.ahk b/komorebi.sample.ahk index a4ed6ef6..c938a5d6 100644 --- a/komorebi.sample.ahk +++ b/komorebi.sample.ahk @@ -3,6 +3,9 @@ ; Enable hot reloading of changes to this file Run, komorebic.exe watch-configuration enable, , Hide +; Configure the invisible border dimensions +Run, komorebic.exe invisible-borders 7 0 14 7, , Hide + ; Enable focus follows mouse Run, komorebic.exe focus-follows-mouse enable, , Hide diff --git a/komorebi.sample.with.lib.ahk b/komorebi.sample.with.lib.ahk index 57e194ac..d580d117 100644 --- a/komorebi.sample.with.lib.ahk +++ b/komorebi.sample.with.lib.ahk @@ -7,6 +7,9 @@ WatchConfiguration("enable") ; Ensure there are 5 workspaces created on monitor 0 EnsureWorkspaces(0, 5) +; Configure the invisible border dimensions +InvisibleBorders(7, 0, 14, 7) + ; Configure the 1st workspace WorkspaceName(0, 0, "bsp") diff --git a/komorebi/src/monitor.rs b/komorebi/src/monitor.rs index 58b49652..34068fd9 100644 --- a/komorebi/src/monitor.rs +++ b/komorebi/src/monitor.rs @@ -145,12 +145,12 @@ impl Monitor { self.workspaces().len() } - pub fn update_focused_workspace(&mut self) -> Result<()> { + pub fn update_focused_workspace(&mut self, invisible_borders: &Rect) -> Result<()> { let work_area = *self.work_area_size(); self.focused_workspace_mut() .ok_or_else(|| anyhow!("there is no workspace"))? - .update(&work_area)?; + .update(&work_area, invisible_borders)?; Ok(()) } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index ae37147f..89df618e 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -124,21 +124,7 @@ impl WindowManager { self.focus_monitor(monitor_idx)?; self.update_focused_workspace(true)?; } - SocketMessage::Retile => { - for monitor in self.monitors_mut() { - let work_area = *monitor.work_area_size(); - let workspace = monitor - .focused_workspace_mut() - .ok_or_else(|| anyhow!("there is no workspace"))?; - - // Reset any resize adjustments if we want to force a retile - for resize in workspace.resize_dimensions_mut() { - *resize = None; - } - - workspace.update(&work_area)?; - } - } + SocketMessage::Retile => self.retile_all()?, SocketMessage::FlipLayout(layout_flip) => self.flip_layout(layout_flip)?, SocketMessage::ChangeLayout(layout) => self.change_workspace_layout(layout)?, SocketMessage::WorkspaceTiling(monitor_idx, workspace_idx, tile) => { @@ -312,7 +298,11 @@ impl WindowManager { SocketMessage::UnmanageFocusedWindow => { self.unmanage_focused_window()?; } - } + SocketMessage::InvisibleBorders(rect) => { + self.invisible_borders = rect; + self.retile_all()?; + } + }; tracing::info!("processed"); Ok(()) diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index 1cfe143e..ed1066cb 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -64,12 +64,14 @@ impl WindowManager { _ => {} } + let invisible_borders = self.invisible_borders; + for (i, monitor) in self.monitors_mut().iter_mut().enumerate() { let work_area = *monitor.work_area_size(); for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() { let reaped_orphans = workspace.reap_orphans()?; if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 { - workspace.update(&work_area)?; + workspace.update(&work_area, &invisible_borders)?; tracing::info!( "reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}", reaped_orphans.0, @@ -218,19 +220,11 @@ impl WindowManager { .ok_or_else(|| anyhow!("there is no latest layout"))?; let mut new_position = WindowsApi::window_rect(window.hwnd())?; - // See Window.set_position() in window.rs for comments - let border = Rect { - left: 12, - top: 0, - right: 24, - bottom: 12, - }; - - // Adjust for the invisible border - new_position.left += border.left; - new_position.top += border.top; - new_position.right -= border.right; - new_position.bottom -= border.bottom; + // Adjust for the invisible borders + new_position.left += invisible_borders.left; + new_position.top += invisible_borders.top; + new_position.right -= invisible_borders.right; + new_position.bottom -= invisible_borders.bottom; let resize = Rect { left: new_position.left - old_position.left, @@ -295,7 +289,7 @@ impl WindowManager { // 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()?)?; + window.center(&self.focused_monitor_work_area()?, &invisible_borders)?; } tracing::trace!("updating list of known hwnds"); diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 253fd501..c77ecfa9 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -71,7 +71,7 @@ impl Window { HWND(self.hwnd) } - pub fn center(&mut self, work_area: &Rect) -> Result<()> { + pub fn center(&mut self, work_area: &Rect, invisible_borders: &Rect) -> Result<()> { let half_width = work_area.right / 2; let half_weight = work_area.bottom / 2; @@ -82,33 +82,18 @@ impl Window { right: half_width, bottom: half_weight, }, + invisible_borders, true, ) } - pub fn set_position(&mut self, layout: &Rect, top: bool) -> Result<()> { - // NOTE: This is how the border variable below was calculated; every time this code was - // run on any window in any position, the generated border was always the same, so I am - // hard coding the border Rect to avoid two calls to set_window_pos and making the screen - // flicker on container/window movement. Still not 100% sure if this is DPI-aware. - - // Set the new position first to be able to get the extended frame bounds - // WindowsApi::set_window_pos(self.hwnd(), layout, false, false)?; - // let mut rect = WindowsApi::window_rect(self.hwnd())?; - - // Get the extended frame bounds of the new position - // let frame = WindowsApi::window_rect_with_extended_frame_bounds(self.hwnd())?; - - // Calculate the invisible border diff - // let border = Rect { - // left: frame.left - rect.left, - // top: frame.top - rect.top, - // right: rect.right - frame.right, - // bottom: rect.bottom - frame.bottom, - // }; - + pub fn set_position( + &mut self, + layout: &Rect, + invisible_borders: &Rect, + top: bool, + ) -> Result<()> { let mut rect = *layout; - let mut should_remove_border = true; let border_overflows = BORDER_OVERFLOW_IDENTIFIERS.lock(); @@ -120,18 +105,11 @@ impl Window { } if should_remove_border { - let border = Rect { - left: 12, - top: 0, - right: 24, - bottom: 12, - }; - - // Remove the invisible border - rect.left -= border.left; - rect.top -= border.top; - rect.right += border.right; - rect.bottom += border.bottom; + // Remove the invisible borders + rect.left -= invisible_borders.left; + rect.top -= invisible_borders.top; + rect.right += invisible_borders.right; + rect.bottom += invisible_borders.bottom; } WindowsApi::position_window(self.hwnd(), &rect, top) diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 43289152..823ad2eb 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -45,6 +45,7 @@ pub struct WindowManager { pub incoming_events: Arc>>, pub command_listener: UnixListener, pub is_paused: bool, + pub invisible_borders: Rect, pub focus_follows_mouse: Option, pub hotwatch: Hotwatch, pub virtual_desktop_id: Option, @@ -55,6 +56,7 @@ pub struct WindowManager { pub struct State { pub monitors: Ring, pub is_paused: bool, + pub invisible_borders: Rect, pub focus_follows_mouse: Option, pub has_pending_raise_op: bool, pub float_identifiers: Vec, @@ -70,6 +72,7 @@ impl From<&mut WindowManager> for State { Self { monitors: wm.monitors.clone(), is_paused: wm.is_paused, + invisible_borders: wm.invisible_borders, focus_follows_mouse: wm.focus_follows_mouse.clone(), has_pending_raise_op: wm.has_pending_raise_op, float_identifiers: FLOAT_IDENTIFIERS.lock().clone(), @@ -135,6 +138,12 @@ impl WindowManager { incoming_events: incoming, command_listener: listener, is_paused: false, + invisible_borders: Rect { + left: 7, + top: 0, + right: 14, + bottom: 7, + }, focus_follows_mouse: None, hotwatch: Hotwatch::new()?, virtual_desktop_id, @@ -379,6 +388,26 @@ impl WindowManager { Ok(()) } + #[tracing::instrument(skip(self))] + pub fn retile_all(&mut self) -> Result<()> { + let invisible_borders = self.invisible_borders; + for monitor in self.monitors_mut() { + let work_area = *monitor.work_area_size(); + let workspace = monitor + .focused_workspace_mut() + .ok_or_else(|| anyhow!("there is no workspace"))?; + + // Reset any resize adjustments if we want to force a retile + for resize in workspace.resize_dimensions_mut() { + *resize = None; + } + + workspace.update(&work_area, &invisible_borders)?; + } + + Ok(()) + } + #[tracing::instrument(skip(self))] pub fn validate_virtual_desktop_id(&self) { let virtual_desktop_id = winvd::helpers::get_current_desktop_number().ok(); @@ -472,9 +501,11 @@ impl WindowManager { pub fn update_focused_workspace(&mut self, mouse_follows_focus: bool) -> Result<()> { tracing::info!("updating"); + let invisible_borders = self.invisible_borders; + self.focused_monitor_mut() .ok_or_else(|| anyhow!("there is no monitor"))? - .update_focused_workspace()?; + .update_focused_workspace(&invisible_borders)?; if mouse_follows_focus { if let Some(window) = self.focused_workspace()?.maximized_window() { @@ -781,6 +812,7 @@ impl WindowManager { tracing::info!("floating window"); let work_area = self.focused_monitor_work_area()?; + let invisible_borders = self.invisible_borders; let workspace = self.focused_workspace_mut()?; workspace.new_floating_window()?; @@ -790,7 +822,7 @@ impl WindowManager { .last_mut() .ok_or_else(|| anyhow!("there is no floating window"))?; - window.center(&work_area)?; + window.center(&work_area, &invisible_borders)?; window.focus()?; Ok(()) @@ -973,6 +1005,7 @@ impl WindowManager { ) -> Result<()> { tracing::info!("setting workspace layout"); + let invisible_borders = self.invisible_borders; let focused_monitor_idx = self.focused_monitor_idx(); let monitor = self @@ -992,7 +1025,7 @@ impl WindowManager { // If this is the focused workspace on a non-focused screen, let's update it if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx { - workspace.update(&work_area)?; + workspace.update(&work_area, &invisible_borders)?; Ok(()) } else { Ok(self.update_focused_workspace(false)?) diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index b23f3cd0..6149bb44 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -139,7 +139,7 @@ impl Workspace { Ok(()) } - pub fn update(&mut self, work_area: &Rect) -> Result<()> { + pub fn update(&mut self, work_area: &Rect, invisible_borders: &Rect) -> Result<()> { let mut adjusted_work_area = *work_area; adjusted_work_area.add_padding(self.workspace_padding()); @@ -148,7 +148,7 @@ impl Workspace { if *self.tile() { if let Some(container) = self.monocle_container_mut() { if let Some(window) = container.focused_window_mut() { - window.set_position(&adjusted_work_area, true)?; + window.set_position(&adjusted_work_area, invisible_borders, true)?; }; } else if let Some(window) = self.maximized_window_mut() { window.maximize(); @@ -166,7 +166,7 @@ impl Workspace { let windows = self.visible_windows_mut(); for (i, window) in windows.into_iter().enumerate() { if let (Some(window), Some(layout)) = (window, layouts.get(i)) { - window.set_position(layout, false)?; + window.set_position(layout, invisible_borders, false)?; } } diff --git a/komorebic.lib.sample.ahk b/komorebic.lib.sample.ahk index b351166f..39f751f4 100644 --- a/komorebic.lib.sample.ahk +++ b/komorebic.lib.sample.ahk @@ -72,6 +72,10 @@ NewWorkspace() { Run, komorebic.exe new-workspace, , Hide } +InvisibleBorders(left, top, right, bottom) { + Run, komorebic.exe invisible-borders %left% %top% %right% %bottom%, , Hide +} + AdjustContainerPadding(sizing, adjustment) { Run, komorebic.exe adjust-container-padding %sizing% %adjustment%, , Hide } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index cf6a4809..889e5e58 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -33,6 +33,7 @@ use komorebi_core::Flip; use komorebi_core::FocusFollowsMouseImplementation; use komorebi_core::Layout; use komorebi_core::OperationDirection; +use komorebi_core::Rect; use komorebi_core::Sizing; use komorebi_core::SocketMessage; use komorebi_core::StateQuery; @@ -150,6 +151,18 @@ struct Resize { sizing: Sizing, } +#[derive(Clap, AhkFunction)] +struct InvisibleBorders { + /// Size of the left invisible border + left: i32, + /// Size of the top invisible border (usually 0) + top: i32, + /// Size of the right invisible border (usually left * 2) + right: i32, + /// Size of the bottom invisible border (usually the same as left) + bottom: i32, +} + #[derive(Clap, AhkFunction)] struct EnsureWorkspaces { /// Monitor index (zero-indexed) @@ -305,6 +318,9 @@ enum SubCommand { FocusWorkspace(FocusWorkspace), /// Create and append a new workspace on the focused monitor NewWorkspace, + /// Set the invisible border dimensions around each window + #[clap(setting = AppSettings::ArgRequiredElseHelp)] + InvisibleBorders(InvisibleBorders), /// Adjust container padding on the focused workspace #[clap(setting = AppSettings::ArgRequiredElseHelp)] AdjustContainerPadding(AdjustContainerPadding), @@ -459,6 +475,17 @@ fn main() -> Result<()> { SubCommand::SendToWorkspace(arg) => { send_message(&*SocketMessage::SendContainerToWorkspaceNumber(arg.target).as_bytes()?)?; } + SubCommand::InvisibleBorders(arg) => { + send_message( + &*SocketMessage::InvisibleBorders(Rect { + left: arg.left, + top: arg.top, + right: arg.right, + bottom: arg.bottom, + }) + .as_bytes()?, + )?; + } SubCommand::ContainerPadding(arg) => { send_message( &*SocketMessage::ContainerPadding(arg.monitor, arg.workspace, arg.size)