1use super::lib_input_native_unsafe_methods::lib_input_event_type::*;
10use super::LibInputBackendOptions;
11use crate::input::ev_dev::EvDevRawEvent;
12use ferroui_base::platform::SurfaceOrientation;
13use ferroui_base::{Point, Size};
14use std::collections::HashMap;
15
16pub(crate) const POSITION_RANGE: u32 = 1 << 20;
21
22#[derive(Clone, Copy, Debug, PartialEq)]
24pub enum LibInputEvent {
25 Touch { type_: i32, slot: i32, time_usec: u64, x: f64, y: f64 },
28 PointerMotionAbsolute { time_usec: u64, x: f64, y: f64 },
30 PointerButton { time_usec: u64, button: u32, button_state: i32 },
32 PointerAxis { time_usec: u64, source_axis: i32, value_discrete: f64 },
35 PointerScrollWheel { time_usec: u64, value_v120: f64 },
37 PointerOther { type_: i32 },
39}
40
41pub(crate) fn calibration_matrix(screen_orientation: SurfaceOrientation) -> [f32; 6] {
44 match screen_orientation {
45 SurfaceOrientation::Rotation90 => [0.0, 1.0, 0.0, -1.0, 0.0, 1.0],
46 SurfaceOrientation::Rotation180 => [-1.0, 0.0, 1.0, 0.0, -1.0, 1.0],
47 SurfaceOrientation::Rotation270 => [0.0, -1.0, 1.0, 1.0, 0.0, 0.0],
48 _ => [1.0, 0.0, 0.0, 0.0, 1.0, 0.0], }
50}
51
52pub(crate) fn is_touch_event(type_: i32) -> bool {
54 (LIBINPUT_EVENT_TOUCH_DOWN..=LIBINPUT_EVENT_TOUCH_CANCEL).contains(&type_)
55}
56
57pub(crate) fn is_pointer_event(type_: i32) -> bool {
62 (LIBINPUT_EVENT_POINTER_MOTION..=LIBINPUT_EVENT_POINTER_AXIS).contains(&type_)
63}
64
65#[derive(Default)]
68pub struct LibInputState {
69 pub(crate) mouse_position: Point,
70 pub(crate) pointers: HashMap<i32, Point>,
71}
72
73impl LibInputState {
74 pub fn new() -> Self {
75 Self::default()
76 }
77
78 pub fn handle(&mut self, ev: &LibInputEvent, info: Size, has_input_root: bool, out: &mut Vec<EvDevRawEvent>) {
83 match ev {
84 LibInputEvent::Touch { .. } => self.handle_touch(ev, info, has_input_root, out),
85 _ => self.handle_pointer(ev, info, out),
86 }
87 }
88}
89
90pub(crate) fn event_devices(options: Option<&LibInputBackendOptions>) -> Vec<String> {
92 if let Some(events) = options.and_then(|options| options.events.clone()) {
93 return events;
94 }
95 let mut files: Vec<String> = std::fs::read_dir("/dev/input")
96 .map(|entries| {
97 entries
98 .filter_map(Result::ok)
99 .filter(|entry| entry.file_name().to_string_lossy().starts_with("event"))
100 .map(|entry| entry.path().to_string_lossy().into_owned())
101 .collect()
102 })
103 .unwrap_or_default();
104 files.sort();
105 files
106}
107
108#[cfg(target_os = "linux")]
109pub use imp::LibInputBackend;
110
111#[cfg(target_os = "linux")]
112mod imp {
113 use super::super::lib_input_native_unsafe_methods::LibInput;
114 use crate::native_library::load_library;
115 use super::*;
116 use crate::input::{IInputBackend, IScreenInfoProvider};
117 use crate::raw_event_grouping::RawEvent;
118 use ferroui_base::input::raw::{RawMouseWheelEventArgs, RawPointerEventArgs, RawTouchEventArgs};
119 use ferroui_base::input::{IInputDevice, IInputRoot, MouseDevice, RawInputModifiers, TouchDevice};
120 use ferroui_base::logging::{LogEventLevel, Logger};
121 use ferroui_base::threading::{Dispatcher, DispatcherPriority};
122 use std::cell::RefCell;
123 use std::rc::{Rc, Weak};
124 use std::sync::atomic::{AtomicU64, Ordering};
125 use std::sync::Arc;
126 use std::thread;
127
128 struct State {
129 screen: RefCell<Option<Rc<dyn IScreenInfoProvider>>>,
130 input_root: RefCell<Option<Rc<dyn IInputRoot>>>,
131 on_input: RefCell<Option<Rc<dyn Fn(RawEvent)>>>,
132 mapping: RefCell<LibInputState>,
133 mouse: Rc<MouseDevice>,
134 touch: Rc<TouchDevice>,
135 }
136
137 ferroui_base::ferro_thread_local! {
138 static BACKENDS: RefCell<HashMap<u64, Weak<State>>> = RefCell::new(HashMap::new());
141 }
142
143 static NEXT_BACKEND: AtomicU64 = AtomicU64::new(1);
144
145 pub struct LibInputBackend {
146 options: Option<LibInputBackendOptions>,
147 state: Rc<State>,
148 }
149
150 impl State {
151 fn handle(&self, ev: &LibInputEvent) {
153 let Some(screen) = self.screen.borrow().clone() else {
154 return;
155 };
156 let input_root = self.input_root.borrow().clone();
157 let mut out = Vec::new();
158 self.mapping.borrow_mut().handle(ev, screen.scaled_size(), input_root.is_some(), &mut out);
159 let (Some(input_root), Some(on_input)) = (input_root, self.on_input.borrow().clone()) else {
162 return;
163 };
164 let mouse: Rc<dyn IInputDevice> = self.mouse.clone();
165 let touch: Rc<dyn IInputDevice> = self.touch.clone();
166 for event in out {
167 let args: RawEvent = match event {
168 EvDevRawEvent::Touch { type_, timestamp, position, raw_pointer_id } => {
169 Rc::new(RawTouchEventArgs::new(
170 touch.clone(),
171 timestamp,
172 input_root.clone(),
173 type_,
174 position,
175 RawInputModifiers::empty(),
176 raw_pointer_id,
177 ))
178 }
179 EvDevRawEvent::Pointer { type_, timestamp, position, modifiers } => {
180 Rc::new(RawPointerEventArgs::new(
181 mouse.clone(),
182 timestamp,
183 input_root.clone(),
184 type_,
185 position,
186 modifiers,
187 ))
188 }
189 EvDevRawEvent::Wheel { timestamp, position, delta, modifiers } => {
190 Rc::new(RawMouseWheelEventArgs::new(
191 mouse.clone(),
192 timestamp,
193 input_root.clone(),
194 position,
195 delta,
196 modifiers,
197 ))
198 }
199 EvDevRawEvent::Key { .. } | EvDevRawEvent::Text { .. } => continue,
200 };
201 on_input(args);
202 }
203 }
204 }
205
206 fn input_thread(lib: LibInput, events: Vec<String>, matrix: [f32; 6], backend: u64, dispatcher: Arc<Dispatcher>) {
208 let Some(ctx) = lib.path_create_context() else {
209 eprintln!("libinput: the context could not be created");
210 return;
211 };
212 let fd = ctx.get_fd();
213
214 for file in &events {
215 ctx.path_add_device(file, &matrix);
216 }
217
218 loop {
219 ctx.dispatch();
220 while let Some(ev) = ctx.get_event() {
221 let type_ = ev.get_type();
222 let value = if is_touch_event(type_) {
223 let with_position = type_ == LIBINPUT_EVENT_TOUCH_DOWN || type_ == LIBINPUT_EVENT_TOUCH_MOTION;
224 ev.touch(with_position, POSITION_RANGE).map(|(slot, time_usec, x, y)| LibInputEvent::Touch {
225 type_,
226 slot,
227 time_usec,
228 x: x / POSITION_RANGE as f64,
229 y: y / POSITION_RANGE as f64,
230 })
231 } else if is_pointer_event(type_) {
232 Some(match type_ {
233 LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE => {
234 let (x, y) = ev.pointer_absolute(POSITION_RANGE);
235 LibInputEvent::PointerMotionAbsolute {
236 time_usec: ev.pointer_time_usec(),
237 x: x / POSITION_RANGE as f64,
238 y: y / POSITION_RANGE as f64,
239 }
240 }
241 LIBINPUT_EVENT_POINTER_BUTTON => {
242 let (button, button_state) = ev.pointer_button();
243 LibInputEvent::PointerButton { time_usec: ev.pointer_time_usec(), button, button_state }
244 }
245 LIBINPUT_EVENT_POINTER_AXIS => {
246 let (source_axis, value_discrete) = ev.pointer_axis();
247 LibInputEvent::PointerAxis {
248 time_usec: ev.pointer_time_usec(),
249 source_axis,
250 value_discrete,
251 }
252 }
253 LIBINPUT_EVENT_POINTER_SCROLL_WHEEL => LibInputEvent::PointerScrollWheel {
254 time_usec: ev.pointer_time_usec(),
255 value_v120: ev.pointer_scroll_value_v120(),
256 },
257 _ => LibInputEvent::PointerOther { type_ },
258 })
259 } else {
260 None
261 };
262 drop(ev);
263
264 if let Some(value) = value {
265 dispatcher.post(
266 move || {
267 let state =
268 BACKENDS.with(|backends| backends.borrow().get(&backend).and_then(Weak::upgrade));
269 if let Some(state) = state {
270 state.handle(&value);
271 }
272 },
273 DispatcherPriority::SEND,
274 );
275 }
276 ctx.dispatch();
277 }
278
279 let mut pfd = libc::pollfd { fd, events: libc::POLLIN, revents: 0 };
280 unsafe {
282 libc::poll(&mut pfd, 1, 10);
283 }
284 }
285 }
286
287 impl LibInputBackend {
288 pub fn new() -> Self {
289 Self::create(None)
290 }
291
292 pub fn with_options(options: LibInputBackendOptions) -> Self {
293 Self::create(Some(options))
294 }
295
296 fn create(options: Option<LibInputBackendOptions>) -> Self {
297 Self {
298 options,
299 state: Rc::new(State {
300 screen: RefCell::new(None),
301 input_root: RefCell::new(None),
302 on_input: RefCell::new(None),
303 mapping: RefCell::new(LibInputState::new()),
304 mouse: MouseDevice::new(),
305 touch: TouchDevice::new(),
306 }),
307 }
308 }
309 }
310
311 impl Default for LibInputBackend {
312 fn default() -> Self {
313 Self::new()
314 }
315 }
316
317 impl IInputBackend for LibInputBackend {
318 fn initialize(&self, screen: Rc<dyn IScreenInfoProvider>, on_input: Rc<dyn Fn(RawEvent)>) {
321 let screen_orientation = screen
322 .as_surface_orientation()
323 .map_or(SurfaceOrientation::Rotation0, |surface_orientation| surface_orientation.orientation());
324 *self.state.screen.borrow_mut() = Some(screen);
325 *self.state.on_input.borrow_mut() = Some(on_input);
326 let lib = load_library(c"libinput.so.10", LibInput::load).unwrap_or_else(|error| panic!("{error}"));
327 let events = event_devices(self.options.as_ref());
328 if let Some(logger) = Logger::try_get(LogEventLevel::Debug, "LinuxFramebuffer/Input/LibInput") {
329 logger.log(None, &format!("{} event devices", events.len()));
330 }
331
332 let backend = NEXT_BACKEND.fetch_add(1, Ordering::Relaxed);
333 BACKENDS.with(|backends| backends.borrow_mut().insert(backend, Rc::downgrade(&self.state)));
334 let dispatcher = Dispatcher::ui_thread();
335 let matrix = calibration_matrix(screen_orientation);
336 thread::Builder::new()
337 .name("Input Manager Worker".to_owned())
338 .spawn(move || input_thread(lib, events, matrix, backend, dispatcher))
339 .unwrap_or_else(|error| panic!("Unable to start the input thread: {error}"));
340 }
341
342 fn set_input_root(&self, root: Rc<dyn IInputRoot>) {
343 *self.state.input_root.borrow_mut() = Some(root);
344 }
345 }
346}