use super::lib_input_native_unsafe_methods::lib_input_event_type::*;
use super::LibInputBackendOptions;
use crate::input::ev_dev::EvDevRawEvent;
use ferroui_base::platform::SurfaceOrientation;
use ferroui_base::{Point, Size};
use std::collections::HashMap;
pub(crate) const POSITION_RANGE: u32 = 1 << 20;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum LibInputEvent {
Touch { type_: i32, slot: i32, time_usec: u64, x: f64, y: f64 },
PointerMotionAbsolute { time_usec: u64, x: f64, y: f64 },
PointerButton { time_usec: u64, button: u32, button_state: i32 },
PointerAxis { time_usec: u64, source_axis: i32, value_discrete: f64 },
PointerScrollWheel { time_usec: u64, value_v120: f64 },
PointerOther { type_: i32 },
}
pub(crate) fn calibration_matrix(screen_orientation: SurfaceOrientation) -> [f32; 6] {
match screen_orientation {
SurfaceOrientation::Rotation90 => [0.0, 1.0, 0.0, -1.0, 0.0, 1.0],
SurfaceOrientation::Rotation180 => [-1.0, 0.0, 1.0, 0.0, -1.0, 1.0],
SurfaceOrientation::Rotation270 => [0.0, -1.0, 1.0, 1.0, 0.0, 0.0],
_ => [1.0, 0.0, 0.0, 0.0, 1.0, 0.0], }
}
pub(crate) fn is_touch_event(type_: i32) -> bool {
(LIBINPUT_EVENT_TOUCH_DOWN..=LIBINPUT_EVENT_TOUCH_CANCEL).contains(&type_)
}
pub(crate) fn is_pointer_event(type_: i32) -> bool {
(LIBINPUT_EVENT_POINTER_MOTION..=LIBINPUT_EVENT_POINTER_AXIS).contains(&type_)
}
#[derive(Default)]
pub struct LibInputState {
pub(crate) mouse_position: Point,
pub(crate) pointers: HashMap<i32, Point>,
}
impl LibInputState {
pub fn new() -> Self {
Self::default()
}
pub fn handle(&mut self, ev: &LibInputEvent, info: Size, has_input_root: bool, out: &mut Vec<EvDevRawEvent>) {
match ev {
LibInputEvent::Touch { .. } => self.handle_touch(ev, info, has_input_root, out),
_ => self.handle_pointer(ev, info, out),
}
}
}
pub(crate) fn event_devices(options: Option<&LibInputBackendOptions>) -> Vec<String> {
if let Some(events) = options.and_then(|options| options.events.clone()) {
return events;
}
let mut files: Vec<String> = std::fs::read_dir("/dev/input")
.map(|entries| {
entries
.filter_map(Result::ok)
.filter(|entry| entry.file_name().to_string_lossy().starts_with("event"))
.map(|entry| entry.path().to_string_lossy().into_owned())
.collect()
})
.unwrap_or_default();
files.sort();
files
}
#[cfg(target_os = "linux")]
pub use imp::LibInputBackend;
#[cfg(target_os = "linux")]
mod imp {
use super::super::lib_input_native_unsafe_methods::LibInput;
use crate::native_library::load_library;
use super::*;
use crate::input::{IInputBackend, IScreenInfoProvider};
use crate::raw_event_grouping::RawEvent;
use ferroui_base::input::raw::{RawMouseWheelEventArgs, RawPointerEventArgs, RawTouchEventArgs};
use ferroui_base::input::{IInputDevice, IInputRoot, MouseDevice, RawInputModifiers, TouchDevice};
use ferroui_base::logging::{LogEventLevel, Logger};
use ferroui_base::threading::{Dispatcher, DispatcherPriority};
use std::cell::RefCell;
use std::rc::{Rc, Weak};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::thread;
struct State {
screen: RefCell<Option<Rc<dyn IScreenInfoProvider>>>,
input_root: RefCell<Option<Rc<dyn IInputRoot>>>,
on_input: RefCell<Option<Rc<dyn Fn(RawEvent)>>>,
mapping: RefCell<LibInputState>,
mouse: Rc<MouseDevice>,
touch: Rc<TouchDevice>,
}
ferroui_base::ferro_thread_local! {
static BACKENDS: RefCell<HashMap<u64, Weak<State>>> = RefCell::new(HashMap::new());
}
static NEXT_BACKEND: AtomicU64 = AtomicU64::new(1);
pub struct LibInputBackend {
options: Option<LibInputBackendOptions>,
state: Rc<State>,
}
impl State {
fn handle(&self, ev: &LibInputEvent) {
let Some(screen) = self.screen.borrow().clone() else {
return;
};
let input_root = self.input_root.borrow().clone();
let mut out = Vec::new();
self.mapping.borrow_mut().handle(ev, screen.scaled_size(), input_root.is_some(), &mut out);
let (Some(input_root), Some(on_input)) = (input_root, self.on_input.borrow().clone()) else {
return;
};
let mouse: Rc<dyn IInputDevice> = self.mouse.clone();
let touch: Rc<dyn IInputDevice> = self.touch.clone();
for event in out {
let args: RawEvent = match event {
EvDevRawEvent::Touch { type_, timestamp, position, raw_pointer_id } => {
Rc::new(RawTouchEventArgs::new(
touch.clone(),
timestamp,
input_root.clone(),
type_,
position,
RawInputModifiers::empty(),
raw_pointer_id,
))
}
EvDevRawEvent::Pointer { type_, timestamp, position, modifiers } => {
Rc::new(RawPointerEventArgs::new(
mouse.clone(),
timestamp,
input_root.clone(),
type_,
position,
modifiers,
))
}
EvDevRawEvent::Wheel { timestamp, position, delta, modifiers } => {
Rc::new(RawMouseWheelEventArgs::new(
mouse.clone(),
timestamp,
input_root.clone(),
position,
delta,
modifiers,
))
}
EvDevRawEvent::Key { .. } | EvDevRawEvent::Text { .. } => continue,
};
on_input(args);
}
}
}
fn input_thread(lib: LibInput, events: Vec<String>, matrix: [f32; 6], backend: u64, dispatcher: Arc<Dispatcher>) {
let Some(ctx) = lib.path_create_context() else {
eprintln!("libinput: the context could not be created");
return;
};
let fd = ctx.get_fd();
for file in &events {
ctx.path_add_device(file, &matrix);
}
loop {
ctx.dispatch();
while let Some(ev) = ctx.get_event() {
let type_ = ev.get_type();
let value = if is_touch_event(type_) {
let with_position = type_ == LIBINPUT_EVENT_TOUCH_DOWN || type_ == LIBINPUT_EVENT_TOUCH_MOTION;
ev.touch(with_position, POSITION_RANGE).map(|(slot, time_usec, x, y)| LibInputEvent::Touch {
type_,
slot,
time_usec,
x: x / POSITION_RANGE as f64,
y: y / POSITION_RANGE as f64,
})
} else if is_pointer_event(type_) {
Some(match type_ {
LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE => {
let (x, y) = ev.pointer_absolute(POSITION_RANGE);
LibInputEvent::PointerMotionAbsolute {
time_usec: ev.pointer_time_usec(),
x: x / POSITION_RANGE as f64,
y: y / POSITION_RANGE as f64,
}
}
LIBINPUT_EVENT_POINTER_BUTTON => {
let (button, button_state) = ev.pointer_button();
LibInputEvent::PointerButton { time_usec: ev.pointer_time_usec(), button, button_state }
}
LIBINPUT_EVENT_POINTER_AXIS => {
let (source_axis, value_discrete) = ev.pointer_axis();
LibInputEvent::PointerAxis {
time_usec: ev.pointer_time_usec(),
source_axis,
value_discrete,
}
}
LIBINPUT_EVENT_POINTER_SCROLL_WHEEL => LibInputEvent::PointerScrollWheel {
time_usec: ev.pointer_time_usec(),
value_v120: ev.pointer_scroll_value_v120(),
},
_ => LibInputEvent::PointerOther { type_ },
})
} else {
None
};
drop(ev);
if let Some(value) = value {
dispatcher.post(
move || {
let state =
BACKENDS.with(|backends| backends.borrow().get(&backend).and_then(Weak::upgrade));
if let Some(state) = state {
state.handle(&value);
}
},
DispatcherPriority::SEND,
);
}
ctx.dispatch();
}
let mut pfd = libc::pollfd { fd, events: libc::POLLIN, revents: 0 };
unsafe {
libc::poll(&mut pfd, 1, 10);
}
}
}
impl LibInputBackend {
pub fn new() -> Self {
Self::create(None)
}
pub fn with_options(options: LibInputBackendOptions) -> Self {
Self::create(Some(options))
}
fn create(options: Option<LibInputBackendOptions>) -> Self {
Self {
options,
state: Rc::new(State {
screen: RefCell::new(None),
input_root: RefCell::new(None),
on_input: RefCell::new(None),
mapping: RefCell::new(LibInputState::new()),
mouse: MouseDevice::new(),
touch: TouchDevice::new(),
}),
}
}
}
impl Default for LibInputBackend {
fn default() -> Self {
Self::new()
}
}
impl IInputBackend for LibInputBackend {
fn initialize(&self, screen: Rc<dyn IScreenInfoProvider>, on_input: Rc<dyn Fn(RawEvent)>) {
let screen_orientation = screen
.as_surface_orientation()
.map_or(SurfaceOrientation::Rotation0, |surface_orientation| surface_orientation.orientation());
*self.state.screen.borrow_mut() = Some(screen);
*self.state.on_input.borrow_mut() = Some(on_input);
let lib = load_library(c"libinput.so.10", LibInput::load).unwrap_or_else(|error| panic!("{error}"));
let events = event_devices(self.options.as_ref());
if let Some(logger) = Logger::try_get(LogEventLevel::Debug, "LinuxFramebuffer/Input/LibInput") {
logger.log(None, &format!("{} event devices", events.len()));
}
let backend = NEXT_BACKEND.fetch_add(1, Ordering::Relaxed);
BACKENDS.with(|backends| backends.borrow_mut().insert(backend, Rc::downgrade(&self.state)));
let dispatcher = Dispatcher::ui_thread();
let matrix = calibration_matrix(screen_orientation);
thread::Builder::new()
.name("Input Manager Worker".to_owned())
.spawn(move || input_thread(lib, events, matrix, backend, dispatcher))
.unwrap_or_else(|error| panic!("Unable to start the input thread: {error}"));
}
fn set_input_root(&self, root: Rc<dyn IInputRoot>) {
*self.state.input_root.borrow_mut() = Some(root);
}
}
}