i_slint_backend_linuxkms/calloop_backend/
input.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! This module contains the code to receive input events from libinput
5
6use std::cell::RefCell;
7#[cfg(feature = "libseat")]
8use std::collections::HashMap;
9#[cfg(not(feature = "libseat"))]
10use std::fs::{File, OpenOptions};
11use std::os::fd::OwnedFd;
12#[cfg(feature = "libseat")]
13use std::os::fd::{AsFd, AsRawFd, FromRawFd, IntoRawFd, RawFd};
14#[cfg(not(feature = "libseat"))]
15use std::os::unix::fs::OpenOptionsExt;
16use std::path::Path;
17use std::pin::Pin;
18use std::rc::Rc;
19
20use i_slint_core::api::LogicalPosition;
21use i_slint_core::platform::{PlatformError, PointerEventButton, WindowEvent};
22use i_slint_core::window::WindowAdapter;
23use i_slint_core::{Property, SharedString};
24use input::LibinputInterface;
25
26use input::event::keyboard::{KeyState, KeyboardEventTrait};
27use input::event::touch::TouchEventPosition;
28use xkbcommon::*;
29
30use crate::fullscreenwindowadapter::FullscreenWindowAdapter;
31
32#[cfg(feature = "libseat")]
33struct SeatWrap {
34    seat: Rc<RefCell<libseat::Seat>>,
35    device_for_fd: HashMap<RawFd, libseat::Device>,
36}
37
38#[cfg(feature = "libseat")]
39impl SeatWrap {
40    pub fn new(seat: &Rc<RefCell<libseat::Seat>>) -> input::Libinput {
41        let seat_name = seat.borrow_mut().name().to_string();
42        let mut libinput = input::Libinput::new_with_udev(Self {
43            seat: seat.clone(),
44            device_for_fd: Default::default(),
45        });
46        libinput.udev_assign_seat(&seat_name).unwrap();
47        libinput
48    }
49}
50
51#[cfg(feature = "libseat")]
52impl<'a> LibinputInterface for SeatWrap {
53    fn open_restricted(&mut self, path: &Path, flags: i32) -> Result<OwnedFd, i32> {
54        self.seat
55            .borrow_mut()
56            .open_device(&path)
57            .map(|device| {
58                let flags = nix::fcntl::OFlag::from_bits_retain(flags);
59                let fd = device.as_fd();
60                nix::fcntl::fcntl(fd, nix::fcntl::FcntlArg::F_SETFL(flags))
61                    .map_err(|e| format!("Error applying libinput provided open fd flags: {e}"))
62                    .unwrap();
63
64                let raw_fd = fd.as_raw_fd();
65                self.device_for_fd.insert(raw_fd, device);
66                // Safety: API requires us to own it, but in close_restricted() we'll take it back.
67                unsafe { OwnedFd::from_raw_fd(raw_fd) }
68            })
69            .map_err(|e| e.0.into())
70    }
71    fn close_restricted(&mut self, fd: OwnedFd) {
72        // Transfer ownership back to libseat
73        let fd = fd.into_raw_fd();
74        if let Some(device_id) = self.device_for_fd.remove(&fd) {
75            let _ = self.seat.borrow_mut().close_device(device_id);
76        }
77    }
78}
79
80#[cfg(not(feature = "libseat"))]
81struct DirectDeviceAccess {}
82
83#[cfg(not(feature = "libseat"))]
84impl DirectDeviceAccess {
85    pub fn new() -> input::Libinput {
86        let mut libinput = input::Libinput::new_with_udev(Self {});
87        libinput.udev_assign_seat("seat0").unwrap();
88        libinput
89    }
90}
91
92#[cfg(not(feature = "libseat"))]
93impl<'a> LibinputInterface for DirectDeviceAccess {
94    fn open_restricted(&mut self, path: &Path, flags_raw: i32) -> Result<OwnedFd, i32> {
95        let flags = nix::fcntl::OFlag::from_bits_retain(flags_raw);
96        OpenOptions::new()
97            .custom_flags(flags_raw)
98            .read(
99                flags.contains(nix::fcntl::OFlag::O_RDONLY)
100                    | flags.contains(nix::fcntl::OFlag::O_RDWR),
101            )
102            .write(
103                flags.contains(nix::fcntl::OFlag::O_WRONLY)
104                    | flags.contains(nix::fcntl::OFlag::O_RDWR),
105            )
106            .open(path)
107            .map(|file| file.into())
108            .map_err(|err| err.raw_os_error().unwrap())
109    }
110    fn close_restricted(&mut self, fd: OwnedFd) {
111        drop(File::from(fd));
112    }
113}
114
115pub struct LibInputHandler<'a> {
116    libinput: input::Libinput,
117    token: Option<calloop::Token>,
118    mouse_pos: Pin<Rc<Property<Option<LogicalPosition>>>>,
119    last_touch_pos: LogicalPosition,
120    window: &'a RefCell<Option<Rc<FullscreenWindowAdapter>>>,
121    keystate: Option<xkb::State>,
122    libinput_event_hook: &'a Option<Box<dyn Fn(&::input::Event) -> bool>>,
123}
124
125impl<'a> LibInputHandler<'a> {
126    pub fn init<T>(
127        window: &'a RefCell<Option<Rc<FullscreenWindowAdapter>>>,
128        event_loop_handle: &calloop::LoopHandle<'a, T>,
129        #[cfg(feature = "libseat")] seat: &'a Rc<RefCell<libseat::Seat>>,
130        libinput_event_hook: &'a Option<Box<dyn Fn(&::input::Event) -> bool>>,
131    ) -> Result<Pin<Rc<Property<Option<LogicalPosition>>>>, PlatformError> {
132        #[cfg(feature = "libseat")]
133        let libinput = SeatWrap::new(seat);
134        #[cfg(not(feature = "libseat"))]
135        let libinput = DirectDeviceAccess::new();
136
137        let mouse_pos_property = Rc::pin(Property::new(None));
138
139        let handler = Self {
140            libinput,
141            token: Default::default(),
142            mouse_pos: mouse_pos_property.clone(),
143            last_touch_pos: Default::default(),
144            window,
145            keystate: Default::default(),
146            libinput_event_hook,
147        };
148
149        event_loop_handle
150            .insert_source(handler, move |_, _, _| {})
151            .map_err(|e| format!("Error registering libinput event source: {e}"))?;
152
153        Ok(mouse_pos_property)
154    }
155}
156
157impl<'a> calloop::EventSource for LibInputHandler<'a> {
158    type Event = i_slint_core::platform::WindowEvent;
159    type Metadata = ();
160    type Ret = ();
161    type Error = std::io::Error;
162
163    fn process_events<F>(
164        &mut self,
165        _readiness: calloop::Readiness,
166        token: calloop::Token,
167        _callback: F,
168    ) -> Result<calloop::PostAction, Self::Error>
169    where
170        F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
171    {
172        if Some(token) != self.token {
173            return Ok(calloop::PostAction::Continue);
174        }
175
176        self.libinput.dispatch()?;
177
178        let Some(adapter) = self.window.borrow().clone() else {
179            return Ok(calloop::PostAction::Continue);
180        };
181        let window = adapter.window();
182        let screen_size = window.size().to_logical(window.scale_factor());
183
184        for event in &mut self.libinput {
185            if self.libinput_event_hook.as_ref().map_or(false, |hook| hook(&event)) {
186                continue;
187            };
188            match event {
189                input::Event::Pointer(pointer_event) => {
190                    match pointer_event {
191                        input::event::PointerEvent::Motion(motion_event) => {
192                            let mut mouse_pos =
193                                self.mouse_pos.as_ref().get().unwrap_or(LogicalPosition {
194                                    x: screen_size.width / 2.,
195                                    y: screen_size.height / 2.,
196                                });
197                            mouse_pos.x = (mouse_pos.x + motion_event.dx() as f32)
198                                .clamp(0., screen_size.width);
199                            mouse_pos.y = (mouse_pos.y + motion_event.dy() as f32)
200                                .clamp(0., screen_size.height);
201                            self.mouse_pos.set(Some(mouse_pos));
202                            let event = WindowEvent::PointerMoved { position: mouse_pos };
203                            window.try_dispatch_event(event).map_err(Self::Error::other)?;
204                        }
205                        input::event::PointerEvent::MotionAbsolute(abs_motion_event) => {
206                            let mouse_pos = LogicalPosition {
207                                x: abs_motion_event.absolute_x_transformed(screen_size.width as u32)
208                                    as _,
209                                y: abs_motion_event
210                                    .absolute_y_transformed(screen_size.height as u32)
211                                    as _,
212                            };
213                            self.mouse_pos.set(Some(mouse_pos));
214                            let event = WindowEvent::PointerMoved { position: mouse_pos };
215                            window.try_dispatch_event(event).map_err(Self::Error::other)?;
216                        }
217                        input::event::PointerEvent::Button(button_event) => {
218                            // https://github.com/torvalds/linux/blob/0dd2a6fb1e34d6dcb96806bc6b111388ad324722/include/uapi/linux/input-event-codes.h#L355
219                            let button = match button_event.button() {
220                                0x110 => PointerEventButton::Left,
221                                0x111 => PointerEventButton::Right,
222                                0x112 => PointerEventButton::Middle,
223                                0x116 => PointerEventButton::Back,
224                                0x115 => PointerEventButton::Forward,
225                                _ => PointerEventButton::Other,
226                            };
227                            let mouse_pos = self.mouse_pos.as_ref().get().unwrap_or_default();
228                            let event = match button_event.button_state() {
229                                input::event::tablet_pad::ButtonState::Pressed => {
230                                    WindowEvent::PointerPressed { position: mouse_pos, button }
231                                }
232                                input::event::tablet_pad::ButtonState::Released => {
233                                    WindowEvent::PointerReleased { position: mouse_pos, button }
234                                }
235                            };
236                            window.try_dispatch_event(event).map_err(Self::Error::other)?;
237                        }
238                        _ => {}
239                    }
240                }
241                input::Event::Touch(touch_event) => {
242                    if let Some(event) = match touch_event {
243                        input::event::TouchEvent::Down(touch_down_event) => {
244                            self.last_touch_pos = LogicalPosition::new(
245                                touch_down_event.x_transformed(screen_size.width as u32) as _,
246                                touch_down_event.y_transformed(screen_size.height as u32) as _,
247                            );
248                            Some(WindowEvent::PointerPressed {
249                                position: self.last_touch_pos,
250                                button: PointerEventButton::Left,
251                            })
252                        }
253                        input::event::TouchEvent::Up(..) => Some(WindowEvent::PointerReleased {
254                            position: self.last_touch_pos,
255                            button: PointerEventButton::Left,
256                        }),
257                        input::event::TouchEvent::Motion(touch_motion_event) => {
258                            self.last_touch_pos = LogicalPosition::new(
259                                touch_motion_event.x_transformed(screen_size.width as u32) as _,
260                                touch_motion_event.y_transformed(screen_size.height as u32) as _,
261                            );
262                            Some(WindowEvent::PointerMoved { position: self.last_touch_pos })
263                        }
264                        _ => None,
265                    } {
266                        window.try_dispatch_event(event).map_err(Self::Error::other)?;
267                    }
268                }
269                input::Event::Keyboard(input::event::KeyboardEvent::Key(key_event)) => {
270                    // On Linux key codes have a fixed offset of 8: https://docs.rs/xkbcommon/0.6.0/xkbcommon/xkb/struct.Keycode.html
271                    let key_code = xkb::Keycode::new(key_event.key() + 8);
272                    let state = key_event.key_state();
273
274                    let xkb_key_state = self.keystate.get_or_insert_with(|| {
275                        let xkb_context = xkb::Context::new(xkb::CONTEXT_NO_FLAGS);
276                        let keymap =
277                            xkb::Keymap::new_from_names(&xkb_context, "", "", "", "", None, 0)
278                                .expect("Error compiling keymap");
279                        xkb::State::new(&keymap)
280                    });
281
282                    let sym = xkb_key_state.key_get_one_sym(key_code);
283
284                    xkb_key_state.update_key(
285                        key_code,
286                        match state {
287                            input::event::tablet_pad::KeyState::Pressed => xkb::KeyDirection::Down,
288                            input::event::tablet_pad::KeyState::Released => xkb::KeyDirection::Up,
289                        },
290                    );
291
292                    let control = xkb_key_state
293                        .mod_name_is_active(xkb::MOD_NAME_CTRL, xkb::STATE_MODS_EFFECTIVE);
294                    let alt = xkb_key_state
295                        .mod_name_is_active(xkb::MOD_NAME_ALT, xkb::STATE_MODS_EFFECTIVE);
296
297                    if state == KeyState::Pressed {
298                        //eprintln!(
299                        //"key {} state {:#?} sym {:x} control {control} alt {alt}",
300                        //key_code, state, sym
301                        //);
302
303                        if control && alt && sym == xkb::Keysym::BackSpace
304                            || control && alt && sym == xkb::Keysym::Delete
305                        {
306                            i_slint_core::api::quit_event_loop()
307                                .expect("Unable to quit event loop multiple times");
308                        } else if (xkb::Keysym::XF86_Switch_VT_1..=xkb::Keysym::XF86_Switch_VT_12)
309                            .contains(&sym)
310                        {
311                            // let target_vt = (sym - xkb::KEY_XF86Switch_VT_1 + 1) as i32;
312                            // TODO: eprintln!("switch vt {target_vt}");
313                        }
314                    }
315
316                    if let Some(text) = map_key_sym(sym) {
317                        let event = match state {
318                            KeyState::Pressed => WindowEvent::KeyPressed { text },
319                            KeyState::Released => WindowEvent::KeyReleased { text },
320                        };
321                        window.try_dispatch_event(event).map_err(Self::Error::other)?;
322                    }
323                }
324                _ => {}
325            }
326            //println!("Got event: {:?}", event);
327        }
328
329        Ok(calloop::PostAction::Continue)
330    }
331
332    fn register(
333        &mut self,
334        poll: &mut calloop::Poll,
335        token_factory: &mut calloop::TokenFactory,
336    ) -> calloop::Result<()> {
337        self.token = Some(token_factory.token());
338        unsafe {
339            poll.register(
340                &self.libinput,
341                calloop::Interest::READ,
342                calloop::Mode::Level,
343                self.token.unwrap(),
344            )
345        }
346    }
347
348    fn reregister(
349        &mut self,
350        poll: &mut calloop::Poll,
351        token_factory: &mut calloop::TokenFactory,
352    ) -> calloop::Result<()> {
353        self.token = Some(token_factory.token());
354        poll.reregister(
355            &self.libinput,
356            calloop::Interest::READ,
357            calloop::Mode::Level,
358            self.token.unwrap(),
359        )
360    }
361
362    fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> {
363        self.token = None;
364        poll.unregister(&self.libinput)
365    }
366}
367
368fn map_key_sym(sym: xkb::Keysym) -> Option<SharedString> {
369    macro_rules! keysym_to_string {
370        ($($char:literal # $name:ident # $($_qt:ident)|* # $($_winit:ident $(($_pos:ident))?)|* # $($xkb:ident)|*;)*) => {
371            match(sym) {
372                $($(xkb::Keysym::$xkb => $char,)*)*
373                _ => std::char::from_u32(xkbcommon::xkb::keysym_to_utf32(sym))?,
374            }
375        };
376    }
377    let char = i_slint_common::for_each_special_keys!(keysym_to_string);
378    Some(char.into())
379}