Skip to main content

ferroui_linuxframebuffer/input/ev_dev/
ev_dev_backend.rs

1//! The input backend over event devices (the port of `EvDevBackend.cs`).
2//!
3//! A thread waits for the devices and reads their events. The reference
4//! handles them on that thread and the top-level posts the raw events to
5//! the UI thread; raw events hold objects of the UI thread here, so the
6//! thread posts what it read and the handlers run on the UI thread
7//! (docs/porting/linux-framebuffer-platform.md, section 4).
8
9use super::ev_dev_touch_screen::handle_events;
10use super::{
11    EvDevDevice, EvDevDeviceDescription, EvDevDeviceHandler, EvDevKeyboard, EvDevMouse, EvDevMultiTouchScreen,
12    EvDevRawEvent, EvDevSingleTouchScreen,
13};
14use crate::input::{IInputBackend, IScreenInfoProvider};
15use crate::native_unsafe_methods::input_event;
16use crate::raw_event_grouping::RawEvent;
17use ferroui_base::input::raw::{
18    RawKeyEventArgs, RawMouseWheelEventArgs, RawPointerEventArgs, RawTextInputEventArgs, RawTouchEventArgs,
19};
20use ferroui_base::input::{IInputDevice, IInputRoot, KeyDeviceType, MouseDevice, TouchDevice};
21use ferroui_base::threading::{Dispatcher, DispatcherPriority};
22use std::cell::{Cell, RefCell};
23use std::collections::HashMap;
24use std::os::fd::RawFd;
25use std::rc::{Rc, Weak};
26use std::sync::atomic::{AtomicU64, Ordering};
27use std::sync::Arc;
28use std::thread;
29
30/// The prefix of the environment variables that name devices.
31const DEVICE_VARIABLE_PREFIX: &str = "FERROUI_EVDEV_DEVICE_";
32
33struct DeviceEntry {
34    /// Keeps the device open for the thread that reads it.
35    _device: EvDevDevice,
36    handler: Box<dyn EvDevDeviceHandler>,
37    /// Whether the kernel dropped events and the next report is awaited.
38    dropped: bool,
39    /// The device of the pointer and touch events of the handler.
40    pointer_device: Rc<dyn IInputDevice>,
41}
42
43#[derive(Default)]
44struct State {
45    handlers: RefCell<Vec<DeviceEntry>>,
46    on_input: RefCell<Option<Rc<dyn Fn(RawEvent)>>>,
47    input_root: RefCell<Option<Rc<dyn IInputRoot>>>,
48    screen: RefCell<Option<Rc<dyn IScreenInfoProvider>>>,
49    warned_about_the_input_root: Cell<bool>,
50}
51
52ferroui_base::ferro_thread_local! {
53    /// The backends of the UI thread, by the number their input threads
54    /// post with.
55    static BACKENDS: RefCell<HashMap<u64, Weak<State>>> = RefCell::new(HashMap::new());
56}
57
58static NEXT_BACKEND: AtomicU64 = AtomicU64::new(1);
59
60pub struct EvDevBackend {
61    device_descriptions: Vec<EvDevDeviceDescription>,
62    state: Rc<State>,
63}
64
65impl State {
66    /// `HandleEvents` of the handler of device `index`, on the UI thread.
67    fn handle(&self, index: usize, events: &[input_event]) {
68        let Some(screen) = self.screen.borrow().clone() else {
69            return;
70        };
71        let mut out = Vec::new();
72        let pointer_device = {
73            let mut handlers = self.handlers.borrow_mut();
74            let Some(entry) = handlers.get_mut(index) else {
75                return;
76            };
77            handle_events(entry.handler.as_mut(), &mut entry.dropped, events, screen.scaled_size(), &mut out);
78            entry.pointer_device.clone()
79        };
80        if out.is_empty() {
81            return;
82        }
83
84        let Some(input_root) = self.input_root.borrow().clone() else {
85            // The reference throws here, on its input thread, which
86            // prints the exception and goes on.
87            if !self.warned_about_the_input_root.replace(true) {
88                eprintln!("InputRoot hasn't been set");
89            }
90            return;
91        };
92        let Some(on_input) = self.on_input.borrow().clone() else {
93            return;
94        };
95
96        for event in out {
97            let args: RawEvent = match event {
98                EvDevRawEvent::Touch { type_, timestamp, position, raw_pointer_id } => Rc::new(RawTouchEventArgs::new(
99                    pointer_device.clone(),
100                    timestamp,
101                    input_root.clone(),
102                    type_,
103                    position,
104                    ferroui_base::input::RawInputModifiers::empty(),
105                    raw_pointer_id,
106                )),
107                EvDevRawEvent::Pointer { type_, timestamp, position, modifiers } => Rc::new(RawPointerEventArgs::new(
108                    pointer_device.clone(),
109                    timestamp,
110                    input_root.clone(),
111                    type_,
112                    position,
113                    modifiers,
114                )),
115                EvDevRawEvent::Wheel { timestamp, position, delta, modifiers } => Rc::new(RawMouseWheelEventArgs::new(
116                    pointer_device.clone(),
117                    timestamp,
118                    input_root.clone(),
119                    position,
120                    delta,
121                    modifiers,
122                )),
123                EvDevRawEvent::Key { type_, timestamp, key, modifiers, physical_key, key_symbol } => {
124                    let Some(keyboard) = keyboard_device() else {
125                        continue;
126                    };
127                    Rc::new(RawKeyEventArgs::new(
128                        keyboard,
129                        timestamp,
130                        input_root.clone(),
131                        type_,
132                        key,
133                        modifiers,
134                        physical_key,
135                        key_symbol,
136                        KeyDeviceType::Keyboard,
137                    ))
138                }
139                EvDevRawEvent::Text { timestamp, text } => {
140                    let Some(keyboard) = keyboard_device() else {
141                        continue;
142                    };
143                    Rc::new(RawTextInputEventArgs::new(keyboard, timestamp, input_root.clone(), text))
144                }
145            };
146            on_input(args);
147        }
148    }
149}
150
151/// The keyboard device of the platform, as an input device.
152fn keyboard_device() -> Option<Rc<dyn IInputDevice>> {
153    crate::LinuxFramebufferPlatform::keyboard_device().map(|keyboard| keyboard as Rc<dyn IInputDevice>)
154}
155
156/// `InputThread`: waits for the devices and posts what it reads.
157fn input_thread(epoll: RawFd, fds: Vec<RawFd>, backend: u64, dispatcher: Arc<Dispatcher>) {
158    const MAX_EVENTS: usize = 16;
159    let mut events = [libc::epoll_event { events: 0, u64: 0 }; MAX_EVENTS];
160    loop {
161        // SAFETY: `events` has room for `MAX_EVENTS` events.
162        let event_count = unsafe { libc::epoll_wait(epoll, events.as_mut_ptr(), MAX_EVENTS as i32, 1000) };
163        let event_count = event_count.clamp(0, MAX_EVENTS as i32) as usize;
164        for ev in &events[..event_count] {
165            let index = ev.u64 as usize;
166            let Some(&fd) = fds.get(index) else {
167                continue;
168            };
169            let read = EvDevDevice::read_events(fd);
170            if read.is_empty() {
171                continue;
172            }
173            dispatcher.post(
174                move || {
175                    let state = BACKENDS.with(|backends| backends.borrow().get(&backend).and_then(Weak::upgrade));
176                    if let Some(state) = state {
177                        state.handle(index, &read);
178                    }
179                },
180                DispatcherPriority::SEND,
181            );
182        }
183    }
184}
185
186impl EvDevBackend {
187    pub fn new(devices: Vec<EvDevDeviceDescription>) -> Self {
188        Self { device_descriptions: devices, state: Rc::new(State::default()) }
189    }
190
191    /// The backend of the devices the environment names: one variable
192    /// `FERROUI_EVDEV_DEVICE_{name}` per device, with the format
193    /// `(path):type=(touchscreen|multitouch|keyboard|mouse):[calibration=..]`.
194    pub fn create_from_environment() -> Result<Self, String> {
195        let mut variables: Vec<(String, String)> =
196            std::env::vars().filter(|(key, _)| key.starts_with(DEVICE_VARIABLE_PREFIX)).collect();
197        variables.sort();
198
199        let mut device_descriptions = Vec::new();
200        for (_, value) in variables {
201            device_descriptions.push(EvDevDeviceDescription::parse_from_env(&value)?);
202        }
203
204        if device_descriptions.is_empty() {
205            return Err(format!(
206                "No device device description found, specify devices by adding {DEVICE_VARIABLE_PREFIX}{{name}} environment variables"
207            ));
208        }
209
210        Ok(Self::new(device_descriptions))
211    }
212}
213
214impl IInputBackend for EvDevBackend {
215    /// # Panics
216    /// Panics when a device cannot be opened or is not of the kind its
217    /// description names.
218    fn initialize(&self, info: Rc<dyn IScreenInfoProvider>, on_input: Rc<dyn Fn(RawEvent)>) {
219        *self.state.on_input.borrow_mut() = Some(on_input);
220        *self.state.screen.borrow_mut() = Some(info);
221        // SAFETY: a plain system call.
222        let epoll = unsafe { libc::epoll_create1(0) };
223        let mut fds = Vec::new();
224
225        for (c, description) in self.device_descriptions.iter().enumerate() {
226            let dev =
227                EvDevDevice::open(description.path().unwrap_or_default()).unwrap_or_else(|error| panic!("{error}"));
228            let failed = |error: String| -> ! { panic!("{error}: {}", description.path().unwrap_or_default()) };
229            let (handler, pointer_device): (Box<dyn EvDevDeviceHandler>, Rc<dyn IInputDevice>) = match description {
230                EvDevDeviceDescription::TouchScreen(touch) => (
231                    Box::new(EvDevSingleTouchScreen::new(dev.info(), touch).unwrap_or_else(|error| failed(error))),
232                    TouchDevice::new(),
233                ),
234                EvDevDeviceDescription::MultiTouchScreen(touch) => (
235                    Box::new(EvDevMultiTouchScreen::new(dev.info(), touch).unwrap_or_else(|error| failed(error))),
236                    TouchDevice::new(),
237                ),
238                EvDevDeviceDescription::Keyboard(_) => (Box::new(EvDevKeyboard::new()), MouseDevice::new()),
239                EvDevDeviceDescription::Mouse(_) => (Box::new(EvDevMouse::new(dev.info())), MouseDevice::new()),
240            };
241
242            let fd = dev.fd();
243            fds.push(fd);
244            self.state.handlers.borrow_mut().push(DeviceEntry {
245                _device: dev,
246                handler,
247                dropped: false,
248                pointer_device,
249            });
250
251            let mut ev = libc::epoll_event { events: libc::EPOLLIN as u32, u64: c as u64 };
252            // SAFETY: `ev` is valid for the call; the descriptors are open.
253            unsafe {
254                libc::epoll_ctl(epoll, libc::EPOLL_CTL_ADD, fd, &mut ev);
255            }
256        }
257
258        let backend = NEXT_BACKEND.fetch_add(1, Ordering::Relaxed);
259        BACKENDS.with(|backends| backends.borrow_mut().insert(backend, Rc::downgrade(&self.state)));
260        let dispatcher = Dispatcher::ui_thread();
261        thread::Builder::new()
262            .name("evdev input".to_owned())
263            .spawn(move || input_thread(epoll, fds, backend, dispatcher))
264            .unwrap_or_else(|error| panic!("Unable to start the input thread: {error}"));
265    }
266
267    fn set_input_root(&self, root: Rc<dyn IInputRoot>) {
268        *self.state.input_root.borrow_mut() = Some(root);
269    }
270}