Skip to content

Commit

Permalink
feat(wm): add active window border
Browse files Browse the repository at this point in the history
This commit adds an optional active window border with a user-defined
colour. This is achieved by spawning a dedicated "border window" and
constantly placing it behind the focused window, or hiding it whenever
necessary.

Some constraints to note:

- The border will only be applied to windows managed by komorebi
- This means that if you temporarily float a window, it will lose the
  active window border
- There are some issues where parts of the border will be broken by
  applications like Zoom, even if Zoom is behind the currently focused
  window
- You probably want to turn off window shadows globally in Advanced
  System Settings -> Performance for the borders to have a consistent
  colour all the way around the window
- There is some inevitable jank due to trying to reposition both the
  focused window and the "border window" behind it simultaneously
- There are no borders for unfocused windows

resolve #182
  • Loading branch information
LGUG2Z committed Aug 8, 2022
1 parent 8c051d9 commit 07bd538
Show file tree
Hide file tree
Showing 16 changed files with 550 additions and 55 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ members = [
"derive-ahk",
"komorebi",
"komorebi-core",
"komorebic"
"komorebic",
]
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,17 @@ If you already have configuration files that you wish to keep, move them to the
The next time you run `komorebic start`, any files created by or loaded by _komorebi_ will be placed or expected to
exist in this folder.

#### Adding an Active Window Border

If you would like to add a visual border around the currently focused window, two commands are available:

```powershell
komorebic.exe active-window-border [enable|disable]
komorebic.exe active-window-border-colour [R G B]
```

It is important to note that the active window border will only apply to windows managed by `komorebi`.

#### Removing Gaps

If you would like to remove all gaps from a given workspace, both between windows themselves, and between the monitor edges and the windows, you can set the following two configuration options to `0` for the desired monitors and workspaces:
Expand Down Expand Up @@ -532,6 +543,7 @@ manage Force komorebi to manage the focused
unmanage Unmanage a window that was forcibly managed
reload-configuration Reload ~/komorebi.ahk (if it exists)
watch-configuration Enable or disable watching of ~/komorebi.ahk (if it exists)
complete-configuration Signal that the final configuration option has been sent
window-hiding-behaviour Set the window behaviour when switching workspaces / cycling stacks
cross-monitor-move-behaviour Set the behaviour when moving windows across monitor boundaries
toggle-cross-monitor-move-behaviour Toggle the behaviour when moving windows across monitor boundaries
Expand All @@ -543,6 +555,8 @@ identify-object-name-change-application Identify an application that sends EV
identify-tray-application Identify an application that closes to the system tray
identify-layered-application Identify an application that has WS_EX_LAYERED, but should still be managed
identify-border-overflow-application Identify an application that has overflowing borders
active-window-border Enable or disable the active window border
active-window-border-colour Set the colour for the active window border
focus-follows-mouse Enable or disable focus follows mouse for the operating system
toggle-focus-follows-mouse Toggle focus follows mouse for the operating system
mouse-follows-focus Enable or disable mouse follows focus on all workspaces
Expand Down
10 changes: 6 additions & 4 deletions komorebi-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ pub enum SocketMessage {
ReloadConfiguration,
WatchConfiguration(bool),
CompleteConfiguration,
ActiveWindowBorder(bool),
ActiveWindowBorderColour(u32, u32, u32),
InvisibleBorders(Rect),
WorkAreaOffset(Rect),
ResizeDelta(i32),
Expand Down Expand Up @@ -132,7 +134,7 @@ impl FromStr for SocketMessage {
}
}

#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum StateQuery {
FocusedMonitorIndex,
Expand All @@ -141,7 +143,7 @@ pub enum StateQuery {
FocusedWindowIndex,
}

#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum ApplicationIdentifier {
Expand All @@ -150,7 +152,7 @@ pub enum ApplicationIdentifier {
Title,
}

#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum FocusFollowsMouseImplementation {
Komorebi,
Expand All @@ -171,7 +173,7 @@ pub enum MoveBehaviour {
Insert,
}

