| 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 | |
| 6 | use std::cell::RefCell; |
| 7 | #[cfg (feature = "libseat" )] |
| 8 | use std::collections::HashMap; |
| 9 | #[cfg (not(feature = "libseat" ))] |
| 10 | use std::fs::{File, OpenOptions}; |
| 11 | use std::os::fd::OwnedFd; |
| 12 | #[cfg (feature = "libseat" )] |
| 13 | use std::os::fd::{AsFd, AsRawFd, FromRawFd, IntoRawFd, RawFd}; |
| 14 | #[cfg (not(feature = "libseat" ))] |
| 15 | use std::os::unix::fs::OpenOptionsExt; |
| 16 | use std::path::Path; |
| 17 | use std::pin::Pin; |
| 18 | use std::rc::Rc; |
| 19 | |
| 20 | use i_slint_core::api::LogicalPosition; |
| 21 | use i_slint_core::platform::{PlatformError, PointerEventButton, WindowEvent}; |
| 22 | use i_slint_core::window::WindowAdapter; |
| 23 | use i_slint_core::{Property, SharedString}; |
| 24 | use input::LibinputInterface; |
| 25 | |
| 26 | use input::event::keyboard::{KeyState, KeyboardEventTrait}; |
| 27 | use input::event::touch::TouchEventPosition; |
| 28 | use xkbcommon::*; |
| 29 | |
| 30 | use crate::fullscreenwindowadapter::FullscreenWindowAdapter; |
| 31 | |
| 32 | #[cfg (feature = "libseat" )] |
| 33 | struct SeatWrap { |
| 34 | seat: Rc<RefCell<libseat::Seat>>, |
| 35 | device_for_fd: HashMap<RawFd, libseat::Device>, |
| 36 | } |
| 37 | |
| 38 | #[cfg (feature = "libseat" )] |
| 39 | impl 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" )] |
| 52 | impl<'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" ))] |
| 80 | struct DirectDeviceAccess {} |
| 81 | |
| 82 | #[cfg (not(feature = "libseat" ))] |
| 83 | impl 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" ))] |
| 92 | impl<'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 | |
| 114 | pub 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 | |
| 123 | impl<'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 | |
| 153 | impl<'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 | |
| 361 | fn 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 | |