input/
context.rs

1// TODO Error type instead of `Result<_, ()>`
2#![allow(clippy::result_unit_err)]
3
4use crate::{ffi, AsRaw, Device, Event, FromRaw};
5use 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")]
14use 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.
25pub 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
44unsafe 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) = (user_data as *mut I).as_mut() {
52        let path_str = CStr::from_ptr(path).to_string_lossy();
53        let res = match path_str {
54            Cow::Borrowed(string) => interface.open_restricted(Path::new(string), flags),
55            Cow::Owned(string) => interface.open_restricted(Path::new(&string), flags),
56        };
57        match res {
58            Ok(fd) => fd.into_raw_fd(),
59            Err(errno) => {
60                if errno > 0 {
61                    -errno
62                } else {
63                    errno
64                }
65            }
66        }
67    } else {
68        -1
69    }
70}
71
72unsafe 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) = (user_data as *mut I).as_mut() {
77        interface.close_restricted(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.
89pub struct Libinput {
90    ffi: *mut ffi::libinput,
91    _interface: Option<Rc<dyn LibinputInterface + 'static>>,
92}
93
94impl ::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
100impl AsRaw<ffi::libinput> for Libinput {
101    fn as_raw(&self) -> *const ffi::libinput {
102        self.ffi as *const _
103    }
104}
105
106impl 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
115impl Drop for Libinput {
116    fn drop(&mut self) {
117        unsafe {
118            ffi::libinput_unref(self.ffi);
119        }
120    }
121}
122
123impl PartialEq for Libinput {
124    fn eq(&self, other: &Self) -> bool {
125        self.as_raw() == other.as_raw()
126    }
127}
128
129impl Eq for Libinput {}
130
131impl ::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
137impl Iterator for Libinput {
138    type Item = Event;
139    fn next(&mut self) -> Option<Self::Item> {
140        loop {
141            let ptr = 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(ptr, self) } {
146                    Some(x) => 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
160impl 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
430impl AsRawFd for Libinput {
431    fn as_raw_fd(&self) -> RawFd {
432        unsafe { ffi::libinput_get_fd(self.as_raw_mut()) }
433    }
434}
435
436impl AsFd for Libinput {
437    fn as_fd(&self) -> BorrowedFd<'_> {
438        unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
439    }
440}