i_slint_backend_linuxkms/calloop_backend/
input.rs1use 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 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 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 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 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 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 }
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 }
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}