1 | // TODO Error type instead of `Result<_, ()>` |
2 | #![allow (clippy::result_unit_err)] |
3 | |
4 | use crate::{ffi, AsRaw, Device, Event, FromRaw}; |
5 | use io_lifetimes::{AsFd, BorrowedFd, OwnedFd}; |
6 | use 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" )] |
15 | use 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. |
26 | pub 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 | |
45 | unsafe 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 | |
73 | unsafe 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. |
90 | pub struct Libinput { |
91 | ffi: *mut ffi::libinput, |
92 | _interface: Option<Rc<dyn LibinputInterface + 'static>>, |
93 | } |
94 | |
95 | impl ::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 | |
101 | impl AsRaw<ffi::libinput> for Libinput { |
102 | fn as_raw(&self) -> *const ffi::libinput { |
103 | self.ffi as *const _ |
104 | } |
105 | } |
106 | |
107 | impl 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 | |
116 | impl Drop for Libinput { |
117 | fn drop(&mut self) { |
118 | unsafe { |
119 | ffi::libinput_unref(self.ffi); |
120 | } |
121 | } |
122 | } |
123 | |
124 | impl PartialEq for Libinput { |
125 | fn eq(&self, other: &Self) -> bool { |
126 | self.as_raw() == other.as_raw() |
127 | } |
128 | } |
129 | |
130 | impl Eq for Libinput {} |
131 | |
132 | impl ::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 | |
138 | impl 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 | |
161 | impl 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 | |
432 | impl AsRawFd for Libinput { |
433 | fn as_raw_fd(&self) -> RawFd { |
434 | unsafe { ffi::libinput_get_fd(self.as_raw_mut()) } |
435 | } |
436 | } |
437 | |
438 | impl AsFd for Libinput { |
439 | fn as_fd(&self) -> BorrowedFd<'_> { |
440 | unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) } |
441 | } |
442 | } |
443 | |