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 <sashomasho@gmail.com>

* fixes

---------

Signed-off-by: Alex Yosifov <sashomasho@gmail.com>
Co-authored-by: Ivan Molodetskikh <yalterz@gmail.com>
This commit is contained in:
sashomasho
2025-06-11 09:05:14 +03:00
committed by GitHub
parent 0407ac5e4c
commit 8d7b22d1a8
6 changed files with 42 additions and 12 deletions
+3
View File
@@ -2351,6 +2351,8 @@ pub struct DebugConfig {
#[knuffel(child)] #[knuffel(child)]
pub honor_xdg_activation_with_invalid_serial: bool, pub honor_xdg_activation_with_invalid_serial: bool,
#[knuffel(child)] #[knuffel(child)]
pub deactivate_unfocused_windows: bool,
#[knuffel(child)]
pub skip_cursor_only_updates_during_vrr: bool, pub skip_cursor_only_updates_during_vrr: bool,
} }
@@ -5324,6 +5326,7 @@ mod tests {
disable_monitor_names: false, disable_monitor_names: false,
strict_new_window_focus_policy: false, strict_new_window_focus_policy: false,
honor_xdg_activation_with_invalid_serial: false, honor_xdg_activation_with_invalid_serial: false,
deactivate_unfocused_windows: false,
skip_cursor_only_updates_during_vrr: false, skip_cursor_only_updates_during_vrr: false,
}, },
workspaces: [ workspaces: [
+5 -2
View File
@@ -1090,7 +1090,7 @@ impl<W: LayoutElement> FloatingSpace<W> {
self.interactive_resize = None; 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(); let active = self.active_window_id.clone();
for tile in &mut self.tiles { for tile in &mut self.tiles {
let win = tile.window_mut(); let win = tile.window_mut();
@@ -1098,7 +1098,10 @@ impl<W: LayoutElement> FloatingSpace<W> {
win.set_active_in_column(true); win.set_active_in_column(true);
win.set_floating(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); win.set_activated(is_active);
let resize_data = self let resize_data = self
+6 -2
View File
@@ -361,6 +361,7 @@ pub struct Options {
// Debug flags. // Debug flags.
pub disable_resize_throttling: bool, pub disable_resize_throttling: bool,
pub disable_transactions: bool, pub disable_transactions: bool,
pub deactivate_unfocused_windows: bool,
} }
impl Default for Options { impl Default for Options {
@@ -393,6 +394,7 @@ impl Default for Options {
PresetSize::Proportion(0.5), PresetSize::Proportion(0.5),
PresetSize::Proportion(2. / 3.), PresetSize::Proportion(2. / 3.),
], ],
deactivate_unfocused_windows: false,
} }
} }
} }
@@ -658,6 +660,7 @@ impl Options {
overview: config.overview, overview: config.overview,
disable_resize_throttling: config.debug.disable_resize_throttling, disable_resize_throttling: config.debug.disable_resize_throttling,
disable_transactions: config.debug.disable_transactions, disable_transactions: config.debug.disable_transactions,
deactivate_unfocused_windows: config.debug.deactivate_unfocused_windows,
preset_window_heights, preset_window_heights,
} }
} }
@@ -5124,7 +5127,8 @@ impl<W: LayoutElement> Layout<W> {
} }
for (ws_idx, ws) in mon.workspaces.iter_mut().enumerate() { 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 { if let Some(is_scrolling) = ongoing_scrolling_dnd {
// Lock or unlock the view for scrolling interactive move. // Lock or unlock the view for scrolling interactive move.
@@ -5144,7 +5148,7 @@ impl<W: LayoutElement> Layout<W> {
} }
MonitorSet::NoOutputs { workspaces, .. } => { MonitorSet::NoOutputs { workspaces, .. } => {
for ws in workspaces { for ws in workspaces {
ws.refresh(false); ws.refresh(false, false);
ws.view_offset_gesture_end(None); ws.view_offset_gesture_end(None);
} }
} }
+7 -4
View File
@@ -3458,7 +3458,7 @@ impl<W: LayoutElement> ScrollingSpace<W> {
self.interactive_resize = None; 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() { for (col_idx, col) in self.columns.iter_mut().enumerate() {
let mut col_resize_data = None; let mut col_resize_data = None;
if let Some(resize) = &self.interactive_resize { if let Some(resize) = &self.interactive_resize {
@@ -3503,11 +3503,14 @@ impl<W: LayoutElement> ScrollingSpace<W> {
win.set_active_in_column(active_in_column); win.set_active_in_column(active_in_column);
win.set_floating(false); win.set_floating(false);
let active = is_active let mut active = is_active && self.active_column_idx == col_idx;
&& 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 // In tabbed mode, all tabs have activated state to reduce unnecessary
// animations when switching tabs. // animations when switching tabs.
&& (active_in_column || is_tabbed); active &= active_in_column || is_tabbed;
}
win.set_activated(active); win.set_activated(active);
win.set_interactive_resize(col_resize_data); win.set_interactive_resize(col_resize_data);
+3 -3
View File
@@ -1600,11 +1600,11 @@ impl<W: LayoutElement> Workspace<W> {
} }
} }
pub fn refresh(&mut self, is_active: bool) { pub fn refresh(&mut self, is_active: bool, is_focused: bool) {
self.scrolling self.scrolling
.refresh(is_active && !self.floating_is_active.get()); .refresh(is_active && !self.floating_is_active.get(), is_focused);
self.floating 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 { pub fn scroll_amount_to_activate(&self, window: &W::Id) -> f64 {
+18 -1
View File
@@ -28,8 +28,9 @@ debug {
keep-laptop-panel-on-when-lid-is-closed keep-laptop-panel-on-when-lid-is-closed
disable-monitor-names disable-monitor-names
strict-new-window-focus-policy strict-new-window-focus-policy
honor-xdg-activation-with-invalid-serial honor-xdg-activation-with-invalid-serial
skip-cursor-only-updates-during-vrr skip-cursor-only-updates-during-vrr
deactivate-unfocused-windows
} }
binds { binds {
@@ -292,6 +293,22 @@ debug {
} }
``` ```
### `deactivate-unfocused-windows`
<sup>Since: next release</sup>
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 ### Key Bindings
These are not debug options, but rather key bindings. These are not debug options, but rather key bindings.