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)