diff --git a/komorebi/src/border_manager/mod.rs b/komorebi/src/border_manager/mod.rs index 42757fb49..2825d086d 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; @@ -345,16 +373,20 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result } // Destroy any borders not associated with the focused workspace - let container_ids = ws + let mut container_and_floating_window_ids = ws .containers() .iter() .map(|c| c.id().clone()) .collect::>(); + for w in ws.floating_windows() { + container_and_floating_window_ids.push(w.hwnd.to_string()); + } + 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) + && !container_and_floating_window_ids.contains(id) { border.destroy()?; to_remove.push(id.clone()); @@ -366,8 +398,14 @@ pub fn handle_notifications(wm: Arc>) -> color_eyre::Result } for (idx, c) in ws.containers().iter().enumerate() { + let hwnd = c.focused_window().copied().unwrap_or_default().hwnd; + let notification_hwnd = notification.0.unwrap_or_default(); + // Update border when moving or resizing with mouse - if pending_move_op.is_some() && idx == ws.focused_container_idx() { + if pending_move_op.is_some() + && idx == ws.focused_container_idx() + && hwnd == notification_hwnd + { let restore_z_order = Z_ORDER.load(); Z_ORDER.store(ZOrder::TopMost); @@ -446,6 +484,101 @@ 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 hwnd = window.hwnd; + let notification_hwnd = notification.0.unwrap_or_default(); + + if pending_move_op.is_some() && hwnd == notification_hwnd { + let mut rect = WindowsApi::window_rect(hwnd)?; + + while WindowsApi::lbutton_is_pressed() { + let border = match borders.entry(hwnd.to_string()) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + if let Ok(border) = + Border::create(&hwnd.to_string()) + { + entry.insert(border) + } else { + continue 'monitors; + } + } + }; + + let new_rect = WindowsApi::window_rect(hwnd)?; + + if rect != new_rect { + rect = new_rect; + border.update(&rect, true)?; + } + } + + Z_ORDER.store(restore_z_order); + + continue 'monitors; + } + + 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 +587,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/config_generation.rs b/komorebi/src/core/config_generation.rs index fe67134fc..932a5f8a9 100644 --- a/komorebi/src/core/config_generation.rs +++ b/komorebi/src/core/config_generation.rs @@ -116,7 +116,8 @@ pub struct ApplicationConfiguration { #[serde(skip_serializing_if = "Option::is_none")] pub options: Option>, #[serde(skip_serializing_if = "Option::is_none")] - pub float_identifiers: Option>, + #[serde(alias = "float_identifiers")] + pub ignore_identifiers: Option>, } impl ApplicationConfiguration { @@ -187,7 +188,7 @@ impl ApplicationConfigurationGenerator { let mut lines = vec![String::from("# Generated by komorebic.exe"), String::new()]; - let mut float_rules = vec![]; + let mut ignore_rules = vec![]; for app in cfgen { lines.push(format!("# {}", app.name)); @@ -201,15 +202,15 @@ impl ApplicationConfigurationGenerator { } } - if let Some(float_identifiers) = app.float_identifiers { - for matching_rule in float_identifiers { + if let Some(ignore_identifiers) = app.ignore_identifiers { + for matching_rule in ignore_identifiers { if let MatchingRule::Simple(float) = matching_rule { let float_rule = format!("komorebic.exe float-rule {} \"{}\"", float.kind, float.id); // Don't want to send duped signals especially as configs get larger - if !float_rules.contains(&float_rule) { - float_rules.push(float_rule.clone()); + if !ignore_rules.contains(&float_rule) { + ignore_rules.push(float_rule.clone()); // if let Some(comment) = float.comment { // lines.push(format!("# {comment}")); @@ -238,7 +239,7 @@ impl ApplicationConfigurationGenerator { let mut lines = vec![String::from("; Generated by komorebic.exe"), String::new()]; - let mut float_rules = vec![]; + let mut ignore_rules = vec![]; for app in cfgen { lines.push(format!("; {}", app.name)); @@ -252,8 +253,8 @@ impl ApplicationConfigurationGenerator { } } - if let Some(float_identifiers) = app.float_identifiers { - for matching_rule in float_identifiers { + if let Some(ignore_identifiers) = app.ignore_identifiers { + for matching_rule in ignore_identifiers { if let MatchingRule::Simple(float) = matching_rule { let float_rule = format!( "RunWait('komorebic.exe float-rule {} \"{}\"', , \"Hide\")", @@ -261,8 +262,8 @@ impl ApplicationConfigurationGenerator { ); // Don't want to send duped signals especially as configs get larger - if !float_rules.contains(&float_rule) { - float_rules.push(float_rule.clone()); + if !ignore_rules.contains(&float_rule) { + ignore_rules.push(float_rule.clone()); // if let Some(comment) = float.comment { // lines.push(format!("; {comment}")); diff --git a/komorebi/src/core/mod.rs b/komorebi/src/core/mod.rs index b84748e3d..c08cb4b70 100644 --- a/komorebi/src/core/mod.rs +++ b/komorebi/src/core/mod.rs @@ -174,7 +174,8 @@ pub enum SocketMessage { ClearWorkspaceRules(usize, usize), ClearNamedWorkspaceRules(String), ClearAllWorkspaceRules, - FloatRule(ApplicationIdentifier, String), + #[serde(alias = "FloatRule")] + IgnoreRule(ApplicationIdentifier, String), ManageRule(ApplicationIdentifier, String), IdentifyObjectNameChangeApplication(ApplicationIdentifier, String), IdentifyTrayApplication(ApplicationIdentifier, String), @@ -294,6 +295,7 @@ pub enum WindowKind { Stack, Monocle, Unfocused, + Floating, } #[derive( diff --git a/komorebi/src/lib.rs b/komorebi/src/lib.rs index db019c340..b001e5776 100644 --- a/komorebi/src/lib.rs +++ b/komorebi/src/lib.rs @@ -139,7 +139,7 @@ lazy_static! { static ref REGEX_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(HashMap::new())); static ref MANAGE_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![])); - static ref FLOAT_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![ + static ref IGNORE_IDENTIFIERS: Arc>> = Arc::new(Mutex::new(vec![ // mstsc.exe creates these on Windows 11 when a WSL process is launched // /~https://github.com/LGUG2Z/komorebi/issues/74 MatchingRule::Simple(IdWithIdentifier { @@ -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(), ])); @@ -224,7 +225,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.rs b/komorebi/src/monitor.rs index 090fe7ee0..5e40467ec 100644 --- a/komorebi/src/monitor.rs +++ b/komorebi/src/monitor.rs @@ -20,6 +20,7 @@ use crate::workspace::Workspace; use crate::DefaultLayout; use crate::Layout; use crate::OperationDirection; +use crate::WindowsApi; #[derive( Debug, @@ -178,66 +179,90 @@ impl Monitor { bail!("cannot move native maximized window to another monitor or workspace"); } - let container = workspace - .remove_focused_container() - .ok_or_else(|| anyhow!("there is no container"))?; + let foreground_hwnd = WindowsApi::foreground_window()?; + let floating_window_index = workspace + .floating_windows() + .iter() + .position(|w| w.hwnd == foreground_hwnd); - let workspaces = self.workspaces_mut(); + if let Some(idx) = floating_window_index { + let window = workspace.floating_windows_mut().remove(idx); - #[allow(clippy::option_if_let_else)] - let target_workspace = match workspaces.get_mut(target_workspace_idx) { - None => { - workspaces.resize(target_workspace_idx + 1, Workspace::default()); - workspaces.get_mut(target_workspace_idx).unwrap() - } - Some(workspace) => workspace, - }; + let workspaces = self.workspaces_mut(); + #[allow(clippy::option_if_let_else)] + let target_workspace = match workspaces.get_mut(target_workspace_idx) { + None => { + workspaces.resize(target_workspace_idx + 1, Workspace::default()); + workspaces.get_mut(target_workspace_idx).unwrap() + } + Some(workspace) => workspace, + }; - match direction { - Some(OperationDirection::Left) => match target_workspace.layout() { - Layout::Default(layout) => match layout { - DefaultLayout::RightMainVerticalStack => { - target_workspace.add_container_to_front(container); - } - DefaultLayout::UltrawideVerticalStack => { - if target_workspace.containers().len() == 1 { - target_workspace.insert_container_at_idx(0, container); - } else { + target_workspace.floating_windows_mut().push(window); + } else { + let container = workspace + .remove_focused_container() + .ok_or_else(|| anyhow!("there is no container"))?; + + let workspaces = self.workspaces_mut(); + + #[allow(clippy::option_if_let_else)] + let target_workspace = match workspaces.get_mut(target_workspace_idx) { + None => { + workspaces.resize(target_workspace_idx + 1, Workspace::default()); + workspaces.get_mut(target_workspace_idx).unwrap() + } + Some(workspace) => workspace, + }; + + match direction { + Some(OperationDirection::Left) => match target_workspace.layout() { + Layout::Default(layout) => match layout { + DefaultLayout::RightMainVerticalStack => { + target_workspace.add_container_to_front(container); + } + DefaultLayout::UltrawideVerticalStack => { + if target_workspace.containers().len() == 1 { + target_workspace.insert_container_at_idx(0, container); + } else { + target_workspace.add_container_to_back(container); + } + } + _ => { target_workspace.add_container_to_back(container); } - } - _ => { + }, + Layout::Custom(_) => { target_workspace.add_container_to_back(container); } }, - Layout::Custom(_) => { - target_workspace.add_container_to_back(container); - } - }, - Some(OperationDirection::Right) => match target_workspace.layout() { - Layout::Default(layout) => { - let target_index = layout.leftmost_index(target_workspace.containers().len()); - - match layout { - DefaultLayout::RightMainVerticalStack - | DefaultLayout::UltrawideVerticalStack => { - if target_workspace.containers().len() == 1 { - target_workspace.add_container_to_back(container); - } else { + Some(OperationDirection::Right) => match target_workspace.layout() { + Layout::Default(layout) => { + let target_index = + layout.leftmost_index(target_workspace.containers().len()); + + match layout { + DefaultLayout::RightMainVerticalStack + | DefaultLayout::UltrawideVerticalStack => { + if target_workspace.containers().len() == 1 { + target_workspace.add_container_to_back(container); + } else { + target_workspace + .insert_container_at_idx(target_index, container); + } + } + _ => { target_workspace.insert_container_at_idx(target_index, container); } } - _ => { - target_workspace.insert_container_at_idx(target_index, container); - } } + Layout::Custom(_) => { + target_workspace.add_container_to_front(container); + } + }, + _ => { + target_workspace.add_container_to_back(container); } - Layout::Custom(_) => { - target_workspace.add_container_to_front(container); - } - }, - _ => { - target_workspace.add_container_to_back(container); } } diff --git a/komorebi/src/monitor_reconciliator/mod.rs b/komorebi/src/monitor_reconciliator/mod.rs index c610a8bce..b15b452fb 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 77777bc85..09fa303ab 100644 --- a/komorebi/src/process_command.rs +++ b/komorebi/src/process_command.rs @@ -68,8 +68,8 @@ use crate::ANIMATION_STYLE; use crate::CUSTOM_FFM; use crate::DATA_DIR; use crate::DISPLAY_INDEX_PREFERENCES; -use crate::FLOAT_IDENTIFIERS; use crate::HIDING_BEHAVIOUR; +use crate::IGNORE_IDENTIFIERS; use crate::INITIAL_CONFIGURATION_LOADED; use crate::LAYERED_WHITELIST; use crate::MANAGE_IDENTIFIERS; @@ -394,20 +394,20 @@ impl WindowManager { })); } } - SocketMessage::FloatRule(identifier, ref id) => { - let mut float_identifiers = FLOAT_IDENTIFIERS.lock(); + SocketMessage::IgnoreRule(identifier, ref id) => { + let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock(); let mut should_push = true; - for f in &*float_identifiers { - if let MatchingRule::Simple(f) = f { - if f.id.eq(id) { + for i in &*ignore_identifiers { + if let MatchingRule::Simple(i) = i { + if i.id.eq(id) { should_push = false; } } } if should_push { - float_identifiers.push(MatchingRule::Simple(IdWithIdentifier { + ignore_identifiers.push(MatchingRule::Simple(IdWithIdentifier { kind: identifier, id: id.clone(), matching_strategy: Option::from(MatchingStrategy::Legacy), @@ -1395,7 +1395,7 @@ impl WindowManager { } } - border_manager::send_notification(); + border_manager::send_notification(None); } } SocketMessage::BorderColour(kind, r, g, b) => match kind { @@ -1411,6 +1411,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); @@ -1540,7 +1543,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 fc3970a7e..30a8a952c 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; @@ -101,6 +102,10 @@ impl WindowManager { } if !transparency_override { + if rule_debug.matches_ignore_identifier.is_some() { + border_manager::send_notification(Option::from(event.hwnd())); + } + return Ok(()); } } @@ -149,14 +154,6 @@ impl WindowManager { _ => {} } - for monitor in self.monitors_mut() { - for workspace in monitor.workspaces_mut() { - if let WindowManagerEvent::FocusChange(_, window) = event { - let _ = workspace.focus_changed(window.hwnd); - } - } - } - self.enforce_workspace_rules()?; if matches!(event, WindowManagerEvent::MouseCapture(..)) { @@ -246,24 +243,31 @@ impl WindowManager { self.update_focused_workspace(self.mouse_follows_focus, false)?; let workspace = self.focused_workspace_mut()?; - if !workspace + let floating_window_idx = workspace .floating_windows() .iter() - .any(|w| w.hwnd == window.hwnd) - { - if let Some(w) = workspace.maximized_window() { - if w.hwnd == window.hwnd { - return Ok(()); + .position(|w| w.hwnd == window.hwnd); + + match floating_window_idx { + None => { + if let Some(w) = workspace.maximized_window() { + if w.hwnd == window.hwnd { + return Ok(()); + } } - } - if let Some(monocle) = workspace.monocle_container() { - if let Some(window) = monocle.focused_window() { + if let Some(monocle) = workspace.monocle_container() { + if let Some(window) = monocle.focused_window() { + window.focus(false)?; + } + } else { + workspace.focus_container_by_window(window.hwnd)?; + } + } + Some(idx) => { + if let Some(window) = workspace.floating_windows().get(idx) { window.focus(false)?; } - } else { - self.focused_workspace_mut()? - .focus_container_by_window(window.hwnd)?; } } } @@ -336,19 +340,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)?; + let floating_applications = FLOATING_APPLICATIONS.lock(); + let regex_identifiers = REGEX_IDENTIFIERS.lock(); + let mut should_float = false; + + 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(); } - 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(); + } + + 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 +671,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 bf771e459..283417254 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 1ca3633a5..2114bc6cd 100644 --- a/komorebi/src/static_config.rs +++ b/komorebi/src/static_config.rs @@ -35,8 +35,9 @@ use crate::DATA_DIR; use crate::DEFAULT_CONTAINER_PADDING; use crate::DEFAULT_WORKSPACE_PADDING; use crate::DISPLAY_INDEX_PREFERENCES; -use crate::FLOAT_IDENTIFIERS; +use crate::FLOATING_APPLICATIONS; use crate::HIDING_BEHAVIOUR; +use crate::IGNORE_IDENTIFIERS; use crate::LAYERED_WHITELIST; use crate::MANAGE_IDENTIFIERS; use crate::MONITOR_INDEX_PREFERENCES; @@ -304,10 +305,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>, @@ -371,6 +376,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) @@ -392,6 +399,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) @@ -545,7 +554,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, @@ -654,7 +664,7 @@ impl StaticConfig { } } - border_manager::send_notification(); + border_manager::send_notification(None); } transparency_manager::TRANSPARENCY_ENABLED @@ -662,7 +672,7 @@ impl StaticConfig { transparency_manager::TRANSPARENCY_ALPHA .store(self.transparency_alpha.unwrap_or(200), Ordering::SeqCst); - let mut float_identifiers = FLOAT_IDENTIFIERS.lock(); + let mut ignore_identifiers = IGNORE_IDENTIFIERS.lock(); let mut regex_identifiers = REGEX_IDENTIFIERS.lock(); let mut manage_identifiers = MANAGE_IDENTIFIERS.lock(); let mut tray_and_multi_window_identifiers = TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock(); @@ -670,9 +680,14 @@ impl StaticConfig { let mut layered_identifiers = LAYERED_WHITELIST.lock(); let mut transparency_blacklist = TRANSPARENCY_BLACKLIST.lock(); let mut slow_application_identifiers = SLOW_APPLICATION_IDENTIFIERS.lock(); + let mut floating_applications = FLOATING_APPLICATIONS.lock(); + + if let Some(rules) = &mut self.ignore_rules { + populate_rules(rules, &mut ignore_identifiers, &mut regex_identifiers)?; + } - if let Some(rules) = &mut self.float_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 { @@ -752,6 +767,7 @@ impl StaticConfig { single_border, stack_border, monocle_border, + floating_border, unfocused_border, stackbar_focused_text, stackbar_unfocused_text, @@ -762,6 +778,7 @@ impl StaticConfig { single_border, stack_border, monocle_border, + floating_border, unfocused_border, stackbar_focused_text, stackbar_unfocused_text, @@ -780,6 +797,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()); @@ -800,6 +821,7 @@ impl StaticConfig { single_border, stack_border, monocle_border, + floating_border, unfocused_border, stackbar_focused_text, stackbar_unfocused_text, @@ -811,6 +833,7 @@ impl StaticConfig { single_border, stack_border, monocle_border, + floating_border, unfocused_border, stackbar_focused_text, stackbar_unfocused_text, @@ -833,6 +856,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); @@ -849,6 +876,7 @@ impl StaticConfig { single_border, stack_border, monocle_border, + floating_border, unfocused_border, stackbar_focused_text, stackbar_unfocused_text, @@ -861,6 +889,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); @@ -886,8 +916,8 @@ impl StaticConfig { let asc = ApplicationConfigurationGenerator::load(&content)?; for mut entry in asc { - if let Some(rules) = &mut entry.float_identifiers { - populate_rules(rules, &mut float_identifiers, &mut regex_identifiers)?; + if let Some(rules) = &mut entry.ignore_identifiers { + populate_rules(rules, &mut ignore_identifiers, &mut regex_identifiers)?; } if let Some(ref options) = entry.options { diff --git a/komorebi/src/window.rs b/komorebi/src/window.rs index 4069d8e87..912becb9d 100644 --- a/komorebi/src/window.rs +++ b/komorebi/src/window.rs @@ -41,9 +41,9 @@ use crate::styles::WindowStyle; use crate::transparency_manager; use crate::window_manager_event::WindowManagerEvent; use crate::windows_api::WindowsApi; -use crate::FLOAT_IDENTIFIERS; use crate::HIDDEN_HWNDS; use crate::HIDING_BEHAVIOUR; +use crate::IGNORE_IDENTIFIERS; use crate::LAYERED_WHITELIST; use crate::MANAGE_IDENTIFIERS; use crate::NO_TITLEBAR; @@ -181,7 +181,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(); @@ -203,7 +203,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(); } @@ -537,7 +537,7 @@ pub struct RuleDebug { pub class: Option, pub path: Option, pub matches_permaignore_class: Option, - pub matches_float_identifier: Option, + pub matches_ignore_identifier: Option, pub matches_managed_override: Option, pub matches_layered_whitelist: Option, pub matches_wsl2_gui: Option, @@ -566,16 +566,16 @@ fn window_is_eligible( let regex_identifiers = REGEX_IDENTIFIERS.lock(); - let float_identifiers = FLOAT_IDENTIFIERS.lock(); + let ignore_identifiers = IGNORE_IDENTIFIERS.lock(); let should_float = if let Some(rule) = should_act( title, exe_name, class, path, - &float_identifiers, + &ignore_identifiers, ®ex_identifiers, ) { - debug.matches_float_identifier = Some(rule); + debug.matches_ignore_identifier = Some(rule); true } else { false diff --git a/komorebi/src/window_manager.rs b/komorebi/src/window_manager.rs index 4fefb4a5d..92f222a69 100644 --- a/komorebi/src/window_manager.rs +++ b/komorebi/src/window_manager.rs @@ -71,9 +71,9 @@ use crate::Rgb; use crate::CUSTOM_FFM; use crate::DATA_DIR; use crate::DISPLAY_INDEX_PREFERENCES; -use crate::FLOAT_IDENTIFIERS; use crate::HIDING_BEHAVIOUR; use crate::HOME_DIR; +use crate::IGNORE_IDENTIFIERS; use crate::LAYERED_WHITELIST; use crate::MANAGE_IDENTIFIERS; use crate::MONITOR_INDEX_PREFERENCES; @@ -136,7 +136,8 @@ pub struct GlobalState { pub stackbar_tab_width: i32, pub stackbar_height: i32, pub remove_titlebars: bool, - pub float_identifiers: Vec, + #[serde(alias = "float_identifiers")] + pub ignore_identifiers: Vec, pub manage_identifiers: Vec, pub layered_whitelist: Vec, pub tray_and_multi_window_identifiers: Vec, @@ -185,7 +186,7 @@ impl Default for GlobalState { stackbar_tab_width: STACKBAR_TAB_WIDTH.load(Ordering::SeqCst), stackbar_height: STACKBAR_TAB_HEIGHT.load(Ordering::SeqCst), remove_titlebars: REMOVE_TITLEBARS.load(Ordering::SeqCst), - float_identifiers: FLOAT_IDENTIFIERS.lock().clone(), + ignore_identifiers: IGNORE_IDENTIFIERS.lock().clone(), manage_identifiers: MANAGE_IDENTIFIERS.lock().clone(), layered_whitelist: LAYERED_WHITELIST.lock().clone(), tray_and_multi_window_identifiers: TRAY_AND_MULTI_WINDOW_IDENTIFIERS.lock().clone(), diff --git a/komorebi/src/workspace.rs b/komorebi/src/workspace.rs index 688c742f5..39a846e91 100644 --- a/komorebi/src/workspace.rs +++ b/komorebi/src/workspace.rs @@ -221,18 +221,19 @@ impl Workspace { container.restore(); } - for window in self.floating_windows() { - window.restore(); - } - if let Some(container) = self.focused_container_mut() { container.focus_window(container.focused_window_idx()); } + for window in self.floating_windows() { + window.restore(); + } + // Do this here to make sure that an error doesn't stop the restoration of other windows - // Maximised windows should always be drawn at the top of the Z order + // Maximised windows and floating windows should always be drawn at the top of the Z order + // when switching to a workspace if let Some(window) = to_focus { - if self.maximized_window().is_none() { + if self.maximized_window().is_none() && self.floating_windows().is_empty() { window.focus(mouse_follows_focus)?; } } @@ -393,26 +394,6 @@ impl Workspace { Ok(()) } - // focus_changed performs updates in response to the fact that a focus - // change event has occurred. The focus change is assumed to be valid, and - // should not result in a new focus change - the intent here is to update - // focus-reactive elements, such as the stackbar. - pub fn focus_changed(&mut self, hwnd: isize) -> Result<()> { - if !self.tile() { - return Ok(()); - } - - let containers = self.containers_mut(); - - for container in containers.iter_mut() { - if let Some(idx) = container.idx_for_window(hwnd) { - container.focus_window(idx); - container.restore(); - } - } - Ok(()) - } - pub fn reap_orphans(&mut self) -> Result<(usize, usize)> { let mut hwnds = vec![]; let mut floating_hwnds = vec![]; diff --git a/komorebi/src/workspace_reconciliator.rs b/komorebi/src/workspace_reconciliator.rs index 3842546a8..60cbf2fdb 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); } } } diff --git a/komorebic/src/main.rs b/komorebic/src/main.rs index 248cf6b83..0079f43fc 100644 --- a/komorebic/src/main.rs +++ b/komorebic/src/main.rs @@ -583,7 +583,7 @@ macro_rules! gen_application_target_subcommand_args { } gen_application_target_subcommand_args! { - FloatRule, + IgnoreRule, ManageRule, IdentifyTrayApplication, IdentifyLayeredApplication, @@ -1208,9 +1208,10 @@ enum SubCommand { /// Set the operation behaviour when the focused window is not managed #[clap(arg_required_else_help = true)] UnmanagedWindowOperationBehaviour(UnmanagedWindowOperationBehaviour), - /// Add a rule to always float the specified application + /// Add a rule to ignore the specified application #[clap(arg_required_else_help = true)] - FloatRule(FloatRule), + #[clap(alias = "float-rule")] + IgnoreRule(IgnoreRule), /// Add a rule to always manage the specified application #[clap(arg_required_else_help = true)] ManageRule(ManageRule), @@ -2154,8 +2155,8 @@ Stop-Process -Name:komorebi -ErrorAction SilentlyContinue } } } - SubCommand::FloatRule(arg) => { - send_message(&SocketMessage::FloatRule(arg.identifier, arg.id))?; + SubCommand::IgnoreRule(arg) => { + send_message(&SocketMessage::IgnoreRule(arg.identifier, arg.id))?; } SubCommand::ManageRule(arg) => { send_message(&SocketMessage::ManageRule(arg.identifier, arg.id))?;