feat: configure keyboard and touchpad input

This commit is contained in:
Samuel Onoja committed 2026-09-25 02:50:19 +01:00
1 parent 94a83c83bb
commit 2f83c58040
5 files changed
+233 -5

No files matched your search

+37 -3
View File
@@ -14,6 +14,7 @@ use smithay::{
},
drm::{DrmDevice, DrmDeviceFd, DrmEvent, DrmEventTime, DrmNode, GbmBufferedSurface},
egl::{EGLContext, EGLDisplay},
input::InputEvent,
libinput::{LibinputInputBackend, LibinputSessionInterface},
renderer::{
Bind, Frame, ImportDma, ImportMemWl, Renderer, damage::OutputDamageTracker,
@@ -29,7 +30,7 @@ use smithay::{
drm::control::{
Device as ControlDevice, Mode as DrmMode, ModeTypeFlags, connector, crtc, property,
},
input::Libinput,
input::{Device as LibinputDevice, Libinput},
rustix::fs::OFlags,
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback::Kind,
wayland_server::backend::GlobalId,
@@ -108,8 +109,9 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
event_loop
.handle()
.insert_source(libinput_backend, |event, _, state| {
state.process_input_event(event);
.insert_source(libinput_backend, |event, _, state| match event {
InputEvent::DeviceAdded { mut device } => configure_libinput_device(state, &mut device),
event => state.process_input_event(event),
})?;
event_loop
.handle()
@@ -176,6 +178,38 @@ pub fn init(event_loop: &mut EventLoop<Ferese>, state: &mut Ferese) -> Result<()
Ok(())
}
fn configure_libinput_device(state: &Ferese, device: &mut LibinputDevice) {
let settings = state.input_settings.touchpad;
if device.config_tap_finger_count() > 0
&& let Err(error) = device.config_tap_set_enabled(settings.tap)
{
tracing::warn!(
device = device.name(),
?error,
"failed to configure tap-to-click"
);
}
if device.config_scroll_has_natural_scroll()
&& let Err(error) = device.config_scroll_set_natural_scroll_enabled(settings.natural_scroll)
{
tracing::warn!(
device = device.name(),
?error,
"failed to configure natural scrolling"
);
}
if device.config_dwt_is_available()
&& let Err(error) = device.config_dwt_set_enabled(settings.disable_while_typing)
{
tracing::warn!(
device = device.name(),
?error,
"failed to configure disable-while-typing"
);
}
}
fn open_primary_device(
event_loop: &mut EventLoop<Ferese>,
state: &mut Ferese,
+157 -1
View File
@@ -17,9 +17,77 @@ pub struct Config {
#[serde(default)]
layout: LayoutConfig,
#[serde(default)]
input: InputConfig,
#[serde(default)]
scrolling: ScrollingConfig,
}
#[derive(Clone, Debug, PartialEq)]
pub struct InputSettings {
pub xkb_layout: String,
pub xkb_variant: String,
pub xkb_options: Vec<String>,
pub repeat_rate: i32,
pub repeat_delay_ms: i32,
pub touchpad: TouchpadSettings,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct TouchpadSettings {
pub tap: bool,
pub natural_scroll: bool,
pub disable_while_typing: bool,
}
#[derive(Debug, Deserialize)]
struct InputConfig {
#[serde(default = "default_xkb_layout")]
xkb_layout: String,
#[serde(default)]
xkb_variant: String,
#[serde(default)]
xkb_options: Vec<String>,
#[serde(default = "default_repeat_rate")]
repeat_rate: i32,
#[serde(default = "default_repeat_delay")]
repeat_delay_ms: i32,
#[serde(default)]
touchpad: TouchpadConfig,
}
impl Default for InputConfig {
fn default() -> Self {
Self {
xkb_layout: default_xkb_layout(),
xkb_variant: String::new(),
xkb_options: Vec::new(),
repeat_rate: default_repeat_rate(),
repeat_delay_ms: default_repeat_delay(),
touchpad: TouchpadConfig::default(),
}
}
}
#[derive(Debug, Deserialize)]
struct TouchpadConfig {
#[serde(default = "enabled_by_default")]
tap: bool,
#[serde(default = "enabled_by_default")]
natural_scroll: bool,
#[serde(default = "enabled_by_default")]
disable_while_typing: bool,
}
impl Default for TouchpadConfig {
fn default() -> Self {
Self {
tap: true,
natural_scroll: true,
disable_while_typing: true,
}
}
}
#[derive(Debug, Deserialize)]
struct AnimationsConfig {
#[serde(default = "enabled_by_default")]
@@ -159,6 +227,10 @@ pub enum ConfigError {
field: &'static str,
value: f64,
},
InvalidInputValue {
field: &'static str,
value: String,
},
}
impl fmt::Display for ConfigError {
@@ -180,6 +252,9 @@ impl fmt::Display for ConfigError {
Self::InvalidLayoutValue { field, value } => {
write!(formatter, "invalid layout.{field} value {value}")
}
Self::InvalidInputValue { field, value } => {
write!(formatter, "invalid input.{field} value {value}")
}
}
}
}
@@ -191,7 +266,8 @@ impl Error for ConfigError {
Self::Parse { source, .. } => Some(source),
Self::InvalidColumnWidth { .. }
| Self::InvalidAnimationValue { .. }
| Self::InvalidLayoutValue { .. } => None,
| Self::InvalidLayoutValue { .. }
| Self::InvalidInputValue { .. } => None,
}
}
}
@@ -266,6 +342,40 @@ impl Config {
}
}
pub fn input_settings(&self) -> Result<InputSettings, ConfigError> {
if self.input.xkb_layout.trim().is_empty() {
return Err(ConfigError::InvalidInputValue {
field: "xkb_layout",
value: self.input.xkb_layout.clone(),
});
}
if self.input.repeat_rate <= 0 {
return Err(ConfigError::InvalidInputValue {
field: "repeat_rate",
value: self.input.repeat_rate.to_string(),
});
}
if self.input.repeat_delay_ms < 0 {
return Err(ConfigError::InvalidInputValue {
field: "repeat_delay_ms",
value: self.input.repeat_delay_ms.to_string(),
});
}
Ok(InputSettings {
xkb_layout: self.input.xkb_layout.clone(),
xkb_variant: self.input.xkb_variant.clone(),
xkb_options: self.input.xkb_options.clone(),
repeat_rate: self.input.repeat_rate,
repeat_delay_ms: self.input.repeat_delay_ms,
touchpad: TouchpadSettings {
tap: self.input.touchpad.tap,
natural_scroll: self.input.touchpad.natural_scroll,
disable_while_typing: self.input.touchpad.disable_while_typing,
},
})
}
pub fn animations_enabled(&self) -> bool {
self.animations.enabled && !self.animations.reduced_motion
}
@@ -343,6 +453,18 @@ const fn default_outer_gap() -> f64 {
10.0
}
fn default_xkb_layout() -> String {
"us".to_owned()
}
const fn default_repeat_rate() -> i32 {
25
}
const fn default_repeat_delay() -> i32 {
600
}
const fn default_animation_speed() -> f64 {
1.0
}
@@ -519,6 +641,40 @@ mod tests {
assert!(invalid.gap_config().is_err());
}
#[test]
fn parses_input_and_touchpad_settings() {
let config = parse(
"[input]\nxkb_layout = \"us,de\"\nxkb_variant = \",nodeadkeys\"\nxkb_options = [\"grp:alt_shift_toggle\"]\nrepeat_rate = 30\nrepeat_delay_ms = 450\n\n[input.touchpad]\ntap = false\nnatural_scroll = false\ndisable_while_typing = true",
);
assert_eq!(
config.input_settings().unwrap(),
InputSettings {
xkb_layout: "us,de".to_owned(),
xkb_variant: ",nodeadkeys".to_owned(),
xkb_options: vec!["grp:alt_shift_toggle".to_owned()],
repeat_rate: 30,
repeat_delay_ms: 450,
touchpad: TouchpadSettings {
tap: false,
natural_scroll: false,
disable_while_typing: true,
},
}
);
}
#[test]
fn rejects_invalid_input_settings() {
let layout = parse("[input]\nxkb_layout = \"\"");
let rate = parse("[input]\nrepeat_rate = 0");
let delay = parse("[input]\nrepeat_delay_ms = -1");
assert!(layout.input_settings().is_err());
assert!(rate.input_settings().is_err());
assert!(delay.input_settings().is_err());
}
#[test]
fn derives_critical_viewport_damping() {
let config = parse("");
+1
View File
@@ -29,6 +29,7 @@ fn main() -> Result<(), Box<dyn Error>> {
let runtime = RuntimeConfig {
layout_mode: config.layout_mode(),
gap_config: config.gap_config()?,
input_settings: config.input_settings()?,
default_column_width: config.default_column_width()?,
scrolling_focus_strategy: config.scrolling_focus_strategy(),
column_width_presets: config.width_presets()?,
+18 -1
View File
@@ -28,6 +28,7 @@ use smithay::{
},
input::{
Seat, SeatState,
keyboard::XkbConfig,
pointer::{CursorIcon, CursorImageStatus},
},
output::Output,
@@ -74,6 +75,8 @@ use smithay::{
},
};
use crate::config::InputSettings;
pub struct Ferese {
pub start_time: Instant,
pub socket_name: OsString,
@@ -91,6 +94,7 @@ pub struct Ferese {
pub focused_window: Option<WindowId>,
column_width_presets: Vec<ColumnWidth>,
gap_config: GapConfig,
pub(crate) input_settings: InputSettings,
animations_enabled: bool,
animation_speed: f64,
spring_config: SpringConfig,
@@ -139,6 +143,7 @@ pub struct Ferese {
pub struct RuntimeConfig {
pub layout_mode: LayoutMode,
pub gap_config: GapConfig,
pub input_settings: InputSettings,
pub default_column_width: ColumnWidth,
pub scrolling_focus_strategy: ViewportFocusStrategy,
pub column_width_presets: Vec<ColumnWidth>,
@@ -335,7 +340,18 @@ impl Ferese {
let xdg_toplevel_icon_manager = XdgToplevelIconManager::new::<Self>(&display_handle);
let mut seat_state = SeatState::new();
let mut seat = seat_state.new_wl_seat(&display_handle, "ferese-winit");
seat.add_keyboard(Default::default(), 200, 25)?;
let xkb_options = (!config.input_settings.xkb_options.is_empty())
.then(|| config.input_settings.xkb_options.join(","));
seat.add_keyboard(
XkbConfig {
layout: &config.input_settings.xkb_layout,
variant: &config.input_settings.xkb_variant,
options: xkb_options,
..XkbConfig::default()
},
config.input_settings.repeat_delay_ms,
config.input_settings.repeat_rate,
)?;
seat.add_pointer();
seat.add_touch();
let socket_name = Self::init_wayland_listener(display, event_loop)?;
@@ -366,6 +382,7 @@ impl Ferese {
focused_window: None,
column_width_presets: config.column_width_presets,
gap_config: config.gap_config,
input_settings: config.input_settings,
animations_enabled: config.animations_enabled,
animation_speed: config.animation_speed,
spring_config: config.spring_config,