mirror of
https://github.com/niri-wm/niri.git
synced 2026-06-24 02:01:18 +07:00
Implement bind cooldown-ms
This commit is contained in:
+50
-3
@@ -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 {
|
||||
|
||||
+47
-4
@@ -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.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,
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
+3
-1
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user