From 8d7b22d1a8968a46286fdd13ca9d8d2c88e10e48 Mon Sep 17 00:00:00 2001 From: sashomasho Date: Wed, 11 Jun 2025 09:05:14 +0300 Subject: Add deactivate-unfocused-windows debug flag (#1706) * force xdg deactivation on invisable workspaces This debug option provides a workaround for many Chromium-based chat applications that fail to show notifications when they're active in a workspace that's not currently visible and don't have keyboard focus Signed-off-by: Alex Yosifov * fixes --------- Signed-off-by: Alex Yosifov Co-authored-by: Ivan Molodetskikh --- niri-config/src/lib.rs | 3 +++ src/layout/floating.rs | 7 +++++-- src/layout/mod.rs | 8 ++++++-- src/layout/scrolling.rs | 11 +++++++---- src/layout/workspace.rs | 6 +++--- wiki/Configuration:-Debug-Options.md | 19 ++++++++++++++++++- 6 files changed, 42 insertions(+), 12 deletions(-) diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index 1fb46c71..67a621de 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -2351,6 +2351,8 @@ pub struct DebugConfig { #[knuffel(child)] pub honor_xdg_activation_with_invalid_serial: bool, #[knuffel(child)] + pub deactivate_unfocused_windows: bool, + #[knuffel(child)] pub skip_cursor_only_updates_during_vrr: bool, } @@ -5324,6 +5326,7 @@ mod tests { disable_monitor_names: false, strict_new_window_focus_policy: false, honor_xdg_activation_with_invalid_serial: false, + deactivate_unfocused_windows: false, skip_cursor_only_updates_during_vrr: false, }, workspaces: [ diff --git a/src/layout/floating.rs b/src/layout/floating.rs index 7d7e7fc0..98927c0d 100644 --- a/src/layout/floating.rs +++ b/src/layout/floating.rs @@ -1090,7 +1090,7 @@ impl FloatingSpace { self.interactive_resize = None; } - pub fn refresh(&mut self, is_active: bool) { + pub fn refresh(&mut self, is_active: bool, is_focused: bool) { let active = self.active_window_id.clone(); for tile in &mut self.tiles { let win = tile.window_mut(); @@ -1098,7 +1098,10 @@ impl FloatingSpace { win.set_active_in_column(true); win.set_floating(true); - let is_active = is_active && Some(win.id()) == active.as_ref(); + let mut is_active = is_active && Some(win.id()) == active.as_ref(); + if self.options.deactivate_unfocused_windows { + is_active &= is_focused; + } win.set_activated(is_active); let resize_data = self diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 9b0002e0..5deda707 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -361,6 +361,7 @@ pub struct Options { // Debug flags. pub disable_resize_throttling: bool, pub disable_transactions: bool, + pub deactivate_unfocused_windows: bool, } impl Default for Options { @@ -393,6 +394,7 @@ impl Default for Options { PresetSize::Proportion(0.5), PresetSize::Proportion(2. / 3.), ], + deactivate_unfocused_windows: false, } } } @@ -658,6 +660,7 @@ impl Options { overview: config.overview, disable_resize_throttling: config.debug.disable_resize_throttling, disable_transactions: config.debug.disable_transactions, + deactivate_unfocused_windows: config.debug.deactivate_unfocused_windows, preset_window_heights, } } @@ -5124,7 +5127,8 @@ impl Layout { } for (ws_idx, ws) in mon.workspaces.iter_mut().enumerate() { - ws.refresh(is_active); + let is_focused = is_active && ws_idx == mon.active_workspace_idx; + ws.refresh(is_active, is_focused); if let Some(is_scrolling) = ongoing_scrolling_dnd { // Lock or unlock the view for scrolling interactive move. @@ -5144,7 +5148,7 @@ impl Layout { } MonitorSet::NoOutputs { workspaces, .. } => { for ws in workspaces { - ws.refresh(false); + ws.refresh(false, false); ws.view_offset_gesture_end(None); } } diff --git a/src/layout/scrolling.rs b/src/layout/scrolling.rs index 1b4d91c5..0c70fc7a 100644 --- a/src/layout/scrolling.rs +++ b/src/layout/scrolling.rs @@ -3458,7 +3458,7 @@ impl ScrollingSpace { self.interactive_resize = None; } - pub fn refresh(&mut self, is_active: bool) { + pub fn refresh(&mut self, is_active: bool, is_focused: bool) { for (col_idx, col) in self.columns.iter_mut().enumerate() { let mut col_resize_data = None; if let Some(resize) = &self.interactive_resize { @@ -3503,11 +3503,14 @@ impl ScrollingSpace { win.set_active_in_column(active_in_column); win.set_floating(false); - let active = is_active - && self.active_column_idx == col_idx + let mut active = is_active && self.active_column_idx == col_idx; + if self.options.deactivate_unfocused_windows { + active &= active_in_column && is_focused; + } else { // In tabbed mode, all tabs have activated state to reduce unnecessary // animations when switching tabs. - && (active_in_column || is_tabbed); + active &= active_in_column || is_tabbed; + } win.set_activated(active); win.set_interactive_resize(col_resize_data); diff --git a/src/layout/workspace.rs b/src/layout/workspace.rs index 3f5a07c5..588a6971 100644 --- a/src/layout/workspace.rs +++ b/src/layout/workspace.rs @@ -1600,11 +1600,11 @@ impl Workspace { } } - pub fn refresh(&mut self, is_active: bool) { + pub fn refresh(&mut self, is_active: bool, is_focused: bool) { self.scrolling - .refresh(is_active && !self.floating_is_active.get()); + .refresh(is_active && !self.floating_is_active.get(), is_focused); self.floating - .refresh(is_active && self.floating_is_active.get()); + .refresh(is_active && self.floating_is_active.get(), is_focused); } pub fn scroll_amount_to_activate(&self, window: &W::Id) -> f64 { diff --git a/wiki/Configuration:-Debug-Options.md b/wiki/Configuration:-Debug-Options.md index cf59d8b6..299437ca 100644 --- a/wiki/Configuration:-Debug-Options.md +++ b/wiki/Configuration:-Debug-Options.md @@ -28,8 +28,9 @@ debug { keep-laptop-panel-on-when-lid-is-closed disable-monitor-names strict-new-window-focus-policy - honor-xdg-activation-with-invalid-serial + honor-xdg-activation-with-invalid-serial skip-cursor-only-updates-during-vrr + deactivate-unfocused-windows } binds { @@ -292,6 +293,22 @@ debug { } ``` +### `deactivate-unfocused-windows` + +Since: next release + +Some clients (notably, Chromium- and Electron-based, like Teams or Slack) erroneously use the Activated xdg window state instead of keyboard focus for things like deciding whether to send notifications for new messages, or for picking where to show an IME popup. +Niri keeps the Activated state on unfocused workspaces and invisible tabbed windows (to reduce unwanted animations), surfacing bugs in these applications. + +Set this debug flag to work around these problems. +It will cause niri to drop the Activated state for all unfocused windows. + +```kdl +debug { + deactivate-unfocused-windows +} +``` + ### Key Bindings These are not debug options, but rather key bindings. -- cgit