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}