Compare commits

...

10 Commits

Author SHA1 Message Date
Ivan Molodetskikh
54e6a01284 Allow clippy false positive harder 2024-03-22 21:24:11 +04:00
Ivan Molodetskikh
7721e3fc44 Allow clippy false positive 2024-03-22 21:14:03 +04:00
Ivan Molodetskikh
0d2fdb49ef default-config: Add mouse wheel binds 2024-03-22 20:56:20 +04:00
Ivan Molodetskikh
b06e51da60 Implement bind cooldown-ms 2024-03-22 20:47:40 +04:00
Ivan Molodetskikh
6c08ba307a input: Make functions return the whole bind 2024-03-22 20:47:35 +04:00
Ivan Molodetskikh
4b2fdd0776 Implement mouse wheel bindings 2024-03-22 13:10:40 +04:00
Ivan Molodetskikh
969519b5d8 input: Generalize bound_action() to Trigger 2024-03-22 11:11:45 +04:00
Ivan Molodetskikh
a0c8c39b06 Make binds accept wheel names 2024-03-22 10:36:19 +04:00
Ivan Molodetskikh
977f1487c2 input: Fix discrete axis value on winit 2024-03-22 09:41:10 +04:00
Ivan Molodetskikh
fbe021fbdf input: Rename discrete => v120 2024-03-22 09:35:17 +04:00
9 changed files with 401 additions and 114 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;
@ -716,18 +717,28 @@ impl PartialEq for Match {
#[derive(Debug, Default, PartialEq)]
pub struct Binds(pub Vec<Bind>);
#[derive(Debug, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct Bind {
pub key: Key,
pub action: Action,
pub cooldown: Option<Duration>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub struct Key {
pub keysym: Keysym,
pub trigger: Trigger,
pub modifiers: Modifiers,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
pub enum Trigger {
Keysym(Keysym),
WheelDown,
WheelUp,
WheelLeft,
WheelRight,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Modifiers : u8 {
@ -1396,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.
@ -1411,6 +1454,7 @@ where
let dummy = Self {
key,
action: Action::Spawn(vec![]),
cooldown: None,
};
if let Some(child) = children.next() {
@ -1422,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)
@ -1499,12 +1547,23 @@ impl FromStr for Key {
}
}
let keysym = keysym_from_name(key, KEYSYM_CASE_INSENSITIVE);
if keysym.raw() == KEY_NoSymbol {
return Err(miette!("invalid key: {key}"));
}
let trigger = if key.eq_ignore_ascii_case("WheelDown") {
Trigger::WheelDown
} else if key.eq_ignore_ascii_case("WheelUp") {
Trigger::WheelUp
} else if key.eq_ignore_ascii_case("WheelLeft") {
Trigger::WheelLeft
} else if key.eq_ignore_ascii_case("WheelRight") {
Trigger::WheelRight
} else {
let keysym = keysym_from_name(key, KEYSYM_CASE_INSENSITIVE);
if keysym.raw() == KEY_NoSymbol {
return Err(miette!("invalid key: {key}"));
}
Trigger::Keysym(keysym)
};
Ok(Key { keysym, modifiers })
Ok(Key { trigger, modifiers })
}
}
@ -1712,6 +1771,7 @@ mod tests {
Mod+Comma { consume-window-into-column; }
Mod+1 { focus-workspace 1; }
Mod+Shift+E { quit skip-confirmation=true; }
Mod+WheelDown cooldown-ms=150 { focus-workspace-down; }
}
debug {
@ -1895,52 +1955,67 @@ mod tests {
binds: Binds(vec![
Bind {
key: Key {
keysym: Keysym::t,
trigger: Trigger::Keysym(Keysym::t),
modifiers: Modifiers::COMPOSITOR,
},
action: Action::Spawn(vec!["alacritty".to_owned()]),
cooldown: None,
},
Bind {
key: Key {
keysym: Keysym::q,
trigger: Trigger::Keysym(Keysym::q),
modifiers: Modifiers::COMPOSITOR,
},
action: Action::CloseWindow,
cooldown: None,
},
Bind {
key: Key {
keysym: Keysym::h,
trigger: Trigger::Keysym(Keysym::h),
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
},
action: Action::FocusMonitorLeft,
cooldown: None,
},
Bind {
key: Key {
keysym: Keysym::l,
trigger: Trigger::Keysym(Keysym::l),
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT | Modifiers::CTRL,
},
action: Action::MoveWindowToMonitorRight,
cooldown: None,
},
Bind {
key: Key {
keysym: Keysym::comma,
trigger: Trigger::Keysym(Keysym::comma),
modifiers: Modifiers::COMPOSITOR,
},
action: Action::ConsumeWindowIntoColumn,
cooldown: None,
},
Bind {
key: Key {
keysym: Keysym::_1,
trigger: Trigger::Keysym(Keysym::_1),
modifiers: Modifiers::COMPOSITOR,
},
action: Action::FocusWorkspace(1),
cooldown: None,
},
Bind {
key: Key {
keysym: Keysym::e,
trigger: Trigger::Keysym(Keysym::e),
modifiers: Modifiers::COMPOSITOR | Modifiers::SHIFT,
},
action: Action::Quit(true),
cooldown: None,
},
Bind {
key: Key {
trigger: Trigger::WheelDown,
modifiers: Modifiers::COMPOSITOR,
},
action: Action::FocusWorkspaceDown,
cooldown: Some(Duration::from_millis(150)),
},
]),
debug: DebugConfig {

View File

@ -519,6 +519,27 @@ binds {
Mod+Shift+U { move-workspace-down; }
Mod+Shift+I { move-workspace-up; }
// You can bind mouse wheel scroll ticks using the following syntax.
// To avoid scrolling through workspaces really fast, you can use
// the cooldown-ms property. The bind will be rate-limited to this value.
// You can set a cooldown on any bind, but it's most useful for the wheel.
Mod+WheelDown cooldown-ms=150 { focus-workspace-down; }
Mod+WheelUp cooldown-ms=150 { focus-workspace-up; }
Mod+Ctrl+WheelDown cooldown-ms=150 { move-column-to-workspace-down; }
Mod+Ctrl+WheelUp cooldown-ms=150 { move-column-to-workspace-up; }
Mod+WheelRight { focus-column-right; }
Mod+WheelLeft { focus-column-left; }
Mod+Ctrl+WheelRight { move-column-right; }
Mod+Ctrl+WheelLeft { move-column-left; }
// Usually scrolling up and down with Shift in applications results in
// horizontal scrolling; these binds replicate that.
Mod+Shift+WheelDown { focus-column-right; }
Mod+Shift+WheelUp { focus-column-left; }
Mod+Ctrl+Shift+WheelDown { move-column-right; }
Mod+Ctrl+Shift+WheelUp { move-column-left; }
// You can refer to workspaces by index. However, keep in mind that
// niri is a dynamic workspace system, so these commands are kind of
// "best effort". Trying to refer to a workspace index bigger than

View File

@ -1,9 +1,11 @@
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, Binds, Modifiers};
use niri_config::{Action, Bind, Binds, Key, Modifiers, Trigger};
use niri_ipc::LayoutSwitchTarget;
use smithay::backend::input::{
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Device, DeviceCapability, Event,
@ -248,7 +250,7 @@ impl State {
let time = Event::time_msec(&event);
let pressed = event.state() == KeyState::Pressed;
let Some(Some(action)) = self.niri.seat.get_keyboard().unwrap().input(
let Some(Some(bind)) = self.niri.seat.get_keyboard().unwrap().input(
self,
event.key_code(),
event.state(),
@ -289,7 +291,40 @@ impl State {
return;
}
self.do_action(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) {
@ -1078,30 +1113,92 @@ impl State {
fn on_pointer_axis<I: InputBackend>(&mut self, event: I::PointerAxisEvent) {
let source = event.source();
let horizontal_amount = event
.amount(Axis::Horizontal)
.unwrap_or_else(|| event.amount_v120(Axis::Horizontal).unwrap_or(0.0) * 3.0 / 120.);
let vertical_amount = event
.amount(Axis::Vertical)
.unwrap_or_else(|| event.amount_v120(Axis::Vertical).unwrap_or(0.0) * 3.0 / 120.);
let horizontal_amount_discrete = event.amount_v120(Axis::Horizontal);
let vertical_amount_discrete = event.amount_v120(Axis::Vertical);
let horizontal_amount_v120 = event.amount_v120(Axis::Horizontal);
let vertical_amount_v120 = event.amount_v120(Axis::Vertical);
// Handle wheel bindings.
if source == AxisSource::Wheel {
let comp_mod = self.backend.mod_key();
let mods = self.niri.seat.get_keyboard().unwrap().modifier_state();
if let Some(v120) = horizontal_amount_v120 {
let config = self.niri.config.borrow();
let bindings = &config.binds;
let bind_left = find_configured_bind(bindings, comp_mod, Trigger::WheelLeft, mods);
let bind_right =
find_configured_bind(bindings, comp_mod, Trigger::WheelRight, mods);
drop(config);
// If we have a bind with current modifiers along the scroll direction, then
// accumulate and don't pass to Wayland. If there's no bind, reset the accumulator.
if bind_left.is_some() || bind_right.is_some() {
let ticks = self.niri.horizontal_wheel_tracker.accumulate(v120);
if let Some(right) = bind_right {
for _ in 0..ticks {
self.handle_bind(right.clone());
}
}
if let Some(left) = bind_left {
for _ in ticks..0 {
self.handle_bind(left.clone());
}
}
return;
} else {
self.niri.horizontal_wheel_tracker.reset();
}
}
if let Some(v120) = vertical_amount_v120 {
let config = self.niri.config.borrow();
let bindings = &config.binds;
let bind_up = find_configured_bind(bindings, comp_mod, Trigger::WheelUp, mods);
let bind_down = find_configured_bind(bindings, comp_mod, Trigger::WheelDown, mods);
drop(config);
if bind_up.is_some() || bind_down.is_some() {
let ticks = self.niri.vertical_wheel_tracker.accumulate(v120);
if let Some(down) = bind_down {
for _ in 0..ticks {
self.handle_bind(down.clone());
}
}
if let Some(up) = bind_up {
for _ in ticks..0 {
self.handle_bind(up.clone());
}
}
return;
} else {
self.niri.vertical_wheel_tracker.reset();
}
}
}
let horizontal_amount = event.amount(Axis::Horizontal).unwrap_or_else(|| {
// Winit backend, discrete scrolling.
horizontal_amount_v120.unwrap_or(0.0) / 120. * 15.
});
let vertical_amount = event.amount(Axis::Vertical).unwrap_or_else(|| {
// Winit backend, discrete scrolling.
vertical_amount_v120.unwrap_or(0.0) / 120. * 15.
});
let mut frame = AxisFrame::new(event.time_msec()).source(source);
if horizontal_amount != 0.0 {
frame = frame
.relative_direction(Axis::Horizontal, event.relative_direction(Axis::Horizontal));
frame = frame.value(Axis::Horizontal, horizontal_amount);
if let Some(discrete) = horizontal_amount_discrete {
frame = frame.v120(Axis::Horizontal, discrete as i32);
if let Some(v120) = horizontal_amount_v120 {
frame = frame.v120(Axis::Horizontal, v120 as i32);
}
}
if vertical_amount != 0.0 {
frame =
frame.relative_direction(Axis::Vertical, event.relative_direction(Axis::Vertical));
frame = frame.value(Axis::Vertical, vertical_amount);
if let Some(discrete) = vertical_amount_discrete {
frame = frame.v120(Axis::Vertical, discrete as i32);
if let Some(v120) = vertical_amount_v120 {
frame = frame.v120(Axis::Vertical, v120 as i32);
}
}
@ -1626,7 +1723,7 @@ fn should_intercept_key(
mods: ModifiersState,
screenshot_ui: &ScreenshotUi,
disable_power_key_handling: bool,
) -> FilterResult<Option<Action>> {
) -> FilterResult<Option<Bind>> {
// Actions are only triggered on presses, release of the key
// shouldn't try to intercept anything unless we have marked
// the key to suppress.
@ -1634,7 +1731,7 @@ fn should_intercept_key(
return FilterResult::Forward;
}
let mut final_action = action(
let mut final_bind = find_bind(
bindings,
comp_mod,
modified,
@ -1648,21 +1745,31 @@ fn should_intercept_key(
if screenshot_ui.is_open() {
let mut use_screenshot_ui_action = true;
if let Some(action) = &final_action {
if allowed_during_screenshot(action) {
if let Some(bind) = &final_bind {
if allowed_during_screenshot(&bind.action) {
use_screenshot_ui_action = false;
}
}
if use_screenshot_ui_action {
final_action = screenshot_ui.action(raw, mods);
if let Some(raw) = raw {
final_bind = screenshot_ui.action(raw, mods).map(|action| Bind {
key: Key {
trigger: Trigger::Keysym(raw),
// Not entirely correct but it doesn't matter in how we currently use it.
modifiers: Modifiers::empty(),
},
action,
cooldown: None,
});
}
}
}
match (final_action, pressed) {
(Some(action), true) => {
match (final_bind, pressed) {
(Some(bind), true) => {
suppressed_keys.insert(key_code);
FilterResult::Intercept(Some(action))
FilterResult::Intercept(Some(bind))
}
(_, false) => {
suppressed_keys.remove(&key_code);
@ -1672,36 +1779,49 @@ fn should_intercept_key(
}
}
fn action(
fn find_bind(
bindings: &Binds,
comp_mod: CompositorMod,
modified: Keysym,
raw: Option<Keysym>,
mods: ModifiersState,
disable_power_key_handling: bool,
) -> Option<Action> {
) -> Option<Bind> {
use keysyms::*;
// Handle hardcoded binds.
#[allow(non_upper_case_globals)] // wat
match modified.raw() {
let hardcoded_action = match modified.raw() {
modified @ KEY_XF86Switch_VT_1..=KEY_XF86Switch_VT_12 => {
let vt = (modified - KEY_XF86Switch_VT_1 + 1) as i32;
return Some(Action::ChangeVt(vt));
Some(Action::ChangeVt(vt))
}
KEY_XF86PowerOff if !disable_power_key_handling => return Some(Action::Suspend),
_ => (),
KEY_XF86PowerOff if !disable_power_key_handling => Some(Action::Suspend),
_ => None,
};
if let Some(action) = hardcoded_action {
return Some(Bind {
key: Key {
// Not entirely correct but it doesn't matter in how we currently use it.
trigger: Trigger::Keysym(modified),
modifiers: Modifiers::empty(),
},
action,
cooldown: None,
});
}
bound_action(bindings, comp_mod, raw, mods)
let trigger = Trigger::Keysym(raw?);
find_configured_bind(bindings, comp_mod, trigger, mods)
}
fn bound_action(
fn find_configured_bind(
bindings: &Binds,
comp_mod: CompositorMod,
raw: Option<Keysym>,
trigger: Trigger,
mods: ModifiersState,
) -> Option<Action> {
) -> Option<Bind> {
// Handle configured binds.
let mut modifiers = Modifiers::empty();
if mods.ctrl {
@ -1725,10 +1845,8 @@ fn bound_action(
modifiers |= Modifiers::COMPOSITOR;
}
let raw = raw?;
for bind in &bindings.0 {
if bind.key.keysym != raw {
if bind.key.trigger != trigger {
continue;
}
@ -1740,7 +1858,7 @@ fn bound_action(
}
if bind_modifiers == modifiers {
return Some(bind.action.clone());
return Some(bind.clone());
}
}
@ -1899,8 +2017,6 @@ pub fn apply_libinput_settings(config: &niri_config::Input, device: &mut input::
#[cfg(test)]
mod tests {
use niri_config::{Bind, Key};
use super::*;
#[test]
@ -1908,10 +2024,11 @@ mod tests {
let close_keysym = Keysym::q;
let bindings = Binds(vec![Bind {
key: Key {
keysym: close_keysym,
trigger: Trigger::Keysym(close_keysym),
modifiers: Modifiers::COMPOSITOR | Modifiers::CTRL,
},
action: Action::CloseWindow,
cooldown: None,
}]);
let comp_mod = CompositorMod::Super;
@ -1966,7 +2083,10 @@ mod tests {
let filter = close_key_event(&mut suppressed_keys, mods, true);
assert!(matches!(
filter,
FilterResult::Intercept(Some(Action::CloseWindow))
FilterResult::Intercept(Some(Bind {
action: Action::CloseWindow,
..
}))
));
assert!(suppressed_keys.contains(&close_key_code));
@ -1997,7 +2117,10 @@ mod tests {
let filter = close_key_event(&mut suppressed_keys, mods, true);
assert!(matches!(
filter,
FilterResult::Intercept(Some(Action::CloseWindow))
FilterResult::Intercept(Some(Bind {
action: Action::CloseWindow,
..
}))
));
let filter = none_key_event(&mut suppressed_keys, mods, true);
@ -2014,7 +2137,10 @@ mod tests {
let filter = close_key_event(&mut suppressed_keys, mods, true);
assert!(matches!(
filter,
FilterResult::Intercept(Some(Action::CloseWindow))
FilterResult::Intercept(Some(Bind {
action: Action::CloseWindow,
..
}))
));
mods = Default::default();
@ -2030,90 +2156,97 @@ mod tests {
let bindings = Binds(vec![
Bind {
key: Key {
keysym: Keysym::q,
trigger: Trigger::Keysym(Keysym::q),
modifiers: Modifiers::COMPOSITOR,
},
action: Action::CloseWindow,
cooldown: None,
},
Bind {
key: Key {
keysym: Keysym::h,
trigger: Trigger::Keysym(Keysym::h),
modifiers: Modifiers::SUPER,
},
action: Action::FocusColumnLeft,
cooldown: None,
},
Bind {
key: Key {
keysym: Keysym::j,
trigger: Trigger::Keysym(Keysym::j),
modifiers: Modifiers::empty(),
},
action: Action::FocusWindowDown,
cooldown: None,
},
Bind {
key: Key {
keysym: Keysym::k,
trigger: Trigger::Keysym(Keysym::k),
modifiers: Modifiers::COMPOSITOR | Modifiers::SUPER,
},
action: Action::FocusWindowUp,
cooldown: None,
},
Bind {
key: Key {
keysym: Keysym::l,
trigger: Trigger::Keysym(Keysym::l),
modifiers: Modifiers::SUPER | Modifiers::ALT,
},
action: Action::FocusColumnRight,
cooldown: None,
},
]);
assert_eq!(
bound_action(
find_configured_bind(
&bindings,
CompositorMod::Super,
Some(Keysym::q),
Trigger::Keysym(Keysym::q),
ModifiersState {
logo: true,
..Default::default()
}
),
Some(Action::CloseWindow)
)
.as_ref(),
Some(&bindings.0[0])
);
assert_eq!(
bound_action(
find_configured_bind(
&bindings,
CompositorMod::Super,
Some(Keysym::q),
Trigger::Keysym(Keysym::q),
ModifiersState::default(),
),
None,
);
assert_eq!(
bound_action(
find_configured_bind(
&bindings,
CompositorMod::Super,
Some(Keysym::h),
Trigger::Keysym(Keysym::h),
ModifiersState {
logo: true,
..Default::default()
}
),
Some(Action::FocusColumnLeft)
)
.as_ref(),
Some(&bindings.0[1])
);
assert_eq!(
bound_action(
find_configured_bind(
&bindings,
CompositorMod::Super,
Some(Keysym::h),
Trigger::Keysym(Keysym::h),
ModifiersState::default(),
),
None,
);
assert_eq!(
bound_action(
find_configured_bind(
&bindings,
CompositorMod::Super,
Some(Keysym::j),
Trigger::Keysym(Keysym::j),
ModifiersState {
logo: true,
..Default::default()
@ -2122,55 +2255,58 @@ mod tests {
None,
);
assert_eq!(
bound_action(
find_configured_bind(
&bindings,
CompositorMod::Super,
Some(Keysym::j),
Trigger::Keysym(Keysym::j),
ModifiersState::default(),
),
Some(Action::FocusWindowDown)
)
.as_ref(),
Some(&bindings.0[2])
);
assert_eq!(
bound_action(
find_configured_bind(
&bindings,
CompositorMod::Super,
Some(Keysym::k),
Trigger::Keysym(Keysym::k),
ModifiersState {
logo: true,
..Default::default()
}
),
Some(Action::FocusWindowUp)
)
.as_ref(),
Some(&bindings.0[3])
);
assert_eq!(
bound_action(
find_configured_bind(
&bindings,
CompositorMod::Super,
Some(Keysym::k),
Trigger::Keysym(Keysym::k),
ModifiersState::default(),
),
None,
);
assert_eq!(
bound_action(
find_configured_bind(
&bindings,
CompositorMod::Super,
Some(Keysym::l),
Trigger::Keysym(Keysym::l),
ModifiersState {
logo: true,
alt: true,
..Default::default()
}
),
Some(Action::FocusColumnRight)
)
.as_ref(),
Some(&bindings.0[4])
);
assert_eq!(
bound_action(
find_configured_bind(
&bindings,
CompositorMod::Super,
Some(Keysym::l),
Trigger::Keysym(Keysym::l),
ModifiersState {
logo: true,
..Default::default()

View File

@ -19,6 +19,7 @@ pub mod rubber_band;
pub mod swipe_tracker;
pub mod ui;
pub mod utils;
pub mod wheel_tracker;
pub mod window;
#[cfg(not(feature = "xdp-gnome-screencast"))]

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};
@ -114,6 +114,7 @@ use crate::utils::spawning::CHILD_ENV;
use crate::utils::{
center, center_f64, get_monotonic_time, make_screenshot_path, output_size, write_png_rgba8,
};
use crate::wheel_tracker::WheelTracker;
use crate::window::{InitialConfigureState, Mapped, ResolvedWindowRules, Unmapped};
use crate::{animation, niri_render_elements};
@ -193,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>,
@ -204,6 +206,8 @@ pub struct Niri {
pub pointer_focus: PointerFocus,
pub tablet_cursor_location: Option<Point<f64, Logical>>,
pub gesture_swipe_3f_cumulative: Option<(f64, f64)>,
pub vertical_wheel_tracker: WheelTracker,
pub horizontal_wheel_tracker: WheelTracker,
pub lock_state: LockState,
@ -1248,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,
@ -1263,6 +1268,8 @@ impl Niri {
pointer_focus: PointerFocus::default(),
tablet_cursor_location: None,
gesture_swipe_3f_cumulative: None,
vertical_wheel_tracker: WheelTracker::new(),
horizontal_wheel_tracker: WheelTracker::new(),
lock_state: LockState::Unlocked,

View File

@ -177,21 +177,24 @@ where
}
// Verify that there's no more data.
match file.read(&mut [0]) {
Ok(0) => (),
Ok(_) => {
warn!("gamma data is too large");
resource.failed();
gamma_controls.remove(&output);
let _ = state.set_gamma(&output, None);
return;
}
Err(err) => {
warn!("error reading gamma data: {err:?}");
resource.failed();
gamma_controls.remove(&output);
let _ = state.set_gamma(&output, None);
return;
#[allow(clippy::unused_io_amount)] // False positive on 1.77.0
{
match file.read(&mut [0]) {
Ok(0) => (),
Ok(_) => {
warn!("gamma data is too large");
resource.failed();
gamma_controls.remove(&output);
let _ = state.set_gamma(&output, None);
return;
}
Err(err) => {
warn!("error reading gamma data: {err:?}");
resource.failed();
gamma_controls.remove(&output);
let _ = state.set_gamma(&output, None);
return;
}
}
}
}

View File

@ -4,7 +4,7 @@ use std::collections::HashMap;
use std::iter::zip;
use std::rc::Rc;
use niri_config::{Action, Config, Key, Modifiers};
use niri_config::{Action, Config, Key, Modifiers, Trigger};
use pangocairo::cairo::{self, ImageSurface};
use pangocairo::pango::{AttrColor, AttrInt, AttrList, AttrString, FontDescription, Weight};
use smithay::backend::renderer::element::memory::{
@ -414,7 +414,15 @@ fn key_name(comp_mod: CompositorMod, key: &Key) -> String {
if key.modifiers.contains(Modifiers::CTRL) {
name.push_str("Ctrl + ");
}
name.push_str(&prettify_keysym_name(&keysym_get_name(key.keysym)));
let pretty = match key.trigger {
Trigger::Keysym(keysym) => prettify_keysym_name(&keysym_get_name(keysym)),
Trigger::WheelDown => String::from("Wheel Down"),
Trigger::WheelUp => String::from("Wheel Up"),
Trigger::WheelLeft => String::from("Wheel Left"),
Trigger::WheelRight => String::from("Wheel Right"),
};
name.push_str(&pretty);
name
}

View File

@ -316,12 +316,12 @@ impl ScreenshotUi {
Ok((rect.size, copy.to_vec()))
}
pub fn action(&self, raw: Option<Keysym>, mods: ModifiersState) -> Option<Action> {
pub fn action(&self, raw: Keysym, mods: ModifiersState) -> Option<Action> {
if !matches!(self, Self::Open { .. }) {
return None;
}
action(raw?, mods)
action(raw, mods)
}
pub fn selection_output(&self) -> Option<&Output> {

36
src/wheel_tracker.rs Normal file
View File

@ -0,0 +1,36 @@
pub struct WheelTracker {
last: f64,
acc: f64,
}
impl WheelTracker {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self { last: 0., acc: 0. }
}
pub fn accumulate(&mut self, amount_v120: f64) -> i8 {
let changed_direction =
(self.last > 0. && amount_v120 < 0.) || (self.last < 0. && amount_v120 > 0.);
if changed_direction {
self.acc = 0.
}
self.last = amount_v120;
self.acc += amount_v120;
let mut ticks = 0;
if self.acc.abs() >= 120. {
let clamped = self.acc.clamp(-127. * 120., 127. * 120.);
ticks = (clamped as i16 / 120) as i8;
self.acc %= 120.;
}
ticks
}
pub fn reset(&mut self) {
self.last = 0.;
self.acc = 0.;
}
}