Skip to main content

ferroui_linuxframebuffer/input/lib_input/
lib_input_backend.rs

1//! The input backend over `libinput` (the port of `LibInputBackend.cs`).
2//!
3//! A thread owns the context of the library and reads its events. The
4//! reference maps them to raw events on that thread; raw events hold
5//! objects of the UI thread here, so the thread posts each event as a
6//! value ([`LibInputEvent`]) and the mapping runs on the UI thread
7//! (docs/porting/linux-framebuffer-platform.md, section 4).
8
9use 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
16/// The range positions are asked of the library in: the thread does not
17/// know the size of the screen, so it asks for a position in this range
18/// and the UI thread scales it (the transformation of the library is
19/// linear).
20pub(crate) const POSITION_RANGE: u32 = 1 << 20;
21
22/// What the mapping needs of an event of the library.
23#[derive(Clone, Copy, Debug, PartialEq)]
24pub enum LibInputEvent {
25    /// A touch event below `LIBINPUT_EVENT_TOUCH_FRAME`: the position is a
26    /// fraction of the screen, and is meaningful for a down and a motion.
27    Touch { type_: i32, slot: i32, time_usec: u64, x: f64, y: f64 },
28    /// `LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE`, the position as a fraction.
29    PointerMotionAbsolute { time_usec: u64, x: f64, y: f64 },
30    /// `LIBINPUT_EVENT_POINTER_BUTTON`.
31    PointerButton { time_usec: u64, button: u32, button_state: i32 },
32    /// `LIBINPUT_EVENT_POINTER_AXIS`: the source and, for a wheel, the
33    /// vertical value in steps.
34    PointerAxis { time_usec: u64, source_axis: i32, value_discrete: f64 },
35    /// `LIBINPUT_EVENT_POINTER_SCROLL_WHEEL`.
36    PointerScrollWheel { time_usec: u64, value_v120: f64 },
37    /// Another event of the pointer range.
38    PointerOther { type_: i32 },
39}
40
41/// The calibration matrix (the first two rows) that turns the input of a
42/// device with the orientation of the screen.
43pub(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], // Normal
49    }
50}
51
52/// Whether the thread hands an event of this type to the touch mapping.
53pub(crate) fn is_touch_event(type_: i32) -> bool {
54    (LIBINPUT_EVENT_TOUCH_DOWN..=LIBINPUT_EVENT_TOUCH_CANCEL).contains(&type_)
55}
56
57/// Whether the thread hands an event of this type to the pointer
58/// mapping. As in the reference the range ends at the legacy axis event:
59/// the wheel scroll event that follows it in the enumeration describes
60/// the same step of the wheel.
61pub(crate) fn is_pointer_event(type_: i32) -> bool {
62    (LIBINPUT_EVENT_POINTER_MOTION..=LIBINPUT_EVENT_POINTER_AXIS).contains(&type_)
63}
64
65/// The state the mapping keeps between events (the fields of the three
66/// partial files of the reference that are not objects of the UI thread).
67#[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    /// Maps one event. `info` is the scaled size of the screen;
79    /// `has_input_root` whether an input root was set (touch events
80    /// before that are dropped after the state is updated, as in the
81    /// reference).
82    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
90/// The devices of the options, or every event device of the system.
91pub(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        /// The backends of the UI thread, by the number their input
139        /// threads post with.
140        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        /// The mapping of one event, on the UI thread.
152        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            // The reference throws on its input thread when a pointer
160            // event arrives before the input root is set.
161            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    /// `InputThread`.
207    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            // SAFETY: one valid entry for the call.
281            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        /// # Panics
319        /// Panics when the library cannot be loaded.
320        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}