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