Implement bind cooldown-ms

This commit is contained in:
Ivan Molodetskikh 2024-03-22 20:47:40 +04:00
parent 6c08ba307a
commit b06e51da60
3 changed files with 101 additions and 9 deletions

View File

@ -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 {

View File

@ -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,
},
]);

View File

@ -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,