feat: configure keyboard and touchpad input
This commit is contained in:
1 parent
94a83c83bb
commit
2f83c58040
5 files changed
+233
-5
No files matched your search
@@ -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
@@ -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("");
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user