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