Wayland compositor (wlroots)
git clone https://git.lucas.co/cce-compositor.git
Make key repeat configurable, default delay 250 ms
The repeat delay was hardcoded at 400 ms in two places. Both now share
keyboard::DEFAULT_REPEAT_{RATE,DELAY} (40/s, 250 ms), and hardware
keyboards take `input { repeat_rate repeat_delay }` over them: at hotplug
in Keyboard::create, and for keyboards already present on every config
load via apply_input_config (which regroups only on an actual change).
Virtual and synthetic keyboards keep the defaults.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/server/config.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++
src/server/keyboard.rs | 17 ++++++++++++--
src/server/seat.rs | 4 ++--
src/server/window_manager.rs | 13 ++++++++++
4 files changed, 86 insertions(+), 4 deletions(-)
diff --git a/src/server/config.rs b/src/server/config.rs
index 2de35047..6758e87b 100644
--- a/src/server/config.rs
+++ b/src/server/config.rs
@@ -559,11 +559,28 @@ pub struct InputConfig {
pub kinetic_scroll: Option<bool>,
/// `scroll_friction`: coast decay, 1/s (default 6).
pub scroll_friction: Option<f64>,
+ /// `repeat_rate`: key repeats per second once repeating starts
+ /// (default `keyboard::DEFAULT_REPEAT_RATE`; 0 disables repeat).
+ pub repeat_rate: Option<i64>,
+ /// `repeat_delay`: ms a key is held before it starts repeating
+ /// (default `keyboard::DEFAULT_REPEAT_DELAY`).
+ pub repeat_delay: Option<i64>,
pub mouse: Option<MouseConfig>,
pub touchpad: Option<TouchpadConfig>,
pub trackpoint: Option<TrackpointConfig>,
}
+impl InputConfig {
+ /// `(rate, delay)` for hardware keyboards, defaults filled in. Negative
+ /// values are clamped to 0, which `wl_keyboard.repeat_info` reads as
+ /// "no repeat" for the rate; the protocol rejects negatives outright.
+ pub fn repeat_info(&self) -> (i32, i32) {
+ let rate = self.repeat_rate.map_or(crate::keyboard::DEFAULT_REPEAT_RATE, |v| v.clamp(0, i32::MAX as i64) as i32);
+ let delay = self.repeat_delay.map_or(crate::keyboard::DEFAULT_REPEAT_DELAY, |v| v.clamp(0, i32::MAX as i64) as i32);
+ (rate, delay)
+ }
+}
+
#[derive(Debug, Deserialize, Clone, Default)]
pub struct TransparencyConfig {
pub opacity: Option<f64>,
@@ -1318,6 +1335,19 @@ fn get_child_arg_i64(node: &kdl::KdlNode, child_name: &str, default: i64) -> i64
default
}
+fn get_child_arg_i64_opt(node: &kdl::KdlNode, child_name: &str) -> Option<i64> {
+ if let Some(children) = node.children() {
+ for child in children.nodes() {
+ if child.name().value() == child_name {
+ if let Some(entry) = child.entries().first() {
+ return entry.value().as_i64();
+ }
+ }
+ }
+ }
+ None
+}
+
fn get_child_arg_f64(node: &kdl::KdlNode, child_name: &str, default: f64) -> f64 {
if let Some(children) = node.children() {
for child in children.nodes() {
@@ -1990,6 +2020,8 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
let scroll_ease = get_child_arg_f64_opt(node, "scroll_ease");
let kinetic_scroll = get_child_arg_bool_opt(node, "kinetic_scroll");
let scroll_friction = get_child_arg_f64_opt(node, "scroll_friction");
+ let repeat_rate = get_child_arg_i64_opt(node, "repeat_rate");
+ let repeat_delay = get_child_arg_i64_opt(node, "repeat_delay");
let mut touchpad = None;
if let Some(children) = node.children() {
@@ -2059,6 +2091,8 @@ fn parse_kdl_config(content: &str) -> Result<Config, String> {
scroll_ease,
kinetic_scroll,
scroll_friction,
+ repeat_rate,
+ repeat_delay,
mouse,
touchpad,
trackpoint,
@@ -3310,6 +3344,28 @@ style {
assert_eq!(tpoint.scroll_method, Some("none".to_string()));
}
+ #[test]
+ fn test_kdl_input_key_repeat() {
+ let input = parse_kdl_config("input {\n repeat_rate 30\n repeat_delay 200\n}\n")
+ .unwrap()
+ .input
+ .unwrap();
+ assert_eq!(input.repeat_rate, Some(30));
+ assert_eq!(input.repeat_delay, Some(200));
+ assert_eq!(input.repeat_info(), (30, 200));
+
+ // Unset keys fall back to the defaults; negatives clamp to 0.
+ let input = parse_kdl_config("input {\n repeat_delay -5\n}\n").unwrap().input.unwrap();
+ assert_eq!(
+ input.repeat_info(),
+ (crate::keyboard::DEFAULT_REPEAT_RATE, 0)
+ );
+ assert_eq!(
+ InputConfig::default().repeat_info(),
+ (crate::keyboard::DEFAULT_REPEAT_RATE, crate::keyboard::DEFAULT_REPEAT_DELAY)
+ );
+ }
+
#[test]
fn test_kdl_touchpad_hscroll_shift_apps() {
let content = r#"
diff --git a/src/server/keyboard.rs b/src/server/keyboard.rs
index 67b56d0b..7234ebab 100644
--- a/src/server/keyboard.rs
+++ b/src/server/keyboard.rs
@@ -7,6 +7,14 @@ use crate::server::{WlListener, wl_listener_remove, wl_signal_add};
use crate::keyboard_group::KeyboardGroup;
use std::collections::HashSet;
+/// Key repeat defaults (keys/s, ms before the first repeat). A hardware
+/// keyboard takes `input { repeat_rate repeat_delay }` over these
+/// (`InputConfig::repeat_info`); the synthetic and virtual keyboards keep
+/// them. A client can still change a device's via river's
+/// `input_device.set_repeat_info`, until the next config load.
+pub const DEFAULT_REPEAT_RATE: i32 = 40;
+pub const DEFAULT_REPEAT_DELAY: i32 = 250;
+
#[derive(Clone, Copy)]
pub struct KeyboardConfig {
pub keymap: *mut ffi::xkb_keymap,
@@ -34,6 +42,11 @@ impl Keyboard {
let wlr_keyboard = ffi::wlr_keyboard_from_input_device((*device).wlr_device);
let virtual_device = (*device).virtual_device;
+ let (repeat_rate, repeat_delay) = if virtual_device {
+ (DEFAULT_REPEAT_RATE, DEFAULT_REPEAT_DELAY)
+ } else {
+ (*(*(*device).seat).server).wm.input_config.repeat_info()
+ };
let mut keymap = std::ptr::null_mut();
if virtual_device {
let kbd_keymap = ffi::river_wlr_keyboard_get_keymap(wlr_keyboard);
@@ -60,8 +73,8 @@ impl Keyboard {
group_link: std::mem::zeroed(),
config: KeyboardConfig {
keymap,
- repeat_rate: 40,
- repeat_delay: 400,
+ repeat_rate,
+ repeat_delay,
},
}));
diff --git a/src/server/seat.rs b/src/server/seat.rs
index f25fe1a5..38705cfe 100644
--- a/src/server/seat.rs
+++ b/src/server/seat.rs
@@ -761,8 +761,8 @@ impl Seat {
// `keyboard_notify_enter` its pressed-key tracking.
let config = crate::keyboard::KeyboardConfig {
keymap,
- repeat_rate: 40,
- repeat_delay: 400,
+ repeat_rate: crate::keyboard::DEFAULT_REPEAT_RATE,
+ repeat_delay: crate::keyboard::DEFAULT_REPEAT_DELAY,
};
match crate::keyboard_group::KeyboardGroup::create(self, config, true) {
Ok(group) => {
diff --git a/src/server/window_manager.rs b/src/server/window_manager.rs
index d1a3556c..cb5831e0 100644
--- a/src/server/window_manager.rs
+++ b/src/server/window_manager.rs
@@ -6616,6 +6616,19 @@ impl WindowManager {
if let Some(ref mut libinput) = (*device).libinput {
libinput.apply_config(&self.input_config);
}
+ // Key repeat: a hardware keyboard regroups on a change (groups
+ // are keyed by repeat info), so only touch it when it differs.
+ if !(*device).virtual_device
+ && ffi::river_wlr_input_device_get_type((*device).wlr_device) == ffi::wlr_input_device_type_WLR_INPUT_DEVICE_KEYBOARD
+ {
+ let keyboard = (*device).destroy_data as *mut crate::keyboard::Keyboard;
+ if !keyboard.is_null() {
+ let (rate, delay) = self.input_config.repeat_info();
+ if (*keyboard).config.repeat_rate != rate || (*keyboard).config.repeat_delay != delay {
+ (*keyboard).set_repeat_info(rate, delay);
+ }
+ }
+ }
curr = next;
}
}