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
This commit is contained in:
LGUG2Z
2021-09-14 21:16:50 -07:00
parent ff53533da0
commit f1ee5ea194
15 changed files with 121 additions and 86 deletions

View File

@@ -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

20
Cargo.lock generated
View File

@@ -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",

View File

@@ -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

View File

@@ -62,6 +62,7 @@ pub enum SocketMessage {
// Configuration
ReloadConfiguration,
WatchConfiguration(bool),
InvisibleBorders(Rect),
WorkspaceRule(ApplicationIdentifier, String, usize, usize),
FloatRule(ApplicationIdentifier, String),
ManageRule(ApplicationIdentifier, String),

View File

@@ -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,

View File

@@ -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

View File

@@ -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")

View File

@@ -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(())
}

View File

@@ -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(())

View File

@@ -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");

View File

@@ -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)

View File

@@ -45,6 +45,7 @@ pub struct WindowManager {
pub incoming_events: Arc<Mutex<Receiver<WindowManagerEvent>>>,
pub command_listener: UnixListener,
pub is_paused: bool,
pub invisible_borders: Rect,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub hotwatch: Hotwatch,
pub virtual_desktop_id: Option<usize>,
@@ -55,6 +56,7 @@ pub struct WindowManager {
pub struct State {
pub monitors: Ring<Monitor>,
pub is_paused: bool,
pub invisible_borders: Rect,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub has_pending_raise_op: bool,
pub float_identifiers: Vec<String>,
@@ -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)?)

View File

@@ -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)?;
}
}

View File

@@ -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
}

View File

@@ -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)