diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs index 42757fb4..c5762eee 100644 --- a/komorebi/src/border_manager/mod.rs +++ b/komorebi/src/border_manager/mod.rs @@ -49,6 +49,8 @@ lazy_static! { pub static ref MONOCLE: AtomicU32 = AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(255, 51, 153)))); pub static ref STACK: AtomicU32 = AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(0, 165, 66)))); + pub static ref FLOATING: AtomicU32 = + AtomicU32::new(u32::from(Colour::Rgb(Rgb::new(245, 245, 165)))); } lazy_static! { @@ -57,7 +59,7 @@ lazy_static! { static ref FOCUS_STATE: Mutex> = Mutex::new(HashMap::new()); } -pub struct Notification; +pub struct Notification(pub Option); static CHANNEL: OnceLock<(Sender, Receiver)> = OnceLock::new(); @@ -73,8 +75,8 @@ fn event_rx() -> Receiver { channel().1.clone() } -pub fn send_notification() { - if event_tx().try_send(Notification).is_err() { +pub fn send_notification(hwnd: Option) { + if event_tx().try_send(Notification(hwnd)).is_err() { tracing::warn!("channel is full; dropping notification") } } @@ -118,6 +120,7 @@ fn window_kind_colour(focus_kind: WindowKind) -> u32 { WindowKind::Single => FOCUSED.load(Ordering::SeqCst), WindowKind::Stack => STACK.load(Ordering::SeqCst), WindowKind::Monocle => MONOCLE.load(Ordering::SeqCst), + WindowKind::Floating => FLOATING.load(Ordering::SeqCst), } } @@ -139,19 +142,29 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result BORDER_TEMPORARILY_DISABLED.store(false, Ordering::SeqCst); let receiver = event_rx(); - event_tx().send(Notification)?; + event_tx().send(Notification(None))?; let mut previous_snapshot = Ring::default(); let mut previous_pending_move_op = None; let mut previous_is_paused = false; + let mut previous_notification: Option = None; - 'receiver: for _ in receiver { + 'receiver: for notification in receiver { // Check the wm state every time we receive a notification let state = wm.lock(); let is_paused = state.is_paused; let focused_monitor_idx = state.focused_monitor_idx(); + let focused_workspace_idx = + state.monitors.elements()[focused_monitor_idx].focused_workspace_idx(); let monitors = state.monitors.clone(); let pending_move_op = state.pending_move_op; + let floating_window_hwnds = state.monitors.elements()[focused_monitor_idx].workspaces() + [focused_workspace_idx] + .floating_windows() + .iter() + .map(|w| w.hwnd) + .collect::>(); + drop(state); match IMPLEMENTATION.load() { @@ -220,6 +233,21 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result should_process_notification = true; } + // when we switch focus to a floating window + if !should_process_notification + && floating_window_hwnds.contains(¬ification.0.unwrap_or_default()) + { + should_process_notification = true; + } + + if !should_process_notification { + if let Some(ref previous) = previous_notification { + if previous.0.unwrap_or_default() != notification.0.unwrap_or_default() { + should_process_notification = true; + } + } + } + if !should_process_notification { tracing::trace!("monitor state matches latest snapshot, skipping notification"); continue 'receiver; @@ -446,6 +474,68 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result border.update(&rect, should_invalidate)?; } + + { + let restore_z_order = Z_ORDER.load(); + Z_ORDER.store(ZOrder::TopMost); + + 'windows: for window in ws.floating_windows() { + let border = match borders.entry(window.hwnd.to_string()) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + if let Ok(border) = Border::create(&window.hwnd.to_string()) + { + entry.insert(border) + } else { + continue 'monitors; + } + } + }; + + borders_monitors.insert(window.hwnd.to_string(), monitor_idx); + + let mut should_destroy = false; + + if let Some(notification_hwnd) = notification.0 { + if notification_hwnd != window.hwnd { + should_destroy = true; + } + } + + if WindowsApi::foreground_window().unwrap_or_default() + != window.hwnd + { + should_destroy = true; + } + + if should_destroy { + border.destroy()?; + borders.remove(&window.hwnd.to_string()); + borders_monitors.remove(&window.hwnd.to_string()); + continue 'windows; + } + + #[allow(unused_assignments)] + let mut last_focus_state = None; + let new_focus_state = WindowKind::Floating; + { + let mut focus_state = FOCUS_STATE.lock(); + last_focus_state = + focus_state.insert(border.hwnd, new_focus_state); + } + + let rect = WindowsApi::window_rect(window.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)?; + } + + Z_ORDER.store(restore_z_order); + } } } } @@ -454,6 +544,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result previous_snapshot = monitors; previous_pending_move_op = pending_move_op; previous_is_paused = is_paused; + previous_notification = Some(notification); } Ok(()) diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index b84748e3..4c59012d 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -294,6 +294,7 @@ pub enum WindowKind { Stack, Monocle, Unfocused, + Floating, } #[derive( diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index b8076102..8e77877f 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -158,6 +158,7 @@ lazy_static! { matching_strategy: Option::from(MatchingStrategy::Equals), }) ])); + static ref FLOATING_APPLICATIONS: Arc>> = Arc::new(Mutex::new(Vec::new())); static ref PERMAIGNORE_CLASSES: Arc>> = Arc::new(Mutex::new(vec![ "Chrome_RenderWidgetHostHWND".to_string(), ])); @@ -217,7 +218,6 @@ lazy_static! { static ref WINDOWS_BY_BAR_HWNDS: Arc>>> = Arc::new(Mutex::new(HashMap::new())); - } pub static DEFAULT_WORKSPACE_PADDING: AtomicI32 = AtomicI32::new(10); diff --git a/komorebi/src/monitor_reconciliator/mod.rs b/komorebi/src/monitor_reconciliator/mod.rs index c610a8bc..b15b452f 100644 --- a/komorebi/src/monitor_reconciliator/mod.rs +++ b/komorebi/src/monitor_reconciliator/mod.rs @@ -172,7 +172,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result if should_update { tracing::info!("updated work area for {}", monitor.device_id()); monitor.update_focused_workspace(offset)?; - border_manager::send_notification(); + border_manager::send_notification(None); } else { tracing::debug!( "work areas match, reconciliation not required for {}", @@ -219,7 +219,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result ); monitor.update_focused_workspace(offset)?; - border_manager::send_notification(); + border_manager::send_notification(None); } else { tracing::debug!( "resolutions match, reconciliation not required for {}", @@ -406,7 +406,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result // Second retile to fix DPI/resolution related jank wm.retile_all(true)?; // Border updates to fix DPI/resolution related jank - border_manager::send_notification(); + border_manager::send_notification(None); } } } diff --git a/komorebi/src/process_command.rs b/komorebi/src/process_command.rs index 2169be97..5323bd73 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -1388,7 +1388,7 @@ impl WindowManager { } } - border_manager::send_notification(); + border_manager::send_notification(None); } } SocketMessage::BorderColour(kind, r, g, b) => match kind { @@ -1404,6 +1404,9 @@ impl WindowManager { WindowKind::Unfocused => { border_manager::UNFOCUSED.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); } + WindowKind::Floating => { + border_manager::FLOATING.store(Rgb::new(r, g, b).into(), Ordering::SeqCst); + } }, SocketMessage::BorderStyle(style) => { STYLE.store(style); @@ -1533,7 +1536,7 @@ impl WindowManager { }; notify_subscribers(&serde_json::to_string(¬ification)?)?; - border_manager::send_notification(); + border_manager::send_notification(None); transparency_manager::send_notification(); stackbar_manager::send_notification(); diff --git a/komorebi/src/process_event.rs b/komorebi/src/process_event.rs index fc3970a7..47dbff78 100644 --- a/komorebi/src/process_event.rs +++ b/komorebi/src/process_event.rs @@ -33,6 +33,7 @@ use crate::workspace_reconciliator::ALT_TAB_HWND_INSTANT; use crate::Notification; use crate::NotificationEvent; use crate::DATA_DIR; +use crate::FLOATING_APPLICATIONS; use crate::HIDDEN_HWNDS; use crate::REGEX_IDENTIFIERS; use crate::TRAY_AND_MULTI_WINDOW_IDENTIFIERS; @@ -336,19 +337,44 @@ impl WindowManager { let monocle_container = workspace.monocle_container().clone(); if !workspace_contains_window && !needs_reconciliation { - match behaviour { - WindowContainerBehaviour::Create => { - workspace.new_container_for_window(window); - self.update_focused_workspace(false, false)?; - } - WindowContainerBehaviour::Append => { - workspace - .focused_container_mut() - .ok_or_else(|| anyhow!("there is no focused container"))? - .add_window(window); - self.update_focused_workspace(true, false)?; + let floating_applications = FLOATING_APPLICATIONS.lock(); + let regex_identifiers = REGEX_IDENTIFIERS.lock(); + let mut should_float = false; - stackbar_manager::send_notification(); + if !floating_applications.is_empty() { + if let (Ok(title), Ok(exe_name), Ok(class), Ok(path)) = + (window.title(), window.exe(), window.class(), window.path()) + { + should_float = should_act( + &title, + &exe_name, + &class, + &path, + &floating_applications, + ®ex_identifiers, + ) + .is_some(); + } + } + + if should_float && !matches!(event, WindowManagerEvent::Manage(_)) { + workspace.floating_windows_mut().push(window); + self.update_focused_workspace(false, true)?; + } else { + match behaviour { + WindowContainerBehaviour::Create => { + workspace.new_container_for_window(window); + self.update_focused_workspace(false, false)?; + } + WindowContainerBehaviour::Append => { + workspace + .focused_container_mut() + .ok_or_else(|| anyhow!("there is no focused container"))? + .add_window(window); + self.update_focused_workspace(true, false)?; + + stackbar_manager::send_notification(); + } } } } @@ -642,7 +668,7 @@ impl WindowManager { }; notify_subscribers(&serde_json::to_string(¬ification)?)?; - border_manager::send_notification(); + border_manager::send_notification(Some(event.hwnd())); transparency_manager::send_notification(); stackbar_manager::send_notification(); diff --git a/komorebi/src/reaper.rs b/komorebi/src/reaper.rs index bf771e45..28341725 100644 --- a/komorebi/src/reaper.rs +++ b/komorebi/src/reaper.rs @@ -51,7 +51,7 @@ pub fn find_orphans(wm: Arc>) -> color_eyre::Result<()> { let reaped_orphans = workspace.reap_orphans()?; if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 { workspace.update(&work_area, offset, window_based_work_area_offset)?; - border_manager::send_notification(); + border_manager::send_notification(None); tracing::info!( "reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}", reaped_orphans.0, diff --git a/komorebi/src/static_config.rs b/komorebi/src/static_config.rs index 409025ed..2c25085b 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -35,6 +35,7 @@ use crate::DATA_DIR; use crate::DEFAULT_CONTAINER_PADDING; use crate::DEFAULT_WORKSPACE_PADDING; use crate::DISPLAY_INDEX_PREFERENCES; +use crate::FLOATING_APPLICATIONS; use crate::FLOAT_IDENTIFIERS; use crate::HIDING_BEHAVIOUR; use crate::LAYERED_WHITELIST; @@ -302,10 +303,14 @@ pub struct StaticConfig { pub global_work_area_offset: Option, /// Individual window floating rules #[serde(skip_serializing_if = "Option::is_none")] - pub float_rules: Option>, + #[serde(alias = "float_rules")] + pub ignore_rules: Option>, /// Individual window force-manage rules #[serde(skip_serializing_if = "Option::is_none")] pub manage_rules: Option>, + /// Identify applications which should be managed as floating windows + #[serde(skip_serializing_if = "Option::is_none")] + pub floating_applications: Option>, /// Identify border overflow applications #[serde(skip_serializing_if = "Option::is_none")] pub border_overflow_applications: Option>, @@ -359,6 +364,8 @@ pub enum KomorebiTheme { stack_border: Option, /// Border colour when the container is in monocle mode (default: Pink) monocle_border: Option, + /// Border colour when the window is floating (default: Yellow) + floating_border: Option, /// Border colour when the container is unfocused (default: Base) unfocused_border: Option, /// Stackbar focused tab text colour (default: Green) @@ -380,6 +387,8 @@ pub enum KomorebiTheme { stack_border: Option, /// Border colour when the container is in monocle mode (default: Base0F) monocle_border: Option, + /// Border colour when the window is floating (default: Base09) + floating_border: Option, /// Border colour when the container is unfocused (default: Base01) unfocused_border: Option, /// Stackbar focused tab text colour (default: Base0B) @@ -533,7 +542,8 @@ impl From<&WindowManager> for StaticConfig { monitors: Option::from(monitors), window_hiding_behaviour: Option::from(*HIDING_BEHAVIOUR.lock()), global_work_area_offset: value.work_area_offset, - float_rules: None, + ignore_rules: None, + floating_applications: None, manage_rules: None, border_overflow_applications: None, tray_and_multi_window_applications: None, @@ -637,7 +647,7 @@ impl StaticConfig { } } - border_manager::send_notification(); + border_manager::send_notification(None); } transparency_manager::TRANSPARENCY_ENABLED @@ -652,11 +662,16 @@ impl StaticConfig { let mut object_name_change_identifiers = OBJECT_NAME_CHANGE_ON_LAUNCH.lock(); let mut layered_identifiers = LAYERED_WHITELIST.lock(); let mut transparency_blacklist = TRANSPARENCY_BLACKLIST.lock(); + let mut floating_applications = FLOATING_APPLICATIONS.lock(); - if let Some(rules) = &mut self.float_rules { + if let Some(rules) = &mut self.ignore_rules { populate_rules(rules, &mut float_identifiers, &mut regex_identifiers)?; } + if let Some(rules) = &mut self.floating_applications { + populate_rules(rules, &mut floating_applications, &mut regex_identifiers)?; + } + if let Some(rules) = &mut self.manage_rules { populate_rules(rules, &mut manage_identifiers, &mut regex_identifiers)?; } @@ -726,6 +741,7 @@ impl StaticConfig { single_border, stack_border, monocle_border, + floating_border, unfocused_border, stackbar_focused_text, stackbar_unfocused_text, @@ -736,6 +752,7 @@ impl StaticConfig { single_border, stack_border, monocle_border, + floating_border, unfocused_border, stackbar_focused_text, stackbar_unfocused_text, @@ -754,6 +771,10 @@ impl StaticConfig { .unwrap_or(komorebi_themes::CatppuccinValue::Pink) .color32(name.as_theme()); + let floating_border = floating_border + .unwrap_or(komorebi_themes::CatppuccinValue::Yellow) + .color32(name.as_theme()); + let unfocused_border = unfocused_border .unwrap_or(komorebi_themes::CatppuccinValue::Base) .color32(name.as_theme()); @@ -774,6 +795,7 @@ impl StaticConfig { single_border, stack_border, monocle_border, + floating_border, unfocused_border, stackbar_focused_text, stackbar_unfocused_text, @@ -785,6 +807,7 @@ impl StaticConfig { single_border, stack_border, monocle_border, + floating_border, unfocused_border, stackbar_focused_text, stackbar_unfocused_text, @@ -807,6 +830,10 @@ impl StaticConfig { .unwrap_or(komorebi_themes::Base16Value::Base01) .color32(*name); + let floating_border = floating_border + .unwrap_or(komorebi_themes::Base16Value::Base09) + .color32(*name); + let stackbar_focused_text = stackbar_focused_text .unwrap_or(komorebi_themes::Base16Value::Base0B) .color32(*name); @@ -823,6 +850,7 @@ impl StaticConfig { single_border, stack_border, monocle_border, + floating_border, unfocused_border, stackbar_focused_text, stackbar_unfocused_text, @@ -835,6 +863,8 @@ impl StaticConfig { border_manager::MONOCLE .store(u32::from(Colour::from(monocle_border)), Ordering::SeqCst); border_manager::STACK.store(u32::from(Colour::from(stack_border)), Ordering::SeqCst); + border_manager::FLOATING + .store(u32::from(Colour::from(floating_border)), Ordering::SeqCst); border_manager::UNFOCUSED .store(u32::from(Colour::from(unfocused_border)), Ordering::SeqCst); diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 97acd326..fb2293c7 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -179,7 +179,7 @@ impl Window { let mut animation = self.animation; border_manager::BORDER_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst); - border_manager::send_notification(); + border_manager::send_notification(Some(self.hwnd)); stackbar_manager::STACKBAR_TEMPORARILY_DISABLED.store(true, Ordering::SeqCst); stackbar_manager::send_notification(); @@ -201,7 +201,7 @@ impl Window { stackbar_manager::STACKBAR_TEMPORARILY_DISABLED .store(false, Ordering::SeqCst); - border_manager::send_notification(); + border_manager::send_notification(Some(hwnd)); stackbar_manager::send_notification(); transparency_manager::send_notification(); } diff --git a/komorebi/src/workspace_reconciliator.rs b/komorebi/src/workspace_reconciliator.rs index 3842546a..60cbf2fd 100644 --- a/komorebi/src/workspace_reconciliator.rs +++ b/komorebi/src/workspace_reconciliator.rs @@ -118,7 +118,7 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result // Unblock the border manager ALT_TAB_HWND.store(None); // Send a notification to the border manager to update the borders - border_manager::send_notification(); + border_manager::send_notification(None); } } }