mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-03-23 09:51:16 +01:00
feat(borders): add windows accent implementation
This commit adds the ability for users to select between komorebi's implementation of borders with variable widths and the native Windows 11 implementation of thin "accent" borders. The new border_implementation configuration option defaults to BorderImplementation::Komorebi in order to preserve compat with existing user configurations. This option will be most useful for people who prefer ultra-thin borders, and people who want borders to be animated along with application windows if they are using the animation feature being worked on in PR #685.
This commit is contained in:
@@ -142,6 +142,7 @@ pub enum SocketMessage {
|
||||
BorderStyle(BorderStyle),
|
||||
BorderWidth(i32),
|
||||
BorderOffset(i32),
|
||||
BorderImplementation(BorderImplementation),
|
||||
Transparency(bool),
|
||||
TransparencyAlpha(u8),
|
||||
InvisibleBorders(Rect),
|
||||
@@ -219,7 +220,17 @@ pub enum StackbarLabel {
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default, Copy, Clone, Debug, Eq, PartialEq, Display, Serialize, Deserialize, JsonSchema,
|
||||
Default,
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Display,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
ValueEnum,
|
||||
)]
|
||||
pub enum BorderStyle {
|
||||
#[default]
|
||||
@@ -231,6 +242,27 @@ pub enum BorderStyle {
|
||||
Square,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Default,
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Display,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
JsonSchema,
|
||||
ValueEnum,
|
||||
)]
|
||||
pub enum BorderImplementation {
|
||||
#[default]
|
||||
/// Use the adjustable komorebi border implementation
|
||||
Komorebi,
|
||||
/// Use the thin Windows accent border implementation
|
||||
Windows,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
use crate::border_manager::window_kind_colour;
|
||||
use crate::border_manager::WindowKind;
|
||||
use crate::border_manager::BORDER_OFFSET;
|
||||
use crate::border_manager::BORDER_WIDTH;
|
||||
use crate::border_manager::FOCUSED;
|
||||
use crate::border_manager::FOCUS_STATE;
|
||||
use crate::border_manager::MONOCLE;
|
||||
use crate::border_manager::STACK;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::border_manager::UNFOCUSED;
|
||||
use crate::border_manager::Z_ORDER;
|
||||
use crate::WindowsApi;
|
||||
use crate::WINDOWS_11;
|
||||
@@ -165,7 +162,7 @@ impl Border {
|
||||
match WindowsApi::window_rect(window) {
|
||||
Ok(rect) => {
|
||||
// Grab the focus kind for this border
|
||||
let focus_kind = {
|
||||
let window_kind = {
|
||||
FOCUS_STATE
|
||||
.lock()
|
||||
.get(&window.0)
|
||||
@@ -177,12 +174,7 @@ impl Border {
|
||||
let hpen = CreatePen(
|
||||
PS_SOLID | PS_INSIDEFRAME,
|
||||
BORDER_WIDTH.load(Ordering::SeqCst),
|
||||
COLORREF(match focus_kind {
|
||||
WindowKind::Unfocused => UNFOCUSED.load(Ordering::SeqCst),
|
||||
WindowKind::Single => FOCUSED.load(Ordering::SeqCst),
|
||||
WindowKind::Stack => STACK.load(Ordering::SeqCst),
|
||||
WindowKind::Monocle => MONOCLE.load(Ordering::SeqCst),
|
||||
}),
|
||||
COLORREF(window_kind_colour(window_kind)),
|
||||
);
|
||||
|
||||
let hbrush = WindowsApi::create_solid_brush(0);
|
||||
|
||||
@@ -6,6 +6,7 @@ use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
use crossbeam_utils::atomic::AtomicCell;
|
||||
use crossbeam_utils::atomic::AtomicConsume;
|
||||
use komorebi_core::BorderImplementation;
|
||||
use komorebi_core::BorderStyle;
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
@@ -17,6 +18,7 @@ use std::collections::HashMap;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicI32;
|
||||
use std::sync::atomic::AtomicU32;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use std::sync::OnceLock;
|
||||
use windows::Win32::Foundation::HWND;
|
||||
@@ -35,10 +37,13 @@ pub static BORDER_WIDTH: AtomicI32 = AtomicI32::new(8);
|
||||
pub static BORDER_OFFSET: AtomicI32 = AtomicI32::new(-1);
|
||||
|
||||
pub static BORDER_ENABLED: AtomicBool = AtomicBool::new(true);
|
||||
pub static BORDER_TEMPORARILY_DISABLED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
lazy_static! {
|
||||
pub static ref Z_ORDER: AtomicCell<ZOrder> = AtomicCell::new(ZOrder::Bottom);
|
||||
pub static ref STYLE: AtomicCell<BorderStyle> = AtomicCell::new(BorderStyle::System);
|
||||
pub static ref IMPLEMENTATION: AtomicCell<BorderImplementation> =
|
||||
AtomicCell::new(BorderImplementation::Windows);
|
||||
pub static ref FOCUSED: AtomicU32 =
|
||||
AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(66, 165, 245))));
|
||||
pub static ref UNFOCUSED: AtomicU32 =
|
||||
@@ -59,7 +64,7 @@ pub struct Notification;
|
||||
static CHANNEL: OnceLock<(Sender<Notification>, Receiver<Notification>)> = OnceLock::new();
|
||||
|
||||
pub fn channel() -> &'static (Sender<Notification>, Receiver<Notification>) {
|
||||
CHANNEL.get_or_init(|| crossbeam_channel::bounded(20))
|
||||
CHANNEL.get_or_init(|| crossbeam_channel::bounded(50))
|
||||
}
|
||||
|
||||
fn event_tx() -> Sender<Notification> {
|
||||
@@ -109,6 +114,15 @@ pub fn destroy_all_borders() -> color_eyre::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn window_kind_colour(focus_kind: WindowKind) -> u32 {
|
||||
match focus_kind {
|
||||
WindowKind::Unfocused => UNFOCUSED.load(Ordering::SeqCst),
|
||||
WindowKind::Single => FOCUSED.load(Ordering::SeqCst),
|
||||
WindowKind::Stack => STACK.load(Ordering::SeqCst),
|
||||
WindowKind::Monocle => MONOCLE.load(Ordering::SeqCst),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
||||
std::thread::spawn(move || loop {
|
||||
match handle_notifications(wm.clone()) {
|
||||
@@ -125,6 +139,7 @@ pub fn listen_for_notifications(wm: Arc<Mutex<WindowManager>>) {
|
||||
pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result<()> {
|
||||
tracing::info!("listening");
|
||||
|
||||
BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst);
|
||||
let receiver = event_rx();
|
||||
event_tx().send(Notification)?;
|
||||
|
||||
@@ -141,179 +156,255 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
let pending_move_op = state.pending_move_op;
|
||||
drop(state);
|
||||
|
||||
let mut should_process_notification = true;
|
||||
|
||||
if monitors == previous_snapshot
|
||||
// handle the window dragging edge case
|
||||
&& pending_move_op == previous_pending_move_op
|
||||
{
|
||||
should_process_notification = false;
|
||||
}
|
||||
|
||||
// handle the pause edge case
|
||||
if is_paused && !previous_is_paused {
|
||||
should_process_notification = true;
|
||||
}
|
||||
|
||||
// handle the unpause edge case
|
||||
if previous_is_paused && !is_paused {
|
||||
should_process_notification = true;
|
||||
}
|
||||
|
||||
// handle the retile edge case
|
||||
if !should_process_notification && BORDER_STATE.lock().is_empty() {
|
||||
should_process_notification = true;
|
||||
}
|
||||
|
||||
if !should_process_notification {
|
||||
tracing::trace!("monitor state matches latest snapshot, skipping notification");
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
let mut borders = BORDER_STATE.lock();
|
||||
let mut borders_monitors = BORDERS_MONITORS.lock();
|
||||
|
||||
// If borders are disabled
|
||||
if !BORDER_ENABLED.load_consume()
|
||||
// Or if the wm is paused
|
||||
|| is_paused
|
||||
// Or if we are handling an alt-tab across workspaces
|
||||
|| ALT_TAB_HWND.load().is_some()
|
||||
{
|
||||
// Destroy the borders we know about
|
||||
for (_, border) in borders.iter() {
|
||||
border.destroy()?;
|
||||
}
|
||||
|
||||
borders.clear();
|
||||
|
||||
previous_is_paused = is_paused;
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() {
|
||||
// Only operate on the focused workspace of each monitor
|
||||
if let Some(ws) = m.focused_workspace() {
|
||||
// Workspaces with tiling disabled don't have borders
|
||||
if !ws.tile() {
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
continue 'monitors;
|
||||
}
|
||||
|
||||
// Handle the monocle container separately
|
||||
if let Some(monocle) = ws.monocle_container() {
|
||||
let border = match borders.entry(monocle.id().clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(monocle.id()) {
|
||||
entry.insert(border)
|
||||
} else {
|
||||
continue 'monitors;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
borders_monitors.insert(monocle.id().clone(), monitor_idx);
|
||||
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
focus_state.insert(
|
||||
border.hwnd,
|
||||
if monitor_idx != focused_monitor_idx {
|
||||
match IMPLEMENTATION.load() {
|
||||
BorderImplementation::Windows => {
|
||||
'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() {
|
||||
// Only operate on the focused workspace of each monitor
|
||||
if let Some(ws) = m.focused_workspace() {
|
||||
// Handle the monocle container separately
|
||||
if let Some(monocle) = ws.monocle_container() {
|
||||
let window_kind = if monitor_idx != focused_monitor_idx {
|
||||
WindowKind::Unfocused
|
||||
} else {
|
||||
WindowKind::Monocle
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let rect = WindowsApi::window_rect(
|
||||
monocle.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
monocle
|
||||
.focused_window()
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.set_accent(window_kind_colour(window_kind))?;
|
||||
|
||||
border.update(&rect, true)?;
|
||||
continue 'monitors;
|
||||
}
|
||||
|
||||
let border_hwnd = border.hwnd;
|
||||
let mut to_remove = vec![];
|
||||
for (id, b) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
|
||||
&& border_hwnd != b.hwnd
|
||||
{
|
||||
b.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
for (idx, c) in ws.containers().iter().enumerate() {
|
||||
let window_kind = if idx != ws.focused_container_idx()
|
||||
|| monitor_idx != focused_monitor_idx
|
||||
{
|
||||
WindowKind::Unfocused
|
||||
} else if c.windows().len() > 1 {
|
||||
WindowKind::Stack
|
||||
} else {
|
||||
WindowKind::Single
|
||||
};
|
||||
|
||||
c.focused_window()
|
||||
.copied()
|
||||
.unwrap_or_default()
|
||||
.set_accent(window_kind_colour(window_kind))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BorderImplementation::Komorebi => {
|
||||
let mut should_process_notification = true;
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
continue 'monitors;
|
||||
if monitors == previous_snapshot
|
||||
// handle the window dragging edge case
|
||||
&& pending_move_op == previous_pending_move_op
|
||||
{
|
||||
should_process_notification = false;
|
||||
}
|
||||
|
||||
let is_maximized = WindowsApi::is_zoomed(HWND(
|
||||
WindowsApi::foreground_window().unwrap_or_default(),
|
||||
));
|
||||
|
||||
if is_maximized {
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx {
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
continue 'monitors;
|
||||
// handle the pause edge case
|
||||
if is_paused && !previous_is_paused {
|
||||
should_process_notification = true;
|
||||
}
|
||||
|
||||
// Destroy any borders not associated with the focused workspace
|
||||
let container_ids = ws
|
||||
.containers()
|
||||
.iter()
|
||||
.map(|c| c.id().clone())
|
||||
.collect::<Vec<_>>();
|
||||
// handle the unpause edge case
|
||||
if previous_is_paused && !is_paused {
|
||||
should_process_notification = true;
|
||||
}
|
||||
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
|
||||
&& !container_ids.contains(id)
|
||||
{
|
||||
// handle the retile edge case
|
||||
if !should_process_notification && BORDER_STATE.lock().is_empty() {
|
||||
should_process_notification = true;
|
||||
}
|
||||
|
||||
if !should_process_notification {
|
||||
tracing::trace!("monitor state matches latest snapshot, skipping notification");
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
let mut borders = BORDER_STATE.lock();
|
||||
let mut borders_monitors = BORDERS_MONITORS.lock();
|
||||
|
||||
// If borders are disabled
|
||||
if !BORDER_ENABLED.load_consume()
|
||||
// Or if they are temporarily disabled
|
||||
|| BORDER_TEMPORARILY_DISABLED.load(Ordering::SeqCst)
|
||||
// Or if the wm is paused
|
||||
|| is_paused
|
||||
// Or if we are handling an alt-tab across workspaces
|
||||
|| ALT_TAB_HWND.load().is_some()
|
||||
{
|
||||
// Destroy the borders we know about
|
||||
for (_, border) in borders.iter() {
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
|
||||
borders.clear();
|
||||
|
||||
previous_is_paused = is_paused;
|
||||
continue 'receiver;
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
'monitors: for (monitor_idx, m) in monitors.elements().iter().enumerate() {
|
||||
// Only operate on the focused workspace of each monitor
|
||||
if let Some(ws) = m.focused_workspace() {
|
||||
// Workspaces with tiling disabled don't have borders
|
||||
if !ws.tile() {
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default()
|
||||
== monitor_idx
|
||||
{
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for (idx, c) in ws.containers().iter().enumerate() {
|
||||
// Update border when moving or resizing with mouse
|
||||
if pending_move_op.is_some() && idx == ws.focused_container_idx() {
|
||||
let restore_z_order = Z_ORDER.load();
|
||||
Z_ORDER.store(ZOrder::TopMost);
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
let mut rect = WindowsApi::window_rect(
|
||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
continue 'monitors;
|
||||
}
|
||||
|
||||
while WindowsApi::lbutton_is_pressed() {
|
||||
// Handle the monocle container separately
|
||||
if let Some(monocle) = ws.monocle_container() {
|
||||
let border = match borders.entry(monocle.id().clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(monocle.id()) {
|
||||
entry.insert(border)
|
||||
} else {
|
||||
continue 'monitors;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
borders_monitors.insert(monocle.id().clone(), monitor_idx);
|
||||
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
focus_state.insert(
|
||||
border.hwnd,
|
||||
if monitor_idx != focused_monitor_idx {
|
||||
WindowKind::Unfocused
|
||||
} else {
|
||||
WindowKind::Monocle
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let rect = WindowsApi::window_rect(
|
||||
monocle.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
|
||||
border.update(&rect, true)?;
|
||||
|
||||
let border_hwnd = border.hwnd;
|
||||
let mut to_remove = vec![];
|
||||
for (id, b) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default()
|
||||
== monitor_idx
|
||||
&& border_hwnd != b.hwnd
|
||||
{
|
||||
b.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
continue 'monitors;
|
||||
}
|
||||
|
||||
let is_maximized = WindowsApi::is_zoomed(HWND(
|
||||
WindowsApi::foreground_window().unwrap_or_default(),
|
||||
));
|
||||
|
||||
if is_maximized {
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default()
|
||||
== monitor_idx
|
||||
{
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
continue 'monitors;
|
||||
}
|
||||
|
||||
// Destroy any borders not associated with the focused workspace
|
||||
let container_ids = ws
|
||||
.containers()
|
||||
.iter()
|
||||
.map(|c| c.id().clone())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut to_remove = vec![];
|
||||
for (id, border) in borders.iter() {
|
||||
if borders_monitors.get(id).copied().unwrap_or_default() == monitor_idx
|
||||
&& !container_ids.contains(id)
|
||||
{
|
||||
border.destroy()?;
|
||||
to_remove.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
for id in &to_remove {
|
||||
borders.remove(id);
|
||||
}
|
||||
|
||||
for (idx, c) in ws.containers().iter().enumerate() {
|
||||
// Update border when moving or resizing with mouse
|
||||
if pending_move_op.is_some() && idx == ws.focused_container_idx() {
|
||||
let restore_z_order = Z_ORDER.load();
|
||||
Z_ORDER.store(ZOrder::TopMost);
|
||||
|
||||
let mut rect = WindowsApi::window_rect(
|
||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
|
||||
while WindowsApi::lbutton_is_pressed() {
|
||||
let border = match borders.entry(c.id().clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(c.id()) {
|
||||
entry.insert(border)
|
||||
} else {
|
||||
continue 'monitors;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let new_rect = WindowsApi::window_rect(
|
||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
|
||||
if rect != new_rect {
|
||||
rect = new_rect;
|
||||
border.update(&rect, true)?;
|
||||
}
|
||||
}
|
||||
|
||||
Z_ORDER.store(restore_z_order);
|
||||
|
||||
continue 'monitors;
|
||||
}
|
||||
|
||||
// Get the border entry for this container from the map or create one
|
||||
let border = match borders.entry(c.id().clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
@@ -325,64 +416,39 @@ pub fn handle_notifications(wm: Arc<Mutex<WindowManager>>) -> color_eyre::Result
|
||||
}
|
||||
};
|
||||
|
||||
let new_rect = WindowsApi::window_rect(
|
||||
borders_monitors.insert(c.id().clone(), monitor_idx);
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
let mut last_focus_state = None;
|
||||
|
||||
let new_focus_state = if idx != ws.focused_container_idx()
|
||||
|| monitor_idx != focused_monitor_idx
|
||||
{
|
||||
WindowKind::Unfocused
|
||||
} else if c.windows().len() > 1 {
|
||||
WindowKind::Stack
|
||||
} else {
|
||||
WindowKind::Single
|
||||
};
|
||||
|
||||
// Update the focused state for all containers on this workspace
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
last_focus_state = focus_state.insert(border.hwnd, new_focus_state);
|
||||
}
|
||||
|
||||
let rect = WindowsApi::window_rect(
|
||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
|
||||
if rect != new_rect {
|
||||
rect = new_rect;
|
||||
border.update(&rect, true)?;
|
||||
}
|
||||
let should_invalidate = match last_focus_state {
|
||||
None => true,
|
||||
Some(last_focus_state) => last_focus_state != new_focus_state,
|
||||
};
|
||||
|
||||
border.update(&rect, should_invalidate)?;
|
||||
}
|
||||
|
||||
Z_ORDER.store(restore_z_order);
|
||||
|
||||
continue 'monitors;
|
||||
}
|
||||
|
||||
// Get the border entry for this container from the map or create one
|
||||
let border = match borders.entry(c.id().clone()) {
|
||||
Entry::Occupied(entry) => entry.into_mut(),
|
||||
Entry::Vacant(entry) => {
|
||||
if let Ok(border) = Border::create(c.id()) {
|
||||
entry.insert(border)
|
||||
} else {
|
||||
continue 'monitors;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
borders_monitors.insert(c.id().clone(), monitor_idx);
|
||||
|
||||
#[allow(unused_assignments)]
|
||||
let mut last_focus_state = None;
|
||||
|
||||
let new_focus_state = if idx != ws.focused_container_idx()
|
||||
|| monitor_idx != focused_monitor_idx
|
||||
{
|
||||
WindowKind::Unfocused
|
||||
} else if c.windows().len() > 1 {
|
||||
WindowKind::Stack
|
||||
} else {
|
||||
WindowKind::Single
|
||||
};
|
||||
|
||||
// Update the focused state for all containers on this workspace
|
||||
{
|
||||
let mut focus_state = FOCUS_STATE.lock();
|
||||
last_focus_state = focus_state.insert(border.hwnd, new_focus_state);
|
||||
}
|
||||
|
||||
let rect = WindowsApi::window_rect(
|
||||
c.focused_window().copied().unwrap_or_default().hwnd(),
|
||||
)?;
|
||||
|
||||
let should_invalidate = match last_focus_state {
|
||||
None => true,
|
||||
Some(last_focus_state) => last_focus_state != new_focus_state,
|
||||
};
|
||||
|
||||
border.update(&rect, should_invalidate)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ use komorebi_core::config_generation::MatchingRule;
|
||||
use komorebi_core::config_generation::MatchingStrategy;
|
||||
use komorebi_core::ApplicationIdentifier;
|
||||
use komorebi_core::Axis;
|
||||
use komorebi_core::BorderImplementation;
|
||||
use komorebi_core::FocusFollowsMouseImplementation;
|
||||
use komorebi_core::Layout;
|
||||
use komorebi_core::MoveBehaviour;
|
||||
@@ -39,6 +40,7 @@ use komorebi_core::WindowContainerBehaviour;
|
||||
use komorebi_core::WindowKind;
|
||||
|
||||
use crate::border_manager;
|
||||
use crate::border_manager::IMPLEMENTATION;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::colour::Rgb;
|
||||
use crate::current_virtual_desktop;
|
||||
@@ -1241,6 +1243,19 @@ impl WindowManager {
|
||||
SocketMessage::Border(enable) => {
|
||||
border_manager::BORDER_ENABLED.store(enable, Ordering::SeqCst);
|
||||
}
|
||||
SocketMessage::BorderImplementation(implementation) => {
|
||||
IMPLEMENTATION.store(implementation);
|
||||
match IMPLEMENTATION.load() {
|
||||
BorderImplementation::Komorebi => {
|
||||
self.remove_all_accents()?;
|
||||
}
|
||||
BorderImplementation::Windows => {
|
||||
border_manager::destroy_all_borders()?;
|
||||
}
|
||||
}
|
||||
|
||||
border_manager::send_notification();
|
||||
}
|
||||
SocketMessage::BorderColour(kind, r, g, b) => match kind {
|
||||
WindowKind::Single => {
|
||||
border_manager::FOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::border_manager;
|
||||
use crate::border_manager::ZOrder;
|
||||
use crate::border_manager::IMPLEMENTATION;
|
||||
use crate::border_manager::STYLE;
|
||||
use crate::border_manager::Z_ORDER;
|
||||
use crate::colour::Colour;
|
||||
@@ -32,6 +33,7 @@ use crate::OBJECT_NAME_CHANGE_ON_LAUNCH;
|
||||
use crate::REGEX_IDENTIFIERS;
|
||||
use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS;
|
||||
use crate::WORKSPACE_RULES;
|
||||
use komorebi_core::BorderImplementation;
|
||||
use komorebi_core::StackbarLabel;
|
||||
use komorebi_core::StackbarMode;
|
||||
|
||||
@@ -279,6 +281,9 @@ pub struct StaticConfig {
|
||||
/// Active window border z-order (default: System)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub border_z_order: Option<ZOrder>,
|
||||
/// Display an active window border (default: false)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub border_implementation: Option<BorderImplementation>,
|
||||
/// Add transparency to unfocused windows (default: false)
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub transparency: Option<bool>,
|
||||
@@ -483,6 +488,7 @@ impl From<&WindowManager> for StaticConfig {
|
||||
),
|
||||
border_style: Option::from(STYLE.load()),
|
||||
border_z_order: Option::from(Z_ORDER.load()),
|
||||
border_implementation: Option::from(IMPLEMENTATION.load()),
|
||||
default_workspace_padding: Option::from(
|
||||
DEFAULT_WORKSPACE_PADDING.load(Ordering::SeqCst),
|
||||
),
|
||||
@@ -557,6 +563,17 @@ impl StaticConfig {
|
||||
}
|
||||
|
||||
STYLE.store(self.border_style.unwrap_or_default());
|
||||
IMPLEMENTATION.store(self.border_implementation.unwrap_or_default());
|
||||
match IMPLEMENTATION.load() {
|
||||
BorderImplementation::Komorebi => {
|
||||
border_manager::destroy_all_borders()?;
|
||||
}
|
||||
BorderImplementation::Windows => {
|
||||
// TODO: figure out how to call wm.remove_all_accents here
|
||||
}
|
||||
}
|
||||
|
||||
border_manager::send_notification();
|
||||
|
||||
transparency_manager::TRANSPARENCY_ENABLED
|
||||
.store(self.transparency.unwrap_or(false), Ordering::SeqCst);
|
||||
|
||||
@@ -275,6 +275,14 @@ impl Window {
|
||||
self.update_ex_style(&ex_style)
|
||||
}
|
||||
|
||||
pub fn set_accent(self, colour: u32) -> Result<()> {
|
||||
WindowsApi::set_window_accent(self.hwnd, Some(colour))
|
||||
}
|
||||
|
||||
pub fn remove_accent(self) -> Result<()> {
|
||||
WindowsApi::set_window_accent(self.hwnd, None)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_style(self, style: &WindowStyle) -> Result<()> {
|
||||
WindowsApi::update_style(self.hwnd(), isize::try_from(style.bits())?)
|
||||
|
||||
@@ -947,6 +947,8 @@ impl WindowManager {
|
||||
window.opaque()?;
|
||||
}
|
||||
|
||||
window.remove_accent()?;
|
||||
|
||||
window.restore();
|
||||
}
|
||||
}
|
||||
@@ -956,6 +958,23 @@ impl WindowManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
pub fn remove_all_accents(&mut self) -> Result<()> {
|
||||
tracing::info!("removing all window accents");
|
||||
|
||||
for monitor in self.monitors_mut() {
|
||||
for workspace in monitor.workspaces_mut() {
|
||||
for containers in workspace.containers_mut() {
|
||||
for window in containers.windows_mut() {
|
||||
window.remove_accent()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip(self))]
|
||||
fn handle_unmanaged_window_behaviour(&self) -> Result<()> {
|
||||
if matches!(
|
||||
|
||||
@@ -21,7 +21,9 @@ use windows::Win32::Foundation::POINT;
|
||||
use windows::Win32::Foundation::WPARAM;
|
||||
use windows::Win32::Graphics::Dwm::DwmGetWindowAttribute;
|
||||
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_BORDER_COLOR;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_CLOAKED;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_COLOR_NONE;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_EXTENDED_FRAME_BOUNDS;
|
||||
use windows::Win32::Graphics::Dwm::DWMWA_WINDOW_CORNER_PREFERENCE;
|
||||
use windows::Win32::Graphics::Dwm::DWMWCP_ROUND;
|
||||
@@ -955,6 +957,19 @@ impl WindowsApi {
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn set_window_accent(hwnd: isize, color: Option<u32>) -> Result<()> {
|
||||
let col_ref = COLORREF(color.unwrap_or(DWMWA_COLOR_NONE));
|
||||
unsafe {
|
||||
DwmSetWindowAttribute(
|
||||
HWND(hwnd),
|
||||
DWMWA_BORDER_COLOR,
|
||||
std::ptr::addr_of!(col_ref).cast(),
|
||||
4,
|
||||
)
|
||||
}
|
||||
.process()
|
||||
}
|
||||
|
||||
pub fn create_border_window(name: PCWSTR, instance: HMODULE) -> Result<isize> {
|
||||
unsafe {
|
||||
let hwnd = CreateWindowExW(
|
||||
|
||||
@@ -684,6 +684,19 @@ struct BorderOffset {
|
||||
/// Desired offset of the window border
|
||||
offset: i32,
|
||||
}
|
||||
#[derive(Parser)]
|
||||
struct BorderStyle {
|
||||
/// Desired border style
|
||||
#[clap(value_enum)]
|
||||
style: komorebi_core::BorderStyle,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
struct BorderImplementation {
|
||||
/// Desired border implementation
|
||||
#[clap(value_enum)]
|
||||
style: komorebi_core::BorderImplementation,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
@@ -1176,6 +1189,12 @@ enum SubCommand {
|
||||
#[clap(arg_required_else_help = true)]
|
||||
#[clap(alias = "active-window-border-offset")]
|
||||
BorderOffset(BorderOffset),
|
||||
/// Set the border style
|
||||
#[clap(arg_required_else_help = true)]
|
||||
BorderStyle(BorderStyle),
|
||||
/// Set the border implementation
|
||||
#[clap(arg_required_else_help = true)]
|
||||
BorderImplementation(BorderImplementation),
|
||||
/// Enable or disable transparency for unfocused windows
|
||||
#[clap(arg_required_else_help = true)]
|
||||
Transparency(Transparency),
|
||||
@@ -2269,6 +2288,12 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue
|
||||
SubCommand::BorderOffset(arg) => {
|
||||
send_message(&SocketMessage::BorderOffset(arg.offset).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::BorderStyle(arg) => {
|
||||
send_message(&SocketMessage::BorderStyle(arg.style).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::BorderImplementation(arg) => {
|
||||
send_message(&SocketMessage::BorderImplementation(arg.style).as_bytes()?)?;
|
||||
}
|
||||
SubCommand::Transparency(arg) => {
|
||||
send_message(&SocketMessage::Transparency(arg.boolean_state.into()).as_bytes()?)?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user