diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs index b85b0011..fae5129e 100644 --- a/niri-config/src/lib.rs +++ b/niri-config/src/lib.rs @@ -1279,6 +1279,7 @@ pub enum Action { id: u64, write_to_disk: bool, }, + ToggleKeyboardShortcutsInhibit, CloseWindow, #[knuffel(skip)] CloseWindowById(u64), @@ -3078,6 +3079,12 @@ where } } + // The toggle-inhibit action must always be uninhibitable. + // Otherwise, it would be impossible to trigger it. + if matches!(action, Action::ToggleKeyboardShortcutsInhibit) { + allow_inhibiting = false; + } + Ok(Self { key, action, @@ -3470,6 +3477,8 @@ mod tests { } binds { + Mod+Escape { toggle-keyboard-shortcuts-inhibit; } + Mod+Shift+Escape allow-inhibiting=true { toggle-keyboard-shortcuts-inhibit; } Mod+T allow-when-locked=true { spawn "alacritty"; } Mod+Q { close-window; } Mod+Shift+H { focus-monitor-left; } @@ -3786,6 +3795,28 @@ mod tests { }, ], binds: Binds(vec![ + Bind { + key: Key { + trigger: Trigger::Keysym(Keysym::Escape), + modifiers: Modifiers::COMPOSITOR, + }, + action: Action::ToggleKeyboardShortcutsInhibit, + repeat: true, + cooldown: None, + allow_when_locked: false, + allow_inhibiting: false, + }, + Bind { + key: Key { + trigger: Trigger::Keysym(Keysym::Escape), + modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT, + }, + action: Action::ToggleKeyboardShortcutsInhibit, + repeat: true, + cooldown: None, + allow_when_locked: false, + allow_inhibiting: false, + }, Bind { key: Key { trigger: Trigger::Keysym(Keysym::t), diff --git a/resources/default-config.kdl b/resources/default-config.kdl index 5936a80e..a3560ab8 100644 --- a/resources/default-config.kdl +++ b/resources/default-config.kdl @@ -536,6 +536,16 @@ binds { Ctrl+Print { screenshot-screen; } Alt+Print { screenshot-window; } + // Applications such as remote-desktop clients and software KVM switches may + // request that niri stops processing the keyboard shortcuts defined here + // so they may, for example, forward the key presses as-is to a remote machine. + // It's a good idea to bind an escape hatch to toggle the inhibitor, + // so a buggy application can't hold your session hostage. + // + // The allow-inhibiting=false property can be applied to other binds as well, + // which ensures niri always processes them, even when an inhibitor is active. + Mod+Escape allow-inhibiting=false { toggle-keyboard-shortcuts-inhibit; } + // The quit action will show a confirmation dialog to avoid accidental exits. Mod+Shift+E { quit; } Ctrl+Alt+Delete { quit; } diff --git a/src/input/mod.rs b/src/input/mod.rs index 9bcfc1ea..ac3b6fc4 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -626,6 +626,19 @@ impl State { }); } } + Action::ToggleKeyboardShortcutsInhibit => { + if let Some(inhibitor) = self.niri.keyboard_focus.surface().and_then(|surface| { + self.niri + .keyboard_shortcuts_inhibiting_surfaces + .get(surface) + }) { + if inhibitor.is_active() { + inhibitor.inactivate(); + } else { + inhibitor.activate(); + } + } + } Action::CloseWindow => { if let Some(mapped) = self.niri.layout.focus() { mapped.toplevel().send_close(); @@ -3071,6 +3084,7 @@ fn allowed_when_locked(action: &Action) -> bool { | Action::PowerOffMonitors | Action::PowerOnMonitors | Action::SwitchLayout(_) + | Action::ToggleKeyboardShortcutsInhibit ) }