From e466a17877212429207fa53caf88731da4a5d80c Mon Sep 17 00:00:00 2001 From: LGUG2Z Date: Tue, 9 Aug 2022 20:55:21 -0700 Subject: [PATCH] feat(wm): restrict border window to outline This commit moves the border window drawing logic into the WNDPROC callback and uses BeginPaint -> Rectangle -> Endpaint to draw a rectangle around the outside of the window in a specific colour that is not black, which is used as the transparency colour with SetLayeredWindowAttributes. All of this results in a non-filled border rect and a much nicer experience for users who are using transparency or translucent effects on their windows. This commit also introduces an optional second active border colour when the user is focused on a stack of windows. If this is not set, the default colour for single windows will be used. Finally, a bunch of small issues relating to the border window staying drawn on the screen even when there are no active windows on a workspace have been addressed. resolve #201 --- README.md | 101 +++--------------------------- komorebi-core/src/lib.rs | 9 ++- komorebi.generated.ahk | 6 ++ komorebi/src/border.rs | 9 ++- komorebi/src/main.rs | 9 +++ komorebi/src/process_command.rs | 21 ++++++- komorebi/src/process_event.rs | 54 ++++++++++++---- komorebi/src/window_manager.rs | 5 +- komorebi/src/windows_api.rs | 45 ++++++++----- komorebi/src/windows_callbacks.rs | 26 +++++++- komorebic.lib.ahk | 6 +- komorebic/src/main.rs | 6 +- 12 files changed, 163 insertions(+), 134 deletions(-) diff --git a/README.md b/README.md index 9c9d443d..3f008291 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,10 @@ If you would like to add a visual border around the currently focused window, tw ```powershell komorebic.exe active-window-border [enable|disable] -komorebic.exe active-window-border-colour [R G B] +komorebic.exe active-window-border-colour [R G B] --window-kind single + +# optionally, if you want a different colour for stacks of windows +komorebic.exe active-window-border-colour [R G B] --window-kind stack ``` It is important to note that the active window border will only apply to windows managed by `komorebi`. @@ -484,95 +487,6 @@ You can run `komorebic.exe` to get a full list of the commands that you can use keybindings with. You can run `komorebic.exe --help` to get a full explanation of the arguments required for each command. -``` -start Start komorebi.exe as a background process -stop Stop the komorebi.exe process and restore all hidden windows -state Show a JSON representation of the current window manager state -query Query the current window manager state -subscribe Subscribe to komorebi events -unsubscribe Unsubscribe from komorebi events -log Tail komorebi.exe's process logs (cancel with Ctrl-C) -quick-save-resize Quicksave the current resize layout dimensions -quick-load-resize Load the last quicksaved resize layout dimensions -save-resize Save the current resize layout dimensions to a file -load-resize Load the resize layout dimensions from a file -focus Change focus to the window in the specified direction -move Move the focused window in the specified direction -cycle-focus Change focus to the window in the specified cycle direction -cycle-move Move the focused window in the specified cycle direction -stack Stack the focused window in the specified direction -resize-edge Resize the focused window in the specified direction -resize-axis Resize the focused window or primary column along the specified axis -unstack Unstack the focused window -cycle-stack Cycle the focused stack in the specified cycle direction -move-to-monitor Move the focused window to the specified monitor -move-to-workspace Move the focused window to the specified workspace -send-to-monitor Send the focused window to the specified monitor -send-to-workspace Send the focused window to the specified workspace -send-to-monitor-workspace Send the focused window to the specified monitor workspace -focus-monitor Focus the specified monitor -focus-workspace Focus the specified workspace on the focused monitor -focus-monitor-workspace Focus the specified workspace on the target monitor -cycle-monitor Focus the monitor in the given cycle direction -cycle-workspace Focus the workspace in the given cycle direction -move-workspace-to-monitor Move the focused workspace to the specified monitor -new-workspace Create and append a new workspace on the focused monitor -resize-delta Set the resize delta (used by resize-edge and resize-axis) -invisible-borders Set the invisible border dimensions around each window -work-area-offset Set offsets to exclude parts of the work area from tiling -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 -load-custom-layout Load a custom layout from file for the focused workspace -flip-layout Flip the layout on the focused workspace (BSP only) -promote Promote the focused window to the top of the tree -retile Force the retiling of all managed windows -ensure-workspaces Create at least this many workspaces for the specified monitor -container-padding Set the container padding for the specified workspace -workspace-padding Set the workspace padding for the specified workspace -workspace-layout Set the layout for the specified workspace -workspace-custom-layout Set a custom layout for the specified workspace -workspace-layout-rule Add a dynamic layout rule for the specified workspace -workspace-custom-layout-rule Add a dynamic custom layout for the specified workspace -clear-workspace-layout-rules Clear all dynamic layout rules for the specified workspace -workspace-tiling Enable or disable window tiling for the specified workspace -workspace-name Set the workspace name for the specified workspace -toggle-window-container-behaviour Toggle the behaviour for new windows (stacking or dynamic tiling) -toggle-pause Toggle window tiling on the focused workspace -toggle-tiling Toggle window tiling on the focused workspace -toggle-float Toggle floating mode for the focused window -toggle-monocle Toggle monocle mode for the focused container -toggle-maximize Toggle native maximization for the focused window -restore-windows Restore all hidden windows (debugging command) -manage Force komorebi to manage the focused window -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 -unmanaged-window-operation-behaviour Set the operation behaviour when the focused window is not managed -float-rule Add a rule to always float the specified application -manage-rule Add a rule to always manage the specified application -workspace-rule Add a rule to associate an application with a workspace -identify-object-name-change-application Identify an application that sends EVENT_OBJECT_NAMECHANGE on launch -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 -toggle-mouse-follows-focus Toggle mouse follows focus on all workspaces -ahk-library Generate a library of AutoHotKey helper functions -ahk-app-specific-configuration Generate common app-specific configurations and fixes to use in komorebi.ahk -format-app-specific-configuration Format a YAML file for use with the 'ahk-app-specific-configuration' command -notification-schema Generate a JSON Schema of subscription notifications -help Print this message or the help of the given subcommand(s) -``` - ### AutoHotKey Helper Library for `komorebic` Additionally, you may run `komorebic.exe ahk-library` to @@ -602,7 +516,7 @@ used [is available here](komorebi.sample.with.lib.ahk). - [x] Resize window container in direction - [x] Resize window container on axis - [x] Set custom resize delta -- [ ] Resize child window containers by split ratio +- [x] Active window border - [x] Quicksave and quickload layouts with resize dimensions - [x] Save and load layouts with resize dimensions to/from specific files - [x] Mouse drag to swap window container position @@ -623,7 +537,7 @@ used [is available here](komorebi.sample.with.lib.ahk). - [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 work area offsets to preserve space for custom taskbars -- [x] Configure and compensate for the size of Windows 10's invisible borders +- [x] Configure and compensate for the size of Windows invisible borders - [x] Toggle floating windows - [x] Toggle monocle window - [x] Toggle native maximization @@ -733,6 +647,9 @@ An example of how to create a named pipe and a subscription to `komorebi`'s hand by [@denBot](https://github.com/denBot) can be found [here](https://gist.github.com/denBot/4136279812f87819f86d99eba77c1ee0). +An example of how to create a named pipe and a subscription to `komorebi`'s handled events in Rust can also be found +in the [`komokana`](https://github.com/LGUG2Z/komokana) repository. + ### Subscription Event Notification Schema A [JSON Schema](https://json-schema.org/) of the event notifications emitted to subscribers can be generated with diff --git a/komorebi-core/src/lib.rs b/komorebi-core/src/lib.rs index 2d84eecb..28189c12 100644 --- a/komorebi-core/src/lib.rs +++ b/komorebi-core/src/lib.rs @@ -98,7 +98,7 @@ pub enum SocketMessage { WatchConfiguration(bool), CompleteConfiguration, ActiveWindowBorder(bool), - ActiveWindowBorderColour(u32, u32, u32), + ActiveWindowBorderColour(WindowKind, u32, u32, u32), InvisibleBorders(Rect), WorkAreaOffset(Rect), ResizeDelta(i32), @@ -134,6 +134,13 @@ impl FromStr for SocketMessage { } } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] +#[strum(serialize_all = "snake_case")] +pub enum WindowKind { + Single, + Stack, +} + #[derive(Copy, Clone, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum, JsonSchema)] #[strum(serialize_all = "snake_case")] pub enum StateQuery { diff --git a/komorebi.generated.ahk b/komorebi.generated.ahk index 09e2361b..d81e88b5 100644 --- a/komorebi.generated.ahk +++ b/komorebi.generated.ahk @@ -250,6 +250,9 @@ Run, komorebic.exe float-rule exe "RepoZ.exe", , Hide ; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line Run, komorebic.exe identify-tray-application exe "rbxfpsunlocker.exe", , Hide +; RoundedTB +Run, komorebic.exe float-rule exe "RoundedTB.exe", , Hide + ; RoundedTB Run, komorebic.exe identify-border-overflow-application exe "RoundedTB.exe", , Hide ; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line @@ -301,6 +304,9 @@ Run, komorebic.exe identify-tray-application exe "Telegram.exe", , Hide Run, komorebic.exe identify-tray-application exe "tcconfig.exe", , Hide Run, komorebic.exe float-rule exe "tcconfig.exe", , Hide +; TranslucentTB +Run, komorebic.exe float-rule exe "TranslucentTB.exe", , Hide + ; TranslucentTB ; If you have disabled minimize/close to tray for this application, you can delete/comment out the next line Run, komorebic.exe identify-tray-application exe "TranslucentTB.exe", , Hide diff --git a/komorebi/src/border.rs b/komorebi/src/border.rs index 18f910ef..b190fa39 100644 --- a/komorebi/src/border.rs +++ b/komorebi/src/border.rs @@ -2,7 +2,6 @@ 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; @@ -13,11 +12,15 @@ use windows::Win32::UI::WindowsAndMessaging::CS_VREDRAW; use windows::Win32::UI::WindowsAndMessaging::MSG; use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA; +use komorebi_core::Rect; + use crate::window::Window; use crate::windows_callbacks; use crate::WindowsApi; use crate::BORDER_HWND; use crate::BORDER_OVERFLOW_IDENTIFIERS; +use crate::BORDER_RECT; +use crate::TRANSPARENCY_COLOUR; use crate::WINDOWS_11; #[derive(Debug, Clone, Copy)] @@ -40,7 +43,7 @@ impl Border { 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 brush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR); let window_class = WNDCLASSA { hInstance: instance, lpszClassName: class_name, @@ -114,6 +117,8 @@ impl Border { rect.bottom += invisible_borders.bottom; } + *BORDER_RECT.lock() = rect; + WindowsApi::position_border_window(self.hwnd(), &rect, activate) } } diff --git a/komorebi/src/main.rs b/komorebi/src/main.rs index 105b415b..101bbcae 100644 --- a/komorebi/src/main.rs +++ b/komorebi/src/main.rs @@ -38,6 +38,7 @@ use winreg::enums::HKEY_CURRENT_USER; use winreg::RegKey; use komorebi_core::HidingBehaviour; +use komorebi_core::Rect; use komorebi_core::SocketMessage; use crate::border::Border; @@ -151,6 +152,9 @@ lazy_static! { Version::Semantic(_, _, x) if x >= &22000 ) }; + + static ref BORDER_RECT: Arc> = + Arc::new(Mutex::new(Rect::default())); } pub static INITIAL_CONFIGURATION_LOADED: AtomicBool = AtomicBool::new(false); @@ -159,6 +163,11 @@ pub static SESSION_ID: AtomicU32 = AtomicU32::new(0); pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(false); pub static BORDER_HWND: AtomicIsize = AtomicIsize::new(0); pub static BORDER_HIDDEN: AtomicBool = AtomicBool::new(false); +pub static BORDER_COLOUR_SINGLE: AtomicU32 = AtomicU32::new(0); +pub static BORDER_COLOUR_STACK: AtomicU32 = AtomicU32::new(0); +pub static BORDER_COLOUR_CURRENT: AtomicU32 = AtomicU32::new(0); +// 0 0 0 aka pure black, I doubt anyone will want this as a border colour +pub const TRANSPARENCY_COLOUR: u32 = 0; fn setup() -> Result<(WorkerGuard, WorkerGuard)> { if std::env::var("RUST_LIB_BACKTRACE").is_err() { diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index abb6f528..08bd06ef 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -26,6 +26,7 @@ use komorebi_core::Sizing; use komorebi_core::SocketMessage; use komorebi_core::StateQuery; use komorebi_core::WindowContainerBehaviour; +use komorebi_core::WindowKind; use crate::border::Border; use crate::current_virtual_desktop; @@ -36,6 +37,9 @@ use crate::window_manager::WindowManager; use crate::windows_api::WindowsApi; use crate::Notification; use crate::NotificationEvent; +use crate::BORDER_COLOUR_CURRENT; +use crate::BORDER_COLOUR_SINGLE; +use crate::BORDER_COLOUR_STACK; use crate::BORDER_ENABLED; use crate::BORDER_HWND; use crate::BORDER_OVERFLOW_IDENTIFIERS; @@ -758,9 +762,18 @@ impl WindowManager { self.hide_border()?; } } - SocketMessage::ActiveWindowBorderColour(r, g, b) => { - let hwnd = BORDER_HWND.load(Ordering::SeqCst); - WindowsApi::change_border_colour(hwnd, r, g, b)?; + SocketMessage::ActiveWindowBorderColour(kind, r, g, b) => { + match kind { + WindowKind::Single => { + BORDER_COLOUR_SINGLE.store(r | (g << 8) | (b << 16), Ordering::SeqCst); + BORDER_COLOUR_CURRENT.store(r | (g << 8) | (b << 16), Ordering::SeqCst); + } + WindowKind::Stack => { + BORDER_COLOUR_STACK.store(r | (g << 8) | (b << 16), Ordering::SeqCst); + } + } + + WindowsApi::invalidate_border_rect()?; } SocketMessage::NotificationSchema => { let notification = schema_for!(Notification); @@ -789,6 +802,8 @@ impl WindowManager { | SocketMessage::ToggleMaximize | SocketMessage::Promote | SocketMessage::Retile + | SocketMessage::InvisibleBorders(_) + | SocketMessage::WorkAreaOffset(_) | SocketMessage::MoveWindow(_) => { let foreground = WindowsApi::foreground_window()?; let foreground_window = Window { hwnd: foreground }; diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index fbebe4e4..8f23f615 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -20,6 +20,9 @@ use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::Notification; use crate::NotificationEvent; +use crate::BORDER_COLOUR_CURRENT; +use crate::BORDER_COLOUR_SINGLE; +use crate::BORDER_COLOUR_STACK; use crate::BORDER_ENABLED; use crate::BORDER_HIDDEN; use crate::BORDER_HWND; @@ -483,19 +486,48 @@ impl WindowManager { } WindowManagerEvent::MoveResizeEnd(_, _) | WindowManagerEvent::Show(_, _) - | WindowManagerEvent::FocusChange(_, _) => { - let window = self.focused_window()?; - let mut rect = WindowsApi::window_rect(window.hwnd())?; - rect.top -= self.invisible_borders.bottom; - rect.bottom += self.invisible_borders.bottom; - - let activate = BORDER_HIDDEN.load(Ordering::SeqCst); - + | WindowManagerEvent::FocusChange(_, _) + | WindowManagerEvent::Minimize(_, _) => { let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - border.set_position(*window, &self.invisible_borders, activate)?; - if activate { - BORDER_HIDDEN.store(false, Ordering::SeqCst); + match self.focused_container() { + // if there is no focused container, the desktop is empty + Err(..) => { + WindowsApi::hide_border_window(border.hwnd())?; + } + Ok(container) => { + if !(matches!(event, WindowManagerEvent::Minimize(_, _)) + && container.windows().len() == 1) + { + let container_size = self.focused_container()?.windows().len(); + + let window = self.focused_window()?; + let mut rect = WindowsApi::window_rect(window.hwnd())?; + rect.top -= self.invisible_borders.bottom; + rect.bottom += self.invisible_borders.bottom; + + let activate = BORDER_HIDDEN.load(Ordering::SeqCst); + + if container_size > 1 { + BORDER_COLOUR_CURRENT.store( + BORDER_COLOUR_STACK.load(Ordering::SeqCst), + Ordering::SeqCst, + ); + } else { + BORDER_COLOUR_CURRENT.store( + BORDER_COLOUR_SINGLE.load(Ordering::SeqCst), + Ordering::SeqCst, + ); + } + + WindowsApi::invalidate_border_rect()?; + border.set_position(*window, &self.invisible_borders, activate)?; + + if activate { + BORDER_HIDDEN.store(false, Ordering::SeqCst); + } + } + } } } _ => {} diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 706c50c4..4c7f1533 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -15,7 +15,6 @@ use schemars::JsonSchema; use serde::Serialize; use uds_windows::UnixListener; -use crate::border::Border; use komorebi_core::custom_layout::CustomLayout; use komorebi_core::Arrangement; use komorebi_core::Axis; @@ -30,6 +29,7 @@ use komorebi_core::Rect; use komorebi_core::Sizing; use komorebi_core::WindowContainerBehaviour; +use crate::border::Border; use crate::container::Container; use crate::current_virtual_desktop; use crate::load_configuration; @@ -205,7 +205,8 @@ impl WindowManager { rect.bottom += self.invisible_borders.bottom; let border = Border::from(BORDER_HWND.load(Ordering::SeqCst)); - border.set_position(foreground_window, &self.invisible_borders, true) + border.set_position(foreground_window, &self.invisible_borders, true)?; + WindowsApi::invalidate_border_rect() } #[tracing::instrument(skip(self))] diff --git a/komorebi/src/windows_api.rs b/komorebi/src/windows_api.rs index 28596fcd..55bca450 100644 --- a/komorebi/src/windows_api.rs +++ b/komorebi/src/windows_api.rs @@ -1,6 +1,7 @@ use std::collections::VecDeque; use std::convert::TryFrom; use std::ffi::c_void; +use std::sync::atomic::Ordering; use color_eyre::eyre::anyhow; use color_eyre::eyre::Error; @@ -28,6 +29,7 @@ use windows::Win32::Graphics::Dwm::DWM_CLOAKED_SHELL; use windows::Win32::Graphics::Gdi::CreateSolidBrush; use windows::Win32::Graphics::Gdi::EnumDisplayMonitors; use windows::Win32::Graphics::Gdi::GetMonitorInfoW; +use windows::Win32::Graphics::Gdi::InvalidateRect; use windows::Win32::Graphics::Gdi::MonitorFromPoint; use windows::Win32::Graphics::Gdi::MonitorFromWindow; use windows::Win32::Graphics::Gdi::HBRUSH; @@ -68,22 +70,22 @@ use windows::Win32::UI::WindowsAndMessaging::IsWindow; use windows::Win32::UI::WindowsAndMessaging::IsWindowVisible; use windows::Win32::UI::WindowsAndMessaging::RealGetWindowClassW; use windows::Win32::UI::WindowsAndMessaging::RegisterClassA; -use windows::Win32::UI::WindowsAndMessaging::SetClassLongPtrW; use windows::Win32::UI::WindowsAndMessaging::SetCursorPos; use windows::Win32::UI::WindowsAndMessaging::SetForegroundWindow; +use windows::Win32::UI::WindowsAndMessaging::SetLayeredWindowAttributes; use windows::Win32::UI::WindowsAndMessaging::SetWindowLongPtrW; use windows::Win32::UI::WindowsAndMessaging::SetWindowPos; use windows::Win32::UI::WindowsAndMessaging::ShowWindow; use windows::Win32::UI::WindowsAndMessaging::SystemParametersInfoW; use windows::Win32::UI::WindowsAndMessaging::WindowFromPoint; use windows::Win32::UI::WindowsAndMessaging::CW_USEDEFAULT; -use windows::Win32::UI::WindowsAndMessaging::GCLP_HBRBACKGROUND; use windows::Win32::UI::WindowsAndMessaging::GWL_EXSTYLE; use windows::Win32::UI::WindowsAndMessaging::GWL_STYLE; use windows::Win32::UI::WindowsAndMessaging::GW_HWNDNEXT; use windows::Win32::UI::WindowsAndMessaging::HWND_BOTTOM; use windows::Win32::UI::WindowsAndMessaging::HWND_NOTOPMOST; use windows::Win32::UI::WindowsAndMessaging::HWND_TOPMOST; +use windows::Win32::UI::WindowsAndMessaging::LWA_COLORKEY; use windows::Win32::UI::WindowsAndMessaging::SET_WINDOW_POS_FLAGS; use windows::Win32::UI::WindowsAndMessaging::SHOW_WINDOW_CMD; use windows::Win32::UI::WindowsAndMessaging::SPIF_SENDCHANGE; @@ -99,6 +101,7 @@ use windows::Win32::UI::WindowsAndMessaging::SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS use windows::Win32::UI::WindowsAndMessaging::WINDOW_LONG_PTR_INDEX; use windows::Win32::UI::WindowsAndMessaging::WNDCLASSA; use windows::Win32::UI::WindowsAndMessaging::WNDENUMPROC; +use windows::Win32::UI::WindowsAndMessaging::WS_EX_LAYERED; use windows::Win32::UI::WindowsAndMessaging::WS_EX_TOOLWINDOW; use windows::Win32::UI::WindowsAndMessaging::WS_MAXIMIZEBOX; use windows::Win32::UI::WindowsAndMessaging::WS_MINIMIZEBOX; @@ -113,6 +116,8 @@ use crate::monitor::Monitor; use crate::ring::Ring; use crate::set_window_position::SetWindowPosition; use crate::windows_callbacks; +use crate::BORDER_HWND; +use crate::TRANSPARENCY_COLOUR; pub enum WindowsResult { Err(E), @@ -673,8 +678,8 @@ impl WindowsApi { unsafe { GetModuleHandleW(None) }.process() } - pub fn create_solid_brush(r: u32, g: u32, b: u32) -> HBRUSH { - unsafe { CreateSolidBrush(r | (g << 8) | (b << 16)) } + pub fn create_solid_brush(colour: u32) -> HBRUSH { + unsafe { CreateSolidBrush(colour) } } pub fn register_class_a(window_class: &WNDCLASSA) -> Result { @@ -692,16 +697,6 @@ impl WindowsApi { Ok(a == b) } - pub fn change_border_colour(hwnd: isize, r: u32, g: u32, b: u32) -> Result { - Result::from(WindowsResult::from(unsafe { - SetClassLongPtrW( - HWND(hwnd), - GCLP_HBRBACKGROUND, - Self::create_solid_brush(r, g, b).0, - ) - })) - } - pub fn round_corners(hwnd: isize) -> Result<()> { let round = DWMWCP_ROUND; @@ -718,8 +713,8 @@ impl WindowsApi { pub fn create_border_window(name: PCSTR, instance: HINSTANCE) -> Result { unsafe { - CreateWindowExA( - WS_EX_TOOLWINDOW, + let hwnd = CreateWindowExA( + WS_EX_TOOLWINDOW | WS_EX_LAYERED, name, name, WS_POPUP | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX, @@ -731,8 +726,24 @@ impl WindowsApi { None, instance, std::ptr::null(), - ) + ); + + SetLayeredWindowAttributes(hwnd, TRANSPARENCY_COLOUR, 0, LWA_COLORKEY); + + hwnd } .process() } + + pub fn invalidate_border_rect() -> Result<()> { + unsafe { + InvalidateRect( + HWND(BORDER_HWND.load(Ordering::SeqCst)), + std::ptr::null(), + false, + ) + } + .ok() + .process() + } } diff --git a/komorebi/src/windows_callbacks.rs b/komorebi/src/windows_callbacks.rs index 88805ace..9e65dc6c 100644 --- a/komorebi/src/windows_callbacks.rs +++ b/komorebi/src/windows_callbacks.rs @@ -1,4 +1,5 @@ use std::collections::VecDeque; +use std::sync::atomic::Ordering; use windows::Win32::Foundation::BOOL; use windows::Win32::Foundation::HWND; @@ -6,9 +7,16 @@ use windows::Win32::Foundation::LPARAM; use windows::Win32::Foundation::LRESULT; use windows::Win32::Foundation::RECT; use windows::Win32::Foundation::WPARAM; -use windows::Win32::Graphics::Gdi::InvalidateRect; +use windows::Win32::Graphics::Gdi::BeginPaint; +use windows::Win32::Graphics::Gdi::CreatePen; +use windows::Win32::Graphics::Gdi::EndPaint; +use windows::Win32::Graphics::Gdi::Rectangle; +use windows::Win32::Graphics::Gdi::SelectObject; +use windows::Win32::Graphics::Gdi::ValidateRect; use windows::Win32::Graphics::Gdi::HDC; use windows::Win32::Graphics::Gdi::HMONITOR; +use windows::Win32::Graphics::Gdi::PAINTSTRUCT; +use windows::Win32::Graphics::Gdi::PS_SOLID; use windows::Win32::UI::Accessibility::HWINEVENTHOOK; use windows::Win32::UI::WindowsAndMessaging::DefWindowProcW; use windows::Win32::UI::WindowsAndMessaging::PostQuitMessage; @@ -22,6 +30,9 @@ use crate::window::Window; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; use crate::winevent_listener::WINEVENT_CALLBACK_CHANNEL; +use crate::BORDER_COLOUR_CURRENT; +use crate::BORDER_RECT; +use crate::TRANSPARENCY_COLOUR; pub extern "system" fn valid_display_monitors( hmonitor: HMONITOR, @@ -120,7 +131,18 @@ pub extern "system" fn border_window( unsafe { match message as u32 { WM_PAINT => { - InvalidateRect(window, std::ptr::null(), true); + let border_rect = *BORDER_RECT.lock(); + let mut ps = PAINTSTRUCT::default(); + let hdc = BeginPaint(window, std::ptr::addr_of_mut!(ps).cast()); + let hpen = CreatePen(PS_SOLID, 20, BORDER_COLOUR_CURRENT.load(Ordering::SeqCst)); + let hbrush = WindowsApi::create_solid_brush(TRANSPARENCY_COLOUR); + + SelectObject(hdc, hpen); + SelectObject(hdc, hbrush); + Rectangle(hdc, 0, 0, border_rect.right, border_rect.bottom); + EndPaint(window, &ps); + ValidateRect(window, std::ptr::null()); + LRESULT(0) } WM_DESTROY => { diff --git a/komorebic.lib.ahk b/komorebic.lib.ahk index e2dd7162..044ac4d3 100644 --- a/komorebic.lib.ahk +++ b/komorebic.lib.ahk @@ -1,7 +1,7 @@ ; Generated by komorebic.exe Start(ffm, await_configuration) { - Run, komorebic.exe start %ffm% --await_configuration %await_configuration%, , Hide + Run, komorebic.exe start %ffm% --await-configuration %await_configuration%, , Hide } Stop() { @@ -304,8 +304,8 @@ ActiveWindowBorder(boolean_state) { Run, komorebic.exe active-window-border %boolean_state%, , Hide } -ActiveWindowBorderColour(r, g, b) { - Run, komorebic.exe active-window-border-colour %r% %g% %b%, , Hide +ActiveWindowBorderColour(r, g, b, window_kind) { + Run, komorebic.exe active-window-border-colour %r% %g% %b% --window-kind %window_kind%, , Hide } FocusFollowsMouse(boolean_state, implementation) { diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 169a3d7c..a89a3888 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -43,6 +43,7 @@ use komorebi_core::Rect; use komorebi_core::Sizing; use komorebi_core::SocketMessage; use komorebi_core::StateQuery; +use komorebi_core::WindowKind; lazy_static! { static ref HOME_DIR: PathBuf = { @@ -401,6 +402,8 @@ struct ActiveWindowBorder { #[derive(Parser, AhkFunction)] struct ActiveWindowBorderColour { + #[clap(arg_enum, short, long, default_value = "single")] + window_kind: WindowKind, /// Red r: u32, /// Green @@ -1209,7 +1212,8 @@ fn main() -> Result<()> { } SubCommand::ActiveWindowBorderColour(arg) => { send_message( - &SocketMessage::ActiveWindowBorderColour(arg.r, arg.g, arg.b).as_bytes()?, + &SocketMessage::ActiveWindowBorderColour(arg.window_kind, arg.r, arg.g, arg.b) + .as_bytes()?, )?; } SubCommand::ResizeDelta(arg) => {