mod r#async;
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-io"))))]
#[cfg(any(feature = "tokio", feature = "async-io"))]
pub use r#async::AsyncEvents;
use std::{
error::Error,
ffi::{CStr, CString, OsString, c_char, c_int},
fmt,
fs::File,
io, mem,
os::{
fd::{AsFd, AsRawFd, BorrowedFd, IntoRawFd, OwnedFd},
unix::{ffi::OsStringExt, prelude::RawFd},
},
ptr, slice,
time::Instant,
};
use uoctl::Ioctl;
use crate::{
AbsInfo, InputId, InputProp, KeyRepeat, Slot,
batch::BatchWriter,
drop::on_drop,
event::{
Abs, AbsEvent, EventType, InputEvent, Key, Led, Misc, Rel, Repeat, RepeatEvent, Sound,
Switch, Syn, SynEvent, UinputCode, UinputEvent,
},
ff::{self, Effect, EffectId},
raw::{
input::ff_effect,
uinput::{
UI_ABS_SETUP, UI_BEGIN_FF_ERASE, UI_BEGIN_FF_UPLOAD, UI_DEV_CREATE, UI_DEV_SETUP,
UI_END_FF_ERASE, UI_END_FF_UPLOAD, UI_GET_SYSNAME, UI_GET_VERSION, UI_SET_ABSBIT,
UI_SET_EVBIT, UI_SET_FFBIT, UI_SET_KEYBIT, UI_SET_LEDBIT, UI_SET_MSCBIT, UI_SET_PHYS,
UI_SET_PROPBIT, UI_SET_RELBIT, UI_SET_SNDBIT, UI_SET_SWBIT, UINPUT_MAX_NAME_SIZE,
uinput_abs_setup, uinput_ff_erase, uinput_ff_upload, uinput_setup,
},
},
read_raw,
util::{block_until_readable, errorkind2libc, is_readable, set_nonblocking},
};
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct AbsSetup(uinput_abs_setup);
impl fmt::Debug for AbsSetup {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AbsSetup")
.field("abs", &self.abs())
.field("abs_info", self.abs_info())
.finish()
}
}
impl AbsSetup {
#[inline]
pub const fn new(abs: Abs, abs_info: AbsInfo) -> Self {
AbsSetup(uinput_abs_setup {
code: abs.raw(),
absinfo: abs_info.0,
})
}
#[inline]
pub const fn abs(&self) -> Abs {
Abs::from_raw(self.0.code)
}
#[inline]
pub const fn abs_info(&self) -> &AbsInfo {
unsafe { mem::transmute(&self.0.absinfo) }
}
}
pub struct Builder {
device: UinputDevice, setup: uinput_setup,
}
impl fmt::Debug for Builder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Builder")
.field("file", &self.device.file)
.field("input_id", &InputId(self.setup.id))
.field("ff_effects_max", &self.setup.ff_effects_max)
.finish()
}
}
impl Builder {
fn new() -> io::Result<Self> {
let file = File::options()
.read(true)
.write(true)
.open("/dev/uinput")
.map_err(|e| io::Error::new(e.kind(), format!("failed to open '/dev/uinput': {e}")))?;
let device = UinputDevice { file };
unsafe {
let mut version = 0;
device.ioctl("UI_GET_VERSION", UI_GET_VERSION, &mut version)?;
log::debug!("opened /dev/uinput; version={version:#x}");
}
Ok(Self {
device,
setup: unsafe { mem::zeroed() },
})
}
#[inline]
pub fn with_input_id(mut self, id: InputId) -> io::Result<Self> {
self.setup.id = id.0;
Ok(self)
}
#[doc(alias = "UI_SET_PHYS")]
pub fn with_phys(self, path: &str) -> io::Result<Self> {
self.with_phys_cstr(&CString::new(path).unwrap())
}
pub fn with_phys_cstr(self, path: &CStr) -> io::Result<Self> {
unsafe {
self.device
.ioctl("UI_SET_PHYS", UI_SET_PHYS, path.as_ptr().cast())?;
}
Ok(self)
}
#[doc(alias = "UI_SET_PROPBIT")]
pub fn with_props(self, props: impl IntoIterator<Item = InputProp>) -> io::Result<Self> {
for prop in props {
unsafe {
self.device
.ioctl("UI_SET_PROPBIT", UI_SET_PROPBIT, prop.0.into())?;
}
}
Ok(self)
}
#[doc(alias = "UI_SET_KEYBIT")]
pub fn with_keys(self, keys: impl IntoIterator<Item = Key>) -> io::Result<Self> {
self.enable_codes(
"UI_SET_KEYBIT",
UI_SET_KEYBIT,
EventType::KEY,
keys.into_iter().map(|v| v.raw().into()),
)?;
Ok(self)
}
#[doc(alias = "UI_SET_RELBIT")]
pub fn with_rel_axes(self, rel: impl IntoIterator<Item = Rel>) -> io::Result<Self> {
self.enable_codes(
"UI_SET_RELBIT",
UI_SET_RELBIT,
EventType::REL,
rel.into_iter().map(|v| v.raw().into()),
)?;
Ok(self)
}
#[doc(alias = "UI_SET_MSCBIT")]
pub fn with_misc(self, misc: impl IntoIterator<Item = Misc>) -> io::Result<Self> {
self.enable_codes(
"UI_SET_MSCBIT",
UI_SET_MSCBIT,
EventType::MSC,
misc.into_iter().map(|v| v.raw().into()),
)?;
Ok(self)
}
#[doc(alias = "UI_SET_LEDBIT")]
pub fn with_leds(self, leds: impl IntoIterator<Item = Led>) -> io::Result<Self> {
self.enable_codes(
"UI_SET_LEDBIT",
UI_SET_LEDBIT,
EventType::LED,
leds.into_iter().map(|v| v.raw().into()),
)?;
Ok(self)
}
#[doc(alias = "UI_SET_SNDBIT")]
pub fn with_sounds(self, sounds: impl IntoIterator<Item = Sound>) -> io::Result<Self> {
self.enable_codes(
"UI_SET_SNDBIT",
UI_SET_SNDBIT,
EventType::SND,
sounds.into_iter().map(|v| v.raw().into()),
)?;
Ok(self)
}
#[doc(alias = "UI_SET_SWBIT")]
pub fn with_switches(self, switches: impl IntoIterator<Item = Switch>) -> io::Result<Self> {
self.enable_codes(
"UI_SET_SWBIT",
UI_SET_SWBIT,
EventType::SW,
switches.into_iter().map(|v| v.raw().into()),
)?;
Ok(self)
}
#[doc(alias = "UI_SET_ABSBIT", alias = "UI_ABS_SETUP")]
pub fn with_abs_axes(self, axes: impl IntoIterator<Item = AbsSetup>) -> io::Result<Self> {
self.enable_event(EventType::ABS)?;
for setup in axes {
unsafe {
self.device
.ioctl("UI_SET_ABSBIT", UI_SET_ABSBIT, setup.0.code as c_int)?;
self.device.ioctl("UI_ABS_SETUP", UI_ABS_SETUP, &setup.0)?;
}
}
Ok(self)
}
#[inline]
pub fn with_ff_effects_max(mut self, ff_max: u32) -> io::Result<Self> {
self.setup.ff_effects_max = ff_max;
Ok(self)
}
#[doc(alias = "UI_SET_FFBIT")]
pub fn with_ff_features(self, feat: impl IntoIterator<Item = ff::Feature>) -> io::Result<Self> {
self.enable_codes(
"UI_SET_FFBIT",
UI_SET_FFBIT,
EventType::FF,
feat.into_iter().map(|v| v.0.into()),
)?;
Ok(self)
}
pub fn with_key_repeat(self) -> io::Result<Self> {
self.enable_event(EventType::REP)?;
Ok(self)
}
fn enable_codes(
&self,
ioctl_name: &'static str,
ioctl: Ioctl<c_int>,
event: EventType,
codes: impl IntoIterator<Item = usize>,
) -> io::Result<()> {
self.enable_event(event)?;
for code in codes {
unsafe {
self.device.ioctl(ioctl_name, ioctl, code as c_int)?;
}
}
Ok(())
}
fn enable_event(&self, event: EventType) -> io::Result<()> {
unsafe {
self.device
.ioctl("UI_SET_EVBIT", UI_SET_EVBIT, event.0 as c_int)?;
}
Ok(())
}
#[doc(alias = "UI_DEV_SETUP")]
pub fn build(mut self, name: &str) -> io::Result<UinputDevice> {
if name.len() >= UINPUT_MAX_NAME_SIZE {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"uinput device name is too long",
));
}
unsafe {
ptr::copy_nonoverlapping(
name.as_ptr(),
self.setup.name.as_mut_ptr().cast(),
name.len(),
);
}
unsafe {
self.device
.ioctl("UI_DEV_SETUP", UI_DEV_SETUP, &self.setup)?;
UI_DEV_CREATE.ioctl(&self.device)?;
}
Ok(self.device)
}
}
#[derive(Debug)]
pub struct UinputDevice {
file: File,
}
impl AsFd for UinputDevice {
#[inline]
fn as_fd(&self) -> BorrowedFd<'_> {
self.file.as_fd()
}
}
impl AsRawFd for UinputDevice {
#[inline]
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl IntoRawFd for UinputDevice {
#[inline]
fn into_raw_fd(self) -> RawFd {
self.file.into_raw_fd()
}
}
impl From<UinputDevice> for OwnedFd {
#[inline]
fn from(value: UinputDevice) -> Self {
value.file.into()
}
}
impl UinputDevice {
pub fn builder() -> io::Result<Builder> {
Builder::new()
}
#[inline]
pub unsafe fn from_owned_fd(owned_fd: OwnedFd) -> Self {
Self {
file: owned_fd.into(),
}
}
pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<bool> {
set_nonblocking(self.as_raw_fd(), nonblocking)
}
#[doc(alias = "dup")]
pub fn try_clone(&self) -> io::Result<Self> {
Ok(Self {
file: self.file.try_clone()?,
})
}
unsafe fn ioctl<T>(&self, name: &'static str, ioctl: Ioctl<T>, arg: T) -> io::Result<c_int> {
match unsafe { ioctl.ioctl(self, arg) } {
Ok(ok) => Ok(ok),
Err(e) => {
#[derive(Debug)]
struct WrappedError {
cause: io::Error,
msg: String,
}
impl fmt::Display for WrappedError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.msg)
}
}
impl Error for WrappedError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.cause)
}
}
log::trace!("ioctl {name} failed with error {e} ({:?})", e.kind());
let msg = format!("ioctl {name} failed ({:?})", e.kind());
Err(io::Error::new(e.kind(), WrappedError { cause: e, msg }))
}
}
}
unsafe fn fetch_string(
&self,
ioctl_name: &'static str,
ioctl: fn(usize) -> Ioctl<*mut c_char>,
) -> io::Result<OsString> {
const INITIAL_LEN: usize = 64;
let mut buf = vec![0_u8; INITIAL_LEN];
let len = loop {
let len = unsafe {
self.ioctl(
ioctl_name,
ioctl(buf.len()),
buf.as_mut_ptr() as *mut c_char,
)?
};
if len as usize == buf.len() {
buf.resize(buf.len() * 2, 0);
} else {
break len;
}
};
buf.truncate(len.saturating_sub(1) as usize);
Ok(OsString::from_vec(buf))
}
#[doc(alias = "UI_GET_SYSNAME")]
pub fn sysname(&self) -> io::Result<OsString> {
unsafe { self.fetch_string("UI_GET_SYSNAME", UI_GET_SYSNAME) }
}
#[inline]
pub fn events(&self) -> Events<'_> {
Events { file: &self.file }
}
#[cfg_attr(docsrs, doc(cfg(any(feature = "tokio", feature = "async-io"))))]
#[cfg(any(feature = "tokio", feature = "async-io"))]
pub fn async_events(&self) -> io::Result<AsyncEvents<'_>> {
AsyncEvents::new(self)
}
pub fn read_events(&self, buf: &mut [InputEvent]) -> io::Result<usize> {
read_raw(&self.file, buf)
}
pub fn is_readable(&self) -> io::Result<bool> {
is_readable(self.as_raw_fd())
}
pub fn block_until_readable(&self) -> io::Result<()> {
block_until_readable(self.as_raw_fd())
}
#[doc(alias = "UI_BEGIN_FF_UPLOAD", alias = "UI_END_FF_UPLOAD")]
pub fn ff_upload<R>(
&self,
request: &UinputEvent,
handler: impl FnOnce(&ForceFeedbackUpload) -> io::Result<R>,
) -> io::Result<R> {
assert!(request.code() == UinputCode::FF_UPLOAD);
let mut upload = unsafe { mem::zeroed::<ForceFeedbackUpload>() };
upload.0.request_id = request.raw_value() as u32;
let now = Instant::now();
let _d = on_drop(|| log::trace!("`ff_upload` took {:?}", now.elapsed()));
unsafe {
self.ioctl("UI_BEGIN_FF_UPLOAD", UI_BEGIN_FF_UPLOAD, &mut upload.0)?;
}
let res = handler(&upload);
match &res {
Ok(_) => {}
Err(e) => {
let os_err = e.raw_os_error();
let errno = e
.raw_os_error()
.unwrap_or_else(|| errorkind2libc(e.kind()).unwrap_or(libc::EIO));
log::debug!(
"ff_upload handler errored: {e} ({:?}, OS error: {os_err:?}) -> code {errno}",
e.kind()
);
upload.0.retval = -errno;
}
}
unsafe {
self.ioctl("UI_END_FF_UPLOAD", UI_END_FF_UPLOAD, &upload.0)?;
}
res
}
#[doc(alias = "UI_BEGIN_FF_ERASE", alias = "UI_END_FF_ERASE")]
pub fn ff_erase(
&self,
request: &UinputEvent,
handler: impl FnOnce(&ForceFeedbackErase) -> io::Result<()>,
) -> io::Result<()> {
assert!(request.code() == UinputCode::FF_ERASE);
let mut erase = unsafe { mem::zeroed::<ForceFeedbackErase>() };
erase.0.request_id = request.raw_value() as u32;
unsafe {
self.ioctl("UI_BEGIN_FF_ERASE", UI_BEGIN_FF_ERASE, &mut erase.0)?;
}
match handler(&erase) {
Ok(()) => {}
Err(e) => {
let os_err = e.raw_os_error();
let errno = e
.raw_os_error()
.unwrap_or_else(|| errorkind2libc(e.kind()).unwrap_or(libc::EIO));
log::debug!(
"ff_erase handler errored: {e} ({:?}, OS error: {os_err:?}) -> code {errno}",
e.kind()
);
erase.0.retval = -errno;
}
}
unsafe {
self.ioctl("UI_END_FF_ERASE", UI_END_FF_ERASE, &erase.0)?;
}
Ok(())
}
pub fn write(&self, events: &[InputEvent]) -> io::Result<()> {
self.writer().write(events)?.finish()?;
Ok(())
}
pub fn writer(&self) -> EventWriter<'_> {
EventWriter {
file: &self.file,
batch: BatchWriter::new(),
needs_syn_report: true,
}
}
}
#[derive(Debug)]
#[must_use = "must call `EventWriter::finish` to flush the event batch"]
pub struct EventWriter<'a> {
file: &'a File,
batch: BatchWriter,
needs_syn_report: bool,
}
impl<'a> EventWriter<'a> {
pub fn write(mut self, events: &[InputEvent]) -> io::Result<Self> {
self.batch.write(events, self.file)?;
Ok(self)
}
pub fn slot(mut self, slot: impl TryInto<Slot>) -> io::Result<SlotWriter<'a>> {
let slot: Slot = slot
.try_into()
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid slot"))?;
self = self.write(&[AbsEvent::new(Abs::MT_SLOT, slot.raw() as i32).into()])?;
Ok(SlotWriter(self))
}
pub fn set_key_repeat(self, rep: KeyRepeat) -> io::Result<Self> {
self.write(&[
RepeatEvent::new(Repeat::PERIOD, rep.period()).into(),
RepeatEvent::new(Repeat::DELAY, rep.delay()).into(),
])
}
pub fn finish(mut self) -> io::Result<()> {
self.finish_impl()?;
Ok(())
}
fn finish_impl(&mut self) -> io::Result<()> {
if self.needs_syn_report {
self.needs_syn_report = false;
self.batch
.write(&[SynEvent::new(Syn::REPORT).into()], self.file)?;
}
self.batch.flush(self.file)
}
}
impl Drop for EventWriter<'_> {
fn drop(&mut self) {
if let Err(e) = self.finish_impl() {
log::error!("uncaught error in `EventWriter` destructor: {e}");
}
}
}
#[derive(Debug)]
#[must_use = "must call `SlotWriter::finish_slot` to finish modifying this slot"]
pub struct SlotWriter<'a>(EventWriter<'a>);
impl<'a> SlotWriter<'a> {
pub fn set_position(mut self, x: i32, y: i32) -> io::Result<Self> {
self.0 = self.0.write(&[
AbsEvent::new(Abs::MT_POSITION_X, x).into(),
AbsEvent::new(Abs::MT_POSITION_Y, y).into(),
])?;
Ok(self)
}
pub fn set_tracking_id(mut self, id: i32) -> io::Result<Self> {
self.0 = self
.0
.write(&[AbsEvent::new(Abs::MT_TRACKING_ID, id).into()])?;
Ok(self)
}
pub fn write(mut self, events: &[InputEvent]) -> io::Result<Self> {
self.0 = self.0.write(events)?;
Ok(self)
}
#[inline]
pub fn finish_slot(self) -> io::Result<EventWriter<'a>> {
Ok(self.0)
}
}
#[derive(Debug)]
pub struct Events<'a> {
file: &'a File,
}
impl Iterator for Events<'_> {
type Item = io::Result<InputEvent>;
fn next(&mut self) -> Option<Self::Item> {
let mut dest = InputEvent::zeroed();
match read_raw(&self.file, slice::from_mut(&mut dest)) {
Err(e) if e.kind() == io::ErrorKind::WouldBlock => None,
Err(e) => Some(Err(e)),
Ok(0) => None,
Ok(1) => Some(Ok(dest)),
Ok(n) => unreachable!("read {n} events, but can only hold 1"),
}
}
}
#[repr(transparent)]
pub struct ForceFeedbackUpload(uinput_ff_upload);
impl ForceFeedbackUpload {
#[inline]
pub fn effect(&self) -> &Effect<'_> {
unsafe { mem::transmute::<&ff_effect, &Effect>(&self.0.effect) }
}
#[inline]
pub fn effect_id(&self) -> EffectId {
self.effect().id()
}
#[inline]
pub fn old(&self) -> &Effect<'_> {
unsafe { mem::transmute::<&ff_effect, &Effect>(&self.0.old) }
}
}
impl fmt::Debug for ForceFeedbackUpload {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ForceFeedbackUpload")
.field("request_id", &self.0.request_id)
.field("effect", self.effect())
.field("old", self.old())
.finish()
}
}
#[repr(transparent)]
pub struct ForceFeedbackErase(uinput_ff_erase);
impl ForceFeedbackErase {
#[inline]
pub fn effect_id(&self) -> EffectId {
EffectId(self.0.effect_id as i16)
}
}
impl fmt::Debug for ForceFeedbackErase {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ForceFeedbackErase")
.field("request_id", &self.0.request_id)
.field("effect_id", &self.effect_id())
.finish()
}
}