use std::os::fd::{AsFd, BorrowedFd};
use std::path::Path;
use input::event::keyboard::{KeyState, KeyboardEventTrait as _};
use input::event::pointer::{Axis, ButtonState, PointerEventTrait, PointerScrollEvent};
use input::event::switch::{Switch as LibinputSwitch, SwitchEventTrait as _, SwitchState};
use input::event::touch::{TouchEventPosition as _, TouchEventSlot as _, TouchEventTrait as _};
use input::event::{
DeviceEvent, EventTrait, KeyboardEvent as LibinputKeyboard, PointerEvent as LibinputPointer,
SwitchEvent as LibinputSwitchEvent, TouchEvent as LibinputTouch,
};
use input::{AsRaw as _, Device, DeviceCapability, Led, Libinput, LibinputInterface};
use crate::InputError;
use crate::event::{
InputEvent, KeyEvent, PointerAbsolute, PointerAxis, PointerButton, PointerEvent, PointerMotion,
ScrollSource, Switch, SwitchEvent, TouchEvent, TouchPhase,
};
use crate::leds::Leds;
use crate::libinput_log::{self, LogPriority};
#[derive(Debug, Clone)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct SeatOptions {
pub seat: String,
pub log_priority: LogPriority,
}
impl Default for SeatOptions {
fn default() -> Self {
Self {
seat: "seat0".to_owned(),
log_priority: LogPriority::default(),
}
}
}
#[derive(Debug)]
pub struct Seat {
libinput: Libinput,
keyboards: Vec<Device>,
leds: Option<Leds>,
}
impl Seat {
pub fn open(options: &SeatOptions) -> Result<Self, InputError> {
Self::open_with(options, Direct)
}
pub fn open_with(
options: &SeatOptions,
opener: impl LibinputInterface + 'static,
) -> Result<Self, InputError> {
let mut libinput = Libinput::new_with_udev(opener);
unsafe {
libinput_log::install(libinput.as_raw().cast_mut().cast(), options.log_priority);
}
libinput
.udev_assign_seat(&options.seat)
.map_err(|()| InputError::Seat(options.seat.clone()))?;
Ok(Self {
libinput,
keyboards: Vec::new(),
leds: None,
})
}
pub fn dispatch(&mut self, out: &mut Vec<InputEvent>) -> Result<(), InputError> {
self.libinput.dispatch().map_err(InputError::Dispatch)?;
while let Some(event) = self.libinput.next() {
match event {
input::Event::Device(event) => self.on_device(event),
other => out.extend(convert(other)),
}
}
Ok(())
}
pub fn suspend(&mut self) {
self.libinput.suspend();
self.keyboards.clear();
}
pub fn resume(&mut self) -> Result<(), InputError> {
self.libinput
.resume()
.map_err(|()| InputError::Seat("resume".to_owned()))
}
pub fn set_leds(&mut self, leds: Leds) {
self.leds = Some(leds);
let mask = mask(leds);
for keyboard in &mut self.keyboards {
keyboard.led_update(mask);
}
}
#[must_use]
pub fn keyboard_count(&self) -> usize {
self.keyboards.len()
}
fn on_device(&mut self, event: DeviceEvent) {
match event {
DeviceEvent::Added(added) => {
let mut device = added.device();
if !device.has_capability(DeviceCapability::Keyboard) {
return;
}
if let Some(leds) = self.leds {
device.led_update(mask(leds));
}
self.keyboards.push(device);
}
DeviceEvent::Removed(removed) => {
let device = removed.device();
self.keyboards.retain(|held| *held != device);
}
_ => {}
}
}
}
impl AsFd for Seat {
fn as_fd(&self) -> BorrowedFd<'_> {
self.libinput.as_fd()
}
}
struct Direct;
impl LibinputInterface for Direct {
fn open_restricted(&mut self, path: &Path, flags: i32) -> Result<std::os::fd::OwnedFd, i32> {
let flags = rustix::fs::OFlags::from_bits_retain(flags.cast_unsigned())
| rustix::fs::OFlags::CLOEXEC;
rustix::fs::open(path, flags, rustix::fs::Mode::empty())
.map_err(|errno| -errno.raw_os_error())
}
fn close_restricted(&mut self, fd: std::os::fd::OwnedFd) {
drop(fd);
}
}
fn mask(leds: Leds) -> Led {
let mut mask = Led::empty();
mask.set(Led::CAPSLOCK, leds.caps_lock);
mask.set(Led::NUMLOCK, leds.num_lock);
mask.set(Led::SCROLLLOCK, leds.scroll_lock);
mask
}
fn device_name(event: &impl EventTrait) -> Option<String> {
Some(event.device().name().into_owned()).filter(|name| !name.is_empty())
}
fn convert(event: input::Event) -> Option<InputEvent> {
match event {
input::Event::Keyboard(LibinputKeyboard::Key(key)) => Some(InputEvent::Key(KeyEvent {
time_ms: key.time(),
key: key.key(),
pressed: key.key_state() == KeyState::Pressed,
repeat: false,
sym: 0,
utf8: String::new(),
})),
input::Event::Pointer(pointer) => convert_pointer(pointer).map(InputEvent::Pointer),
input::Event::Touch(touch) => convert_touch(&touch).map(InputEvent::Touch),
input::Event::Switch(LibinputSwitchEvent::Toggle(toggle)) => {
let which = match toggle.switch()? {
LibinputSwitch::Lid => Switch::Lid,
LibinputSwitch::TabletMode => Switch::TabletMode,
_ => return None,
};
Some(InputEvent::Switch(SwitchEvent {
time_ms: toggle.time(),
which,
active: toggle.switch_state() == SwitchState::On,
}))
}
_ => None,
}
}
fn convert_pointer(event: LibinputPointer) -> Option<PointerEvent> {
match event {
LibinputPointer::Motion(motion) => Some(PointerEvent::Motion(PointerMotion {
time_ms: motion.time(),
dx: motion.dx(),
dy: motion.dy(),
device: device_name(&motion),
})),
LibinputPointer::MotionAbsolute(motion) => Some(PointerEvent::Absolute(PointerAbsolute {
time_ms: motion.time(),
x: motion.absolute_x_transformed(1),
y: motion.absolute_y_transformed(1),
device: device_name(&motion),
})),
LibinputPointer::Button(button) => Some(PointerEvent::Button(PointerButton {
time_ms: button.time(),
button: button.button(),
pressed: button.button_state() == ButtonState::Pressed,
})),
LibinputPointer::ScrollWheel(scroll) => Some(scrolled(&scroll, ScrollSource::Wheel)),
LibinputPointer::ScrollFinger(scroll) => Some(scrolled(&scroll, ScrollSource::Finger)),
LibinputPointer::ScrollContinuous(scroll) => {
Some(scrolled(&scroll, ScrollSource::Continuous))
}
_ => None,
}
}
fn scrolled(
event: &(impl PointerScrollEvent + PointerEventTrait),
source: ScrollSource,
) -> PointerEvent {
PointerEvent::Axis(PointerAxis {
time_ms: event.time(),
horizontal: axis(event, Axis::Horizontal),
vertical: axis(event, Axis::Vertical),
source,
})
}
fn axis(event: &impl PointerScrollEvent, axis: Axis) -> f64 {
if event.has_axis(axis) {
event.scroll_value(axis)
} else {
0.0
}
}
fn convert_touch(event: &LibinputTouch) -> Option<TouchEvent> {
let (phase, time_ms) = match event {
LibinputTouch::Down(touch) => (TouchPhase::Down, touch.time()),
LibinputTouch::Up(touch) => (TouchPhase::Up, touch.time()),
LibinputTouch::Motion(touch) => (TouchPhase::Motion, touch.time()),
LibinputTouch::Frame(touch) => (TouchPhase::Frame, touch.time()),
LibinputTouch::Cancel(touch) => (TouchPhase::Cancel, touch.time()),
_ => return None,
};
let (x, y) = match event {
LibinputTouch::Down(touch) => (touch.x_transformed(1), touch.y_transformed(1)),
LibinputTouch::Motion(touch) => (touch.x_transformed(1), touch.y_transformed(1)),
_ => (0.0, 0.0),
};
let slot = match event {
LibinputTouch::Down(touch) => touch.slot(),
LibinputTouch::Up(touch) => touch.slot(),
LibinputTouch::Motion(touch) => touch.slot(),
LibinputTouch::Cancel(touch) => touch.slot(),
_ => None,
};
Some(TouchEvent {
time_ms,
slot: slot.map_or(-1, |slot| i32::try_from(slot).unwrap_or(-1)),
x,
y,
phase,
device: device_name(event),
})
}