| 1 | // TODO Error type instead of `Result<_, ()>` |
| 2 | #![allow (clippy::result_unit_err)] |
| 3 | |
| 4 | use crate::{ffi, AsRaw, Device, Event, FromRaw}; |
| 5 | use std::{ |
| 6 | ffi::{CStr, CString}, |
| 7 | io::{Error as IoError, Result as IoResult}, |
| 8 | iter::Iterator, |
| 9 | os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}, |
| 10 | path::Path, |
| 11 | rc::Rc, |
| 12 | }; |
| 13 | #[cfg (feature = "udev" )] |
| 14 | use udev::ffi as udev; |
| 15 | |
| 16 | /// libinput does not open file descriptors to devices directly, |
| 17 | /// instead `open_restricted` and `close_restricted` are called for |
| 18 | /// each path that must be opened. |
| 19 | /// |
| 20 | /// Implementations are of course permitted to just use `open` and |
| 21 | /// `close` respectively, but doing so would require root permissions |
| 22 | /// to open devices. This interface provides an api agnostic way to |
| 23 | /// use ConsoleKit or similar endpoints to open devices without |
| 24 | /// direct priviledge escalation. |
| 25 | pub trait LibinputInterface { |
| 26 | /// Open the device at the given path with the flags provided and |
| 27 | /// return the fd. |
| 28 | /// |
| 29 | /// ## Paramater |
| 30 | /// - `path` - The device path to open |
| 31 | /// - `flags` Flags as defined by open(2) |
| 32 | /// |
| 33 | /// ## Returns |
| 34 | /// The file descriptor, or a negative errno on failure. |
| 35 | fn open_restricted(&mut self, path: &Path, flags: i32) -> Result<OwnedFd, i32>; |
| 36 | |
| 37 | /// Close the file descriptor. |
| 38 | /// |
| 39 | /// ## Parameter |
| 40 | /// `fd` - The file descriptor to close |
| 41 | fn close_restricted(&mut self, fd: OwnedFd); |
| 42 | } |
| 43 | |
| 44 | unsafe extern "C" fn open_restricted<I: LibinputInterface + 'static>( |
| 45 | path: *const libc::c_char, |
| 46 | flags: libc::c_int, |
| 47 | user_data: *mut libc::c_void, |
| 48 | ) -> libc::c_int { |
| 49 | use std::borrow::Cow; |
| 50 | |
| 51 | if let Some(interface: &mut I) = (user_data as *mut I).as_mut() { |
| 52 | let path_str: Cow<'_, str> = CStr::from_ptr(path).to_string_lossy(); |
| 53 | let res: Result = match path_str { |
| 54 | Cow::Borrowed(string: &str) => interface.open_restricted(Path::new(string), flags), |
| 55 | Cow::Owned(string: String) => interface.open_restricted(Path::new(&string), flags), |
| 56 | }; |
| 57 | match res { |
| 58 | Ok(fd: OwnedFd) => fd.into_raw_fd(), |
| 59 | Err(errno: i32) => { |
| 60 | if errno > 0 { |
| 61 | -errno |
| 62 | } else { |
| 63 | errno |
| 64 | } |
| 65 | } |
| 66 | } |
| 67 | } else { |
| 68 | -1 |
| 69 | } |
| 70 | } |
| 71 | |
| 72 | unsafe extern "C" fn close_restricted<I: LibinputInterface + 'static>( |
| 73 | fd: libc::c_int, |
| 74 | user_data: *mut libc::c_void, |
| 75 | ) { |
| 76 | if let Some(interface: &mut I) = (user_data as *mut I).as_mut() { |
| 77 | interface.close_restricted(fd:unsafe { OwnedFd::from_raw_fd(fd) }) |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | /// Libinput context |
| 82 | /// |
| 83 | /// Contexts can be used to track input devices and receive events from them. |
| 84 | /// You can use either `new_from_udev` to create a context tracking all devices on a specific seat, |
| 85 | /// or use `new_from_path` to track input devices manually. |
| 86 | /// |
| 87 | /// Either way you then have to use `dispatch()` and `next()` (provided by the `Iterator` trait) to |
| 88 | /// receive events. |
| 89 | pub struct Libinput { |
| 90 | ffi: *mut ffi::libinput, |
| 91 | _interface: Option<Rc<dyn LibinputInterface + 'static>>, |
| 92 | } |
| 93 | |
| 94 | impl ::std::fmt::Debug for Libinput { |
| 95 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { |
| 96 | write!(f, "Libinput @ {:p}" , self.as_raw()) |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | impl AsRaw<ffi::libinput> for Libinput { |
| 101 | fn as_raw(&self) -> *const ffi::libinput { |
| 102 | self.ffi as *const _ |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | impl Clone for Libinput { |
| 107 | fn clone(&self) -> Self { |
| 108 | Libinput { |
| 109 | ffi: unsafe { ffi::libinput_ref(self.as_raw_mut()) }, |
| 110 | _interface: self._interface.clone(), |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | impl Drop for Libinput { |
| 116 | fn drop(&mut self) { |
| 117 | unsafe { |
| 118 | ffi::libinput_unref(self.ffi); |
| 119 | } |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | impl PartialEq for Libinput { |
| 124 | fn eq(&self, other: &Self) -> bool { |
| 125 | self.as_raw() == other.as_raw() |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | impl Eq for Libinput {} |
| 130 | |
| 131 | impl ::std::hash::Hash for Libinput { |
| 132 | fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) { |
| 133 | self.as_raw().hash(state); |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | impl Iterator for Libinput { |
| 138 | type Item = Event; |
| 139 | fn next(&mut self) -> Option<Self::Item> { |
| 140 | loop { |
| 141 | let ptr: *mut libinput_event = unsafe { ffi::libinput_get_event(self.as_raw_mut()) }; |
| 142 | if ptr.is_null() { |
| 143 | return None; |
| 144 | } else { |
| 145 | match unsafe { Event::try_from_raw(ffi:ptr, self) } { |
| 146 | Some(x: Event) => return Some(x), |
| 147 | None => { |
| 148 | #[cfg (feature = "log" )] |
| 149 | log::warn!("Skipping unknown event: {}" , unsafe { |
| 150 | ffi::libinput_event_get_type(ptr) |
| 151 | }); |
| 152 | continue; |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | |
| 160 | impl Libinput { |
| 161 | /// Create a new libinput context using a udev context. |
| 162 | /// |
| 163 | /// This context is inactive until `udev_assign_seat` is called. |
| 164 | /// |
| 165 | /// ## Arguments |
| 166 | /// |
| 167 | /// - interface - A `LibinputInterface` providing functions to open and close devices. |
| 168 | /// - userdata - Optionally some userdata attached to the newly created context (see [`Userdata`](./trait.Userdata.html)) |
| 169 | /// - udev_context - Raw pointer to a valid udev context. |
| 170 | /// |
| 171 | /// # Safety |
| 172 | /// |
| 173 | /// This function is unsafe, because there is no way to verify that `udev_context` is indeed a valid udev context or even points to valid memory. |
| 174 | #[cfg (feature = "udev" )] |
| 175 | pub fn new_with_udev<I: LibinputInterface + 'static>(interface: I) -> Libinput { |
| 176 | let boxed_userdata = Rc::new(interface); |
| 177 | let boxed_interface = Box::new(ffi::libinput_interface { |
| 178 | open_restricted: Some(open_restricted::<I>), |
| 179 | close_restricted: Some(close_restricted::<I>), |
| 180 | }); |
| 181 | |
| 182 | unsafe { |
| 183 | let udev = udev::udev_new(); |
| 184 | let libinput = ffi::libinput_udev_create_context( |
| 185 | Box::into_raw(boxed_interface), |
| 186 | Rc::as_ptr(&boxed_userdata) as *mut _, |
| 187 | udev as *mut input_sys::udev, |
| 188 | ); |
| 189 | udev::udev_unref(udev); |
| 190 | Libinput { |
| 191 | ffi: libinput, |
| 192 | _interface: Some(boxed_userdata as Rc<dyn LibinputInterface>), |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | /// Create a new libinput context that requires the caller to manually add or remove devices. |
| 198 | /// |
| 199 | /// The returned context is active, but will not yield any events |
| 200 | /// until at least one device is added. |
| 201 | /// |
| 202 | /// Devices can be added and removed by calling `path_add_device` and `path_remove_device` respectively. |
| 203 | /// |
| 204 | /// ## Arguments |
| 205 | /// |
| 206 | /// - interface - A `LibinputInterface` providing functions to open and close devices. |
| 207 | /// - userdata - Optionally some userdata attached to the newly created context (see [`Userdata`](./trait.Userdata.html)) |
| 208 | /// |
| 209 | pub fn new_from_path<I: 'static + LibinputInterface>(interface: I) -> Libinput { |
| 210 | let boxed_userdata = Rc::new(interface); |
| 211 | let boxed_interface = Box::new(ffi::libinput_interface { |
| 212 | open_restricted: Some(open_restricted::<I>), |
| 213 | close_restricted: Some(close_restricted::<I>), |
| 214 | }); |
| 215 | |
| 216 | Libinput { |
| 217 | ffi: unsafe { |
| 218 | ffi::libinput_path_create_context( |
| 219 | Box::into_raw(boxed_interface), |
| 220 | Rc::as_ptr(&boxed_userdata) as *mut _, |
| 221 | ) |
| 222 | }, |
| 223 | _interface: Some(boxed_userdata as Rc<dyn LibinputInterface>), |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | /// Add a device to a libinput context initialized with |
| 228 | /// `new_from_context`. |
| 229 | /// |
| 230 | /// If successful, the device will be added to the internal list |
| 231 | /// and re-opened on `resume`. The device can be removed with |
| 232 | /// `path_remove_device()`. |
| 233 | /// |
| 234 | /// If the device was successfully initialized, it is returned. |
| 235 | /// |
| 236 | /// ## Warning |
| 237 | /// |
| 238 | /// It is an application bug to call this function on a context |
| 239 | /// initialized with `new_from_udev`. |
| 240 | pub fn path_add_device(&mut self, path: &str) -> Option<Device> { |
| 241 | let path = CString::new(path).expect("Device Path contained a null-byte" ); |
| 242 | unsafe { |
| 243 | let ptr = ffi::libinput_path_add_device(self.as_raw_mut(), path.as_ptr()); |
| 244 | if ptr.is_null() { |
| 245 | None |
| 246 | } else { |
| 247 | Some(Device::from_raw(ptr, self)) |
| 248 | } |
| 249 | } |
| 250 | } |
| 251 | |
| 252 | /// Remove a device from a libinput context initialized with |
| 253 | /// `new_from_path` and added to such a context with |
| 254 | /// `path_add_device`. |
| 255 | /// |
| 256 | /// Events already processed from this input device are kept in |
| 257 | /// the queue, the `DeviceRemovedEvent` event marks the end of |
| 258 | /// events for this device. |
| 259 | /// |
| 260 | /// ## Warning |
| 261 | /// |
| 262 | /// It is an application bug to call this function on a context |
| 263 | /// initialized with `new_from_udev`. |
| 264 | pub fn path_remove_device(&mut self, device: Device) { |
| 265 | unsafe { ffi::libinput_path_remove_device(device.as_raw_mut()) } |
| 266 | } |
| 267 | |
| 268 | /// Assign a seat to this libinput context. |
| 269 | /// |
| 270 | /// New devices or the removal of existing devices will appear as |
| 271 | /// events during `dispatch`. |
| 272 | /// |
| 273 | /// `udev_assign_seat` succeeds even if no input devices are |
| 274 | /// currently available on this seat, or if devices are available |
| 275 | /// but fail to open in `LibinputInterface::open_restricted`. |
| 276 | /// |
| 277 | /// Devices that do not have the minimum capabilities to be |
| 278 | /// recognized as pointer, keyboard or touch device are ignored. /// Such devices and those that failed to open ignored until the |
| 279 | /// next call to `resume`. |
| 280 | /// |
| 281 | /// ## Warning |
| 282 | /// |
| 283 | /// This function may only be called once per context. |
| 284 | #[cfg (feature = "udev" )] |
| 285 | pub fn udev_assign_seat(&mut self, seat_id: &str) -> Result<(), ()> { |
| 286 | let id = CString::new(seat_id).expect("Seat Id contained a null-byte" ); |
| 287 | unsafe { |
| 288 | match ffi::libinput_udev_assign_seat(self.as_raw_mut(), id.as_ptr()) { |
| 289 | 0 => Ok(()), |
| 290 | -1 => Err(()), |
| 291 | _ => unreachable!(), |
| 292 | } |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | ffi_func!( |
| 297 | /// Suspend monitoring for new devices and close existing |
| 298 | /// devices. |
| 299 | /// |
| 300 | /// This closes all open devices and terminates libinput but |
| 301 | /// does keep the context valid to be resumed with `resume`. |
| 302 | pub fn suspend, ffi::libinput_suspend, ()); |
| 303 | |
| 304 | /// Resume a suspended libinput context. |
| 305 | /// |
| 306 | /// This re-enables device monitoring and adds existing devices. |
| 307 | pub fn resume(&mut self) -> Result<(), ()> { |
| 308 | unsafe { |
| 309 | match ffi::libinput_resume(self.as_raw_mut()) { |
| 310 | 0 => Ok(()), |
| 311 | -1 => Err(()), |
| 312 | _ => unreachable!(), |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | /// Main event dispatchment function. |
| 318 | /// |
| 319 | /// Reads events of the file descriptors and processes them |
| 320 | /// internally. Use `next` or any other function provided by the |
| 321 | /// `Iterator` trait to retrieve the events until `None` is |
| 322 | /// returned. |
| 323 | /// |
| 324 | /// Dispatching does not necessarily queue libinput events. This |
| 325 | /// function should be called immediately once data is available |
| 326 | /// on the file descriptor returned by `fd`. libinput has a number |
| 327 | /// of timing-sensitive features (e.g. tap-to-click), any delay in |
| 328 | /// calling `dispatch` may prevent these features from working |
| 329 | /// correctly. |
| 330 | pub fn dispatch(&mut self) -> IoResult<()> { |
| 331 | unsafe { |
| 332 | match ffi::libinput_dispatch(self.as_raw_mut()) { |
| 333 | 0 => Ok(()), |
| 334 | x if x < 0 => Err(IoError::from_raw_os_error(-x)), |
| 335 | _ => unreachable!(), |
| 336 | } |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | /// libinput keeps a single file descriptor for all events. |
| 341 | /// |
| 342 | /// Call into `dispatch` if any events become available on this fd. |
| 343 | /// |
| 344 | /// The most simple variant to check for available bytes is to use |
| 345 | /// `nix::poll`: |
| 346 | /// |
| 347 | /// ``` |
| 348 | /// # extern crate libc; |
| 349 | /// # extern crate rustix; |
| 350 | /// # |
| 351 | /// # use std::fs::{File, OpenOptions}; |
| 352 | /// # use std::os::unix::{fs::OpenOptionsExt, io::OwnedFd}; |
| 353 | /// # use std::path::Path; |
| 354 | /// # use libc::{O_RDONLY, O_RDWR, O_WRONLY}; |
| 355 | /// # |
| 356 | /// use input::{Libinput, LibinputInterface}; |
| 357 | /// use rustix::event::{poll, PollFlags, PollFd}; |
| 358 | /// |
| 359 | /// # struct Interface; |
| 360 | /// # |
| 361 | /// # impl LibinputInterface for Interface { |
| 362 | /// # fn open_restricted(&mut self, path: &Path, flags: i32) -> Result<OwnedFd, i32> { |
| 363 | /// # OpenOptions::new() |
| 364 | /// # .custom_flags(flags) |
| 365 | /// # .read((flags & O_RDONLY != 0) | (flags & O_RDWR != 0)) |
| 366 | /// # .write((flags & O_WRONLY != 0) | (flags & O_RDWR != 0)) |
| 367 | /// # .open(path) |
| 368 | /// # .map(|file| file.into()) |
| 369 | /// # .map_err(|err| err.raw_os_error().unwrap()) |
| 370 | /// # } |
| 371 | /// # fn close_restricted(&mut self, fd: OwnedFd) { |
| 372 | /// # unsafe { |
| 373 | /// # File::from(fd); |
| 374 | /// # } |
| 375 | /// # } |
| 376 | /// # } |
| 377 | /// # |
| 378 | /// # // Preventing infinite execution (in particular on CI) |
| 379 | /// # std::thread::spawn(|| { |
| 380 | /// # std::thread::sleep(std::time::Duration::from_secs(5)); |
| 381 | /// # std::process::exit(0); |
| 382 | /// # }); |
| 383 | /// # |
| 384 | /// let mut input = Libinput::new_with_udev(Interface); |
| 385 | /// input.udev_assign_seat("seat0" ).unwrap(); |
| 386 | /// |
| 387 | /// while poll(&mut [PollFd::new(&input, PollFlags::IN)], -1).is_ok() { |
| 388 | /// input.dispatch().unwrap(); |
| 389 | /// for event in &mut input { |
| 390 | /// // do some processing... |
| 391 | /// } |
| 392 | /// } |
| 393 | /// ``` |
| 394 | /// |
| 395 | /// For more complex operations you may wish to use other approches |
| 396 | /// as event loops e.g. in the `wayland-server` or the `tokio` |
| 397 | /// crates to wait for data to become available on this file |
| 398 | /// descriptor. |
| 399 | /// |
| 400 | /// # Safety |
| 401 | /// |
| 402 | /// See [`AsRawFd`] |
| 403 | #[deprecated (since = "0.4.1" , note = "Use the provided AsRawFd implementation" )] |
| 404 | pub unsafe fn fd(&self) -> RawFd { |
| 405 | ffi::libinput_get_fd(self.as_raw_mut()) |
| 406 | } |
| 407 | |
| 408 | /// Create a new instance of this type from a raw pointer. |
| 409 | /// |
| 410 | /// ## Warning |
| 411 | /// |
| 412 | /// If you make use of [`Userdata`](./trait.Userdata.html) make sure you use the correct types |
| 413 | /// to allow receiving the set userdata. When dealing with raw pointers initialized by other |
| 414 | /// libraries this must be done extra carefully to select a correct representation. |
| 415 | /// |
| 416 | /// If unsure using `()` is always a safe option.. |
| 417 | /// |
| 418 | /// # Safety |
| 419 | /// |
| 420 | /// If the pointer is pointing to a different struct, invalid memory or `NULL` the returned |
| 421 | /// struct may panic on use or cause other undefined behavior. |
| 422 | pub unsafe fn from_raw(ffi: *mut ffi::libinput) -> Self { |
| 423 | Libinput { |
| 424 | ffi: ffi::libinput_ref(ffi), |
| 425 | _interface: None, |
| 426 | } |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | impl AsRawFd for Libinput { |
| 431 | fn as_raw_fd(&self) -> RawFd { |
| 432 | unsafe { ffi::libinput_get_fd(self.as_raw_mut()) } |
| 433 | } |
| 434 | } |
| 435 | |
| 436 | impl AsFd for Libinput { |
| 437 | fn as_fd(&self) -> BorrowedFd<'_> { |
| 438 | unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) } |
| 439 | } |
| 440 | } |
| 441 | |