aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Molodetskikh <yalterz@gmail.com>2024-03-22 20:47:40 +0400
committerIvan Molodetskikh <yalterz@gmail.com>2024-03-22 20:47:40 +0400
commitb06e51da60505bba42ef310d510f38440b897f9b (patch)
treea7440c6ea6f59dd2fbdf2e2771a562640c949a05
parent6c08ba307aef03938e009601ac81bc140c38a1dd (diff)
downloadniri-b06e51da60505bba42ef310d510f38440b897f9b.tar.gz
niri-b06e51da60505bba42ef310d510f38440b897f9b.tar.bz2
niri-b06e51da60505bba42ef310d510f38440b897f9b.zip
Implement bind cooldown-ms
-rw-r--r--niri-config/src/lib.rs53
-rw-r--r--src/input.rs53
-rw-r--r--src/niri.rs4
3 files changed, 101 insertions, 9 deletions
diff --git a/niri-config/src/lib.rs b/niri-config/src/lib.rs
index 64904d3c..7a9c0b50 100644
--- a/niri-config/src/lib.rs
+++ b/niri-config/src/lib.rs
@@ -5,6 +5,7 @@ use std::collections::HashSet;
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use std::str::FromStr;
+use std::time::Duration;
use bitflags::bitflags;
use knuffel::errors::DecodeError;
@@ -720,6 +721,7 @@ pub struct Binds(pub Vec<Bind>);
pub struct Bind {
pub key: Key,
pub action: Action,
+ pub cooldown: Option<Duration>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
@@ -1405,13 +1407,45 @@ where
node: &knuffel::ast::SpannedNode<S>,
ctx: &mut knuffel::decode::Context<S>,
) -> Result<Self, DecodeError<S>> {
- expect_only_children(node, ctx);
+ if let Some(type_name) = &node.type_name {
+ ctx.emit_error(DecodeError::unexpected(
+ type_name,
+ "type name",
+ "no type name expected for this node",
+ ));
+ }
+
+ for val in node.arguments.iter() {
+ ctx.emit_error(DecodeError::unexpected(
+ &val.literal,
+ "argument",
+ "no arguments expected for this node",
+ ))
+ }
let key = node
.node_name
.parse::<Key>()
.map_err(|e| DecodeError::conversion(&node.node_name, e.wrap_err("invalid keybind")))?;
+ let mut cooldown = None;
+ for (name, val) in &node.properties {
+ match &***name {
+ "cooldown-ms" => {
+ cooldown = Some(Duration::from_millis(
+ knuffel::traits::DecodeScalar::decode(val, ctx)?,
+ ));
+ }
+ name_str => {
+ ctx.emit_error(DecodeError::unexpected(
+ name,
+ "property",
+ format!("unexpected property `{}`", name_str.escape_default()),
+ ));
+ }
+ }
+ }
+
let mut children = node.children();
// If the action is invalid but the key is fine, we still want to return something.
@@ -1420,6 +1454,7 @@ where
let dummy = Self {
key,
action: Action::Spawn(vec![]),
+ cooldown: None,
};
if let Some(child) = children.next() {
@@ -1431,7 +1466,11 @@ where
));
}
match Action::decode_node(child, ctx) {
- Ok(action) => Ok(Self { key, action }),
+ Ok(action) => Ok(Self {
+ key,
+ action,
+ cooldown,
+ }),
Err(e) => {
ctx.emit_error(e);
Ok(dummy)
@@ -1732,7 +1771,7 @@ mod tests {
Mod+Comma { consume-window-into-column; }
Mod+1 { focus-workspace 1; }
Mod+Shift+E { quit skip-confirmation=true; }
- Mod+WheelDown { focus-workspace-down; }
+ Mod+WheelDown cooldown-ms=150 { focus-workspace-down; }
}
debug {
@@ -1920,6 +1959,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR,
},
action: Action::Spawn(vec!["alacritty".to_owned()]),
+ cooldown: None,
},
Bind {
key: Key {
@@ -1927,6 +1967,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR,
},
action: Action::CloseWindow,
+ cooldown: None,
},
Bind {
key: Key {
@@ -1934,6 +1975,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
},
action: Action::FocusMonitorLeft,
+ cooldown: None,
},
Bind {
key: Key {
@@ -1941,6 +1983,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL,
},
action: Action::MoveWindowToMonitorRight,
+ cooldown: None,
},
Bind {
key: Key {
@@ -1948,6 +1991,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR,
},
action: Action::ConsumeWindowIntoColumn,
+ cooldown: None,
},
Bind {
key: Key {
@@ -1955,6 +1999,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR,
},
action: Action::FocusWorkspace(1),
+ cooldown: None,
},
Bind {
key: Key {
@@ -1962,6 +2007,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
},
action: Action::Quit(true),
+ cooldown: None,
},
Bind {
key: Key {
@@ -1969,6 +2015,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR,
},
action: Action::FocusWorkspaceDown,
+ cooldown: Some(Duration::from_millis(150)),
},
]),
debug: DebugConfig {
diff --git a/src/input.rs b/src/input.rs
index aa08d580..b4e44974 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -1,7 +1,9 @@
use std::any::Any;
+use std::collections::hash_map::Entry;
use std::collections::HashSet;
use std::time::Duration;
+use calloop::timer::{TimeoutAction, Timer};
use input::event::gesture::GestureEventCoordinates as _;
use niri_config::{Action, Bind, Binds, Key, Modifiers, Trigger};
use niri_ipc::LayoutSwitchTarget;
@@ -289,7 +291,40 @@ impl State {
return;
}
- self.do_action(bind.action);
+ self.handle_bind(bind);
+ }
+
+ pub fn handle_bind(&mut self, bind: Bind) {
+ let Some(cooldown) = bind.cooldown else {
+ self.do_action(bind.action);
+ return;
+ };
+
+ // Check this first so that it doesn't trigger the cooldown.
+ if self.niri.is_locked() && !allowed_when_locked(&bind.action) {
+ return;
+ }
+
+ match self.niri.bind_cooldown_timers.entry(bind.key) {
+ // The bind is on cooldown.
+ Entry::Occupied(_) => (),
+ Entry::Vacant(entry) => {
+ let timer = Timer::from_duration(cooldown);
+ let token = self
+ .niri
+ .event_loop
+ .insert_source(timer, move |_, _, state| {
+ if state.niri.bind_cooldown_timers.remove(&bind.key).is_none() {
+ error!("bind cooldown timer entry disappeared");
+ }
+ TimeoutAction::Drop
+ })
+ .unwrap();
+ entry.insert(token);
+
+ self.do_action(bind.action);
+ }
+ }
}
pub fn do_action(&mut self, action: Action) {
@@ -1100,12 +1135,12 @@ impl State {
let ticks = self.niri.horizontal_wheel_tracker.accumulate(v120);
if let Some(right) = bind_right {
for _ in 0..ticks {
- self.do_action(right.action.clone());
+ self.handle_bind(right.clone());
}
}
if let Some(left) = bind_left {
for _ in ticks..0 {
- self.do_action(left.action.clone());
+ self.handle_bind(left.clone());
}
}
return;
@@ -1125,12 +1160,12 @@ impl State {
let ticks = self.niri.vertical_wheel_tracker.accumulate(v120);
if let Some(down) = bind_down {
for _ in 0..ticks {
- self.do_action(down.action.clone());
+ self.handle_bind(down.clone());
}
}
if let Some(up) = bind_up {
for _ in ticks..0 {
- self.do_action(up.action.clone());
+ self.handle_bind(up.clone());
}
}
return;
@@ -1725,6 +1760,7 @@ fn should_intercept_key(
modifiers: Modifiers::empty(),
},
action,
+ cooldown: None,
});
}
}
@@ -1772,6 +1808,7 @@ fn find_bind(
modifiers: Modifiers::empty(),
},
action,
+ cooldown: None,
});
}
@@ -1991,6 +2028,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR | Modifiers::CTRL,
},
action: Action::CloseWindow,
+ cooldown: None,
}]);
let comp_mod = CompositorMod::Super;
@@ -2122,6 +2160,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR,
},
action: Action::CloseWindow,
+ cooldown: None,
},
Bind {
key: Key {
@@ -2129,6 +2168,7 @@ mod tests {
modifiers: Modifiers::SUPER,
},
action: Action::FocusColumnLeft,
+ cooldown: None,
},
Bind {
key: Key {
@@ -2136,6 +2176,7 @@ mod tests {
modifiers: Modifiers::empty(),
},
action: Action::FocusWindowDown,
+ cooldown: None,
},
Bind {
key: Key {
@@ -2143,6 +2184,7 @@ mod tests {
modifiers: Modifiers::COMPOSITOR | Modifiers::SUPER,
},
action: Action::FocusWindowUp,
+ cooldown: None,
},
Bind {
key: Key {
@@ -2150,6 +2192,7 @@ mod tests {
modifiers: Modifiers::SUPER | Modifiers::ALT,
},
action: Action::FocusColumnRight,
+ cooldown: None,
},
]);
diff --git a/src/niri.rs b/src/niri.rs
index babd961c..49592cf6 100644
--- a/src/niri.rs
+++ b/src/niri.rs
@@ -11,7 +11,7 @@ use std::{env, mem, thread};
use _server_decoration::server::org_kde_kwin_server_decoration_manager::Mode as KdeDecorationsMode;
use anyhow::{ensure, Context};
use calloop::futures::Scheduler;
-use niri_config::{Config, TrackLayout};
+use niri_config::{Config, Key, TrackLayout};
use smithay::backend::allocator::Fourcc;
use smithay::backend::renderer::element::memory::MemoryRenderBufferRenderElement;
use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement};
@@ -194,6 +194,7 @@ pub struct Niri {
pub seat: Seat<State>,
/// Scancodes of the keys to suppress.
pub suppressed_keys: HashSet<u32>,
+ pub bind_cooldown_timers: HashMap<Key, RegistrationToken>,
pub keyboard_focus: KeyboardFocus,
pub idle_inhibiting_surfaces: HashSet<WlSurface>,
pub is_fdo_idle_inhibited: Arc<AtomicBool>,
@@ -1251,6 +1252,7 @@ impl Niri {
popups: PopupManager::default(),
popup_grab: None,
suppressed_keys: HashSet::new(),
+ bind_cooldown_timers: HashMap::new(),
presentation_state,
security_context_state,
gamma_control_manager_state,