#[derive(Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)]
#[strum(serialize_all = "snake_case")]
pub enum HidingBehaviour {
Hide,
Expand Down
12 changes: 8 additions & 4 deletions komorebi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ dirs = "4"
getset = "0.1"
hotwatch = "0.4"
lazy_static = "1"
miow = "0.4"
nanoid = "0.4"
os_info = "3.4"
parking_lot = { version = "0.12", features = ["deadlock_detection"] }
paste = "1"
schemars = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
strum = { version = "0.24", features = ["derive"] }
Expand All @@ -36,22 +39,23 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
uds_windows = "1"
which = "4"
winput = "0.2"
miow = "0.4"
winreg = "0.10"
schemars = "0.8"

[dependencies.windows]
version = "0.39"
features = [
"Win32_Foundation",
"Win32_Graphics_Dwm",
"Win32_Graphics_Gdi",
"Win32_System_Threading",
"Win32_System_LibraryLoader",
"Win32_System_RemoteDesktop",
"Win32_System_Threading",
"Win32_UI_Accessibility",
"Win32_UI_HiDpi",
"Win32_UI_Input_KeyboardAndMouse",
"Win32_UI_WindowsAndMessaging",
"Win32_UI_Shell",
"Win32_UI_Shell_Common",
"Win32_UI_WindowsAndMessaging"
]

[features]
Expand Down
119 changes: 119 additions & 0 deletions komorebi/src/border.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use std::sync::atomic::Ordering;
use std::time::Duration;

use color_eyre::Result;
use komorebi_core::Rect;
use windows::core::PCSTR;
use windows::Win32::Foundation::HWND;
use windows::Win32::UI::WindowsAndMessaging::DispatchMessageA;
use windows::Win32::UI::WindowsAndMessaging::FindWindowA;
use windows::Win32::UI::WindowsAndMessaging::GetMessageA;
use windows::Win32::UI::WindowsAndMessaging::CS_HREDRAW;
use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW;
use windows::Win32::UI::WindowsAndMessaging::MSG;
use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA;

use crate::window::Window;
use crate::windows_callbacks;
use crate::WindowsApi;
use crate::BORDER_HWND;
use crate::BORDER_OVERFLOW_IDENTIFIERS;
use crate::WINDOWS_11;

#[derive(Debug, Clone, Copy)]
pub struct Border {
pub(crate) hwnd: isize,
}

impl From<isize> for Border {
fn from(hwnd: isize) -> Self {
Self { hwnd }
}
}

impl Border {
pub const fn hwnd(self) -> HWND {
HWND(self.hwnd)
}

pub fn create(name: &str) -> Result<()> {
let name = format!("{name}\0");
let instance = WindowsApi::module_handle_w()?;
let class_name = PCSTR(name.as_ptr());
let brush = WindowsApi::create_solid_brush(255, 140, 0);
let window_class = WNDCLASSA {
hInstance: instance,
lpszClassName: class_name,
style: CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(windows_callbacks::border_window),
hbrBackground: brush,
..Default::default()
};

let _atom = WindowsApi::register_class_a(&window_class)?;

let name_cl = name.clone();
std::thread::spawn(move || -> Result<()> {
let hwnd = WindowsApi::create_border_window(PCSTR(name_cl.as_ptr()), instance)?;
let border = Self::from(hwnd);

let mut message = MSG::default();

unsafe {
while GetMessageA(&mut message, border.hwnd(), 0, 0).into() {
DispatchMessageA(&message);
std::thread::sleep(Duration::from_millis(10));
}
}

Ok(())
});

let mut hwnd = HWND(0);
while hwnd == HWND(0) {
hwnd = unsafe { FindWindowA(PCSTR(name.as_ptr()), PCSTR::null()) };
}

BORDER_HWND.store(hwnd.0, Ordering::SeqCst);

if *WINDOWS_11 {
WindowsApi::round_corners(hwnd.0)?;
}

Ok(())
}

pub fn hide(self) -> Result<()> {
WindowsApi::hide_border_window(self.hwnd())
}

pub fn set_position(
self,
window: Window,
invisible_borders: &Rect,
activate: bool,
) -> Result<()> {
let mut should_expand_border = false;

let mut rect = WindowsApi::window_rect(window.hwnd())?;
rect.top -= invisible_borders.bottom;
rect.bottom += invisible_borders.bottom;

let border_overflows = BORDER_OVERFLOW_IDENTIFIERS.lock();
if border_overflows.contains(&window.title()?)
|| border_overflows.contains(&window.exe()?)
|| border_overflows.contains(&window.class()?)
{
should_expand_border = true;
}

if should_expand_border {
rect.left -= invisible_borders.left;
rect.top -= invisible_borders.top;
rect.right += invisible_borders.right;
rect.bottom += invisible_borders.bottom;
}

WindowsApi::position_border_window(self.hwnd(), &rect, activate)
}
}
14 changes: 14 additions & 0 deletions komorebi/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::AtomicIsize;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use std::sync::Arc;
Expand All @@ -20,6 +21,7 @@ use crossbeam_channel::Receiver;
use crossbeam_channel::Sender;
use crossbeam_utils::Backoff;
use lazy_static::lazy_static;
use os_info::Version;
#[cfg(feature = "deadlock_detection")]
use parking_lot::deadlock;
use parking_lot::Mutex;
Expand All @@ -35,6 +37,7 @@ use which::which;
use winreg::enums::HKEY_CURRENT_USER;
use winreg::RegKey;

use crate::border::Border;
use komorebi_core::HidingBehaviour;
use komorebi_core::SocketMessage;

Expand All @@ -49,6 +52,7 @@ use crate::windows_api::WindowsApi;
#[macro_use]
mod ring;

mod border;
mod container;
mod monitor;
mod process_command;
Expand Down Expand Up @@ -140,11 +144,20 @@ lazy_static! {

ahk_v2
};

static ref WINDOWS_11: bool = {
matches!(
os_info::get().version(),
Version::Semantic(_, _, x) if x >= &22000
)
};
}

pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false);
pub static CUSTOM_FFM: AtomicBool = AtomicBool::new(false);
pub static SESSION_ID: AtomicU32 = AtomicU32::new(0);
pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0);
pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false);

fn setup() -> Result<(WorkerGuard, WorkerGuard)> {
if std::env::var("RUST_LIB_BACKTRACE").is_err() {
Expand Down Expand Up @@ -415,6 +428,7 @@ fn main() -> Result<()> {
let process_id = WindowsApi::current_process_id();
WindowsApi::allow_set_foreground_window(process_id)?;
WindowsApi::set_process_dpi_awareness_context()?;
Border::create("komorebi-border-window")?;

let (outgoing, incoming): (Sender<WindowManagerEvent>, Receiver<WindowManagerEvent>) =
crossbeam_channel::unbounded();
Expand Down
Loading

0 comments on commit 07bd538

Please sign in to comment.