Add support for virtual uinput devices (#37)

* Prototype support for virtual uinput devices

* Tidy uinput a bit

* Have all the evtest examples use the same args/prompt code

* Switch to libc uinput* structs

* Use InputId in uinput, use libc _CNT constants

* Don't use align_to_mut

* Only check /dev/input/eventN files

* Spelling fixup

* Fix compilation error

* Ah, there we go. Much better.

Co-authored-by: Jeff Hiner <jeff-hiner@users.noreply.github.com>
Co-authored-by: Noah <33094578+coolreader18@users.noreply.github.com>
This commit is contained in:
Jeff Hiner 2021-03-19 09:19:51 -06:00 committed by GitHub
parent 79b6c2b403
commit 6c1add8b73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 319 additions and 88 deletions

View file

@ -12,7 +12,7 @@ edition = "2018"
tokio = ["tokio_1", "futures-core"]
[dependencies]
libc = "0.2.22"
libc = "0.2.89"
bitvec = "0.21"
nix = "0.19.0"

28
examples/_pick_device.rs Normal file
View file

@ -0,0 +1,28 @@
//! An arg parser/prompt shared between the `evtest*` examples. Also demonstrates opening/finding
//! connected devices.
use std::io::prelude::*;
pub fn pick_device() -> evdev::Device {
let mut args = std::env::args_os();
args.next();
if let Some(dev_file) = args.next() {
evdev::Device::open(dev_file).unwrap()
} else {
let mut devices = evdev::enumerate().collect::<Vec<_>>();
// readdir returns them in reverse order from their eventN names for some reason
devices.reverse();
for (i, d) in devices.iter().enumerate() {
println!("{}: {}", i, d.name().unwrap_or("Unnamed device"));
}
print!("Select the device [0-{}]: ", devices.len());
let _ = std::io::stdout().flush();
let mut chosen = String::new();
std::io::stdin().read_line(&mut chosen).unwrap();
let n = chosen.trim().parse::<usize>().unwrap();
devices.into_iter().nth(n).unwrap()
}
}
#[allow(dead_code)]
fn main() {}

View file

@ -1,23 +1,10 @@
// Similar to the evtest tool.
//! Similar to the evtest tool.
use std::io::prelude::*;
// cli/"tui" shared between the evtest examples
mod _pick_device;
fn main() {
let mut args = std::env::args_os();
let mut d = if args.len() > 1 {
evdev::Device::open(&args.nth(1).unwrap()).unwrap()
} else {
let devices = evdev::enumerate().collect::<Vec<_>>();
for (i, d) in devices.iter().enumerate() {
println!("{}: {}", i, d.name().unwrap_or("Unnamed device"));
}
print!("Select the device [0-{}]: ", devices.len());
let _ = std::io::stdout().flush();
let mut chosen = String::new();
std::io::stdin().read_line(&mut chosen).unwrap();
let n = chosen.trim().parse::<usize>().unwrap();
devices.into_iter().nth(n).unwrap()
};
let mut d = _pick_device::pick_device();
println!("{}", d);
println!("Events:");
loop {

View file

@ -9,24 +9,13 @@ use nix::{
fcntl::{FcntlArg, OFlag},
sys::epoll,
};
use std::io::prelude::*;
use std::os::unix::io::{AsRawFd, RawFd};
// cli/"tui" shared between the evtest examples
mod _pick_device;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args_os();
let mut d = if args.len() > 1 {
evdev::Device::open(&args.nth(1).unwrap()).unwrap()
} else {
let mut devices = evdev::enumerate().collect::<Vec<_>>();
for (i, d) in devices.iter().enumerate() {
println!("{}: {}", i, d.name().unwrap_or("Unnamed device"));
}
print!("Select the device [0-{}]: ", devices.len());
let _ = std::io::stdout().flush();
let mut chosen = String::new();
std::io::stdin().read_line(&mut chosen).unwrap();
devices.swap_remove(chosen.trim().parse::<usize>().unwrap())
};
let mut d = _pick_device::pick_device();
println!("{}", d);
let raw_fd = d.as_raw_fd();

View file

@ -1,21 +1,13 @@
//! Demonstrating how to monitor events with evdev + tokio
use tokio_1 as tokio;
// cli/"tui" shared between the evtest examples
mod _pick_device;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args_os();
let d = if args.len() > 1 {
evdev::Device::open(&args.nth(1).unwrap())?
} else {
let mut devices = evdev::enumerate().collect::<Vec<_>>();
for (i, d) in devices.iter().enumerate() {
println!("{}: {}", i, d.name().unwrap_or("Unnamed device"));
}
print!("Select the device [0-{}]: ", devices.len());
let _ = std::io::Write::flush(&mut std::io::stdout());
let mut chosen = String::new();
std::io::stdin().read_line(&mut chosen)?;
devices.swap_remove(chosen.trim().parse::<usize>()?)
};
let d = _pick_device::pick_device();
println!("{}", d);
println!("Events:");
let mut events = d.into_event_stream()?;

View file

@ -0,0 +1,35 @@
// Create a virtual keyboard, just while this is running.
// Generally this requires root.
use evdev::{uinput::VirtualDeviceBuilder, AttributeSet, EventType, InputEvent, Key};
use std::thread::sleep;
use std::time::Duration;
fn main() -> std::io::Result<()> {
let mut keys = AttributeSet::<Key>::new();
keys.insert(Key::BTN_DPAD_UP);
let mut device = VirtualDeviceBuilder::new()?
.name("Fake Keyboard")
.with_keys(&keys)?
.build()
.unwrap();
let type_ = EventType::KEY;
// Note this will ACTUALLY PRESS the button on your computer.
// Hopefully you don't have BTN_DPAD_UP bound to anything important.
let code = Key::BTN_DPAD_UP.code();
println!("Waiting for Ctrl-C...");
loop {
let down_event = InputEvent::new(type_, code, 1);
device.emit(&[down_event]).unwrap();
println!("Pressed.");
sleep(Duration::from_secs(2));
let up_event = InputEvent::new(type_, code, 0);
device.emit(&[up_event]).unwrap();
println!("Released.");
sleep(Duration::from_secs(2));
}
}

View file

@ -43,7 +43,7 @@ evdev_enum!(
);
impl EventType {
pub(crate) const COUNT: usize = 0x20;
pub(crate) const COUNT: usize = libc::EV_CNT;
}
/// A "synchronization" message type published by the kernel into the events stream.
@ -87,7 +87,7 @@ evdev_enum!(
);
impl PropType {
pub(crate) const COUNT: usize = 0x20;
pub(crate) const COUNT: usize = libc::INPUT_PROP_CNT;
}
/// A type of relative axis measurement, typically produced by mice.
@ -113,7 +113,7 @@ evdev_enum!(
);
impl RelativeAxisType {
pub(crate) const COUNT: usize = 0x10;
pub(crate) const COUNT: usize = libc::REL_CNT;
}
/// A type of absolute axis measurement, typically used for touch events and joysticks.
@ -182,7 +182,7 @@ evdev_enum!(
);
impl AbsoluteAxisType {
pub(crate) const COUNT: usize = 0x40;
pub(crate) const COUNT: usize = libc::ABS_CNT;
}
/// An event type corresponding to a physical or virtual switch.
@ -229,7 +229,7 @@ evdev_enum!(
);
impl SwitchType {
pub(crate) const COUNT: usize = 0x11;
pub(crate) const COUNT: usize = libc::SW_CNT;
}
/// LEDs specified by USB HID.
@ -257,7 +257,7 @@ evdev_enum!(
);
impl LedType {
pub(crate) const COUNT: usize = 0x10;
pub(crate) const COUNT: usize = libc::LED_CNT;
}
/// Various miscellaneous event types.
@ -282,7 +282,7 @@ evdev_enum!(
);
impl MiscType {
pub(crate) const COUNT: usize = 0x08;
pub(crate) const COUNT: usize = libc::MSC_CNT;
}
// pub enum FFStatusDataIndex {
@ -312,7 +312,7 @@ impl MiscType {
// impl FFEffect {
// // Needs to be a multiple of 8
// pub const COUNT: usize = 0x80;
// pub const COUNT: usize = libc::FF_CNT;
// }
// #[derive(Copy, Clone, PartialEq, Eq)]
@ -321,7 +321,7 @@ impl MiscType {
// evdev_enum!(RepeatType, REP_DELAY = 0x00, REP_PERIOD = 0x01,);
// impl RepeatType {
// pub(crate) const COUNT: usize = 0x02;
// pub(crate) const COUNT: usize = libc::REP_CNT;
// }
/// A type associated with simple sounds, such as beeps or tones.
@ -337,5 +337,5 @@ evdev_enum!(
);
impl SoundType {
pub(crate) const COUNT: usize = 0x08;
pub(crate) const COUNT: usize = libc::SND_CNT;
}

View file

@ -2,7 +2,7 @@ use std::fmt;
#[derive(Clone)]
#[repr(transparent)]
pub struct InputId(libc::input_id);
pub struct InputId(pub(crate) libc::input_id);
impl From<libc::input_id> for InputId {
#[inline]
@ -30,6 +30,16 @@ impl InputId {
pub fn version(&self) -> u16 {
self.0.version
}
/// Crate a new InputId, useful for customizing virtual input devices.
pub fn new(bus_type: BusType, vendor: u16, product: u16, version: u16) -> Self {
Self::from(libc::input_id {
bustype: bus_type.0,
vendor,
product,
version,
})
}
}
impl fmt::Debug for InputId {

View file

@ -67,8 +67,8 @@
//! For demonstrations of how to use this library in blocking, nonblocking, and async (tokio) modes,
//! please reference the "examples" directory.
#![cfg(any(unix, target_os = "android"))]
#![allow(non_camel_case_types)]
// should really be cfg(target_os = "linux") and maybe also android?
#![cfg(unix)]
// has to be first for its macro
#[macro_use]
@ -81,10 +81,12 @@ pub mod raw_stream;
mod scancodes;
mod sync_stream;
mod sys;
pub mod uinput;
#[cfg(feature = "tokio")]
mod tokio_stream;
use std::os::unix::ffi::OsStrExt;
use std::time::{Duration, SystemTime};
use std::{fmt, io};
@ -123,6 +125,7 @@ pub enum InputEventKind {
/// - `value: s32`
///
/// The meaning of the "code" and "value" fields will depend on the underlying type of event.
#[derive(Copy, Clone)]
#[repr(transparent)]
pub struct InputEvent(libc::input_event);
@ -174,6 +177,34 @@ impl InputEvent {
pub fn value(&self) -> i32 {
self.0.value
}
/// Create a new InputEvent. Only really useful for emitting events on virtual devices.
pub fn new(type_: EventType, code: u16, value: i32) -> Self {
InputEvent(libc::input_event {
time: libc::timeval {
tv_sec: 0,
tv_usec: 0,
},
type_: type_.0,
code,
value,
})
}
/// Create a new InputEvent with the time field set to "now" on the system clock.
///
/// Note that this isn't usually necessary simply for emitting events on a virtual device, as
/// even though [`InputEvent::new`] creates an `input_event` with the time field as zero,
/// the kernel will update `input_event.time` when it emits the events to any programs reading
/// the event "file".
pub fn new_now(type_: EventType, code: u16, value: i32) -> Self {
InputEvent(libc::input_event {
time: systime_to_timeval(&SystemTime::now()),
type_: type_.0,
code,
value,
})
}
}
impl From<libc::input_event> for InputEvent {
@ -223,12 +254,16 @@ impl Iterator for EnumerateDevices {
let readdir = self.readdir.as_mut()?;
loop {
if let Ok(entry) = readdir.next()? {
if let Ok(dev) = Device::open(entry.path()) {
let path = entry.path();
let fname = path.file_name().unwrap();
if fname.as_bytes().starts_with(b"event") {
if let Ok(dev) = Device::open(&path) {
return Some(dev);
}
}
}
}
}
}
/// A safe Rust version of clock_gettime against CLOCK_REALTIME
@ -263,24 +298,7 @@ pub(crate) fn nix_err(err: nix::Error) -> io::Error {
}
}
#[cfg(test)]
mod test {
use std::mem::MaybeUninit;
#[test]
fn align_to_mut_is_sane() {
// We assume align_to_mut -> u8 puts everything in inner. Let's double check.
let mut bits: u32 = 0;
let (prefix, inner, suffix) =
unsafe { std::slice::from_mut(&mut bits).align_to_mut::<u8>() };
assert_eq!(prefix.len(), 0);
assert_eq!(inner.len(), std::mem::size_of::<u32>());
assert_eq!(suffix.len(), 0);
let mut ev: MaybeUninit<libc::input_event> = MaybeUninit::uninit();
let (prefix, inner, suffix) = unsafe { std::slice::from_mut(&mut ev).align_to_mut::<u8>() };
assert_eq!(prefix.len(), 0);
assert_eq!(inner.len(), std::mem::size_of::<libc::input_event>());
assert_eq!(suffix.len(), 0);
}
/// SAFETY: T must not have any padding or otherwise uninitialized bytes inside of it
pub(crate) unsafe fn cast_to_bytes<T: ?Sized>(mem: &T) -> &[u8] {
std::slice::from_raw_parts(mem as *const T as *const u8, std::mem::size_of_val(mem))
}

View file

@ -382,10 +382,10 @@ impl RawDevice {
// TODO: use Vec::spare_capacity_mut or Vec::split_at_spare_mut when they stabilize
let spare_capacity = vec_spare_capacity_mut(&mut self.event_buf);
let (_, uninit_buf, _) = unsafe { spare_capacity.align_to_mut::<mem::MaybeUninit<u8>>() };
let spare_capacity_size = std::mem::size_of_val(spare_capacity);
// use libc::read instead of nix::unistd::read b/c we need to pass an uninitialized buf
let res = unsafe { libc::read(fd, uninit_buf.as_mut_ptr() as _, uninit_buf.len()) };
let res = unsafe { libc::read(fd, spare_capacity.as_mut_ptr() as _, spare_capacity_size) };
let bytes_read = nix::errno::Errno::result(res).map_err(nix_err)?;
let num_read = bytes_read as usize / mem::size_of::<libc::input_event>();
unsafe {

View file

@ -16,7 +16,7 @@ impl Key {
self.0
}
pub(crate) const COUNT: usize = 0x300;
pub(crate) const COUNT: usize = libc::KEY_CNT;
}
const fn bit_elts<T>(bits: usize) -> usize {

View file

@ -1,12 +1,12 @@
use libc::c_int;
use libc::{ff_effect, input_absinfo, input_id};
use libc::{ff_effect, input_absinfo, input_id, uinput_setup};
// use libc::{
// ff_condition_effect, ff_constant_effect, ff_envelope, ff_periodic_effect, ff_ramp_effect,
// ff_replay, ff_rumble_effect, ff_trigger, input_event, input_keymap_entry,
// };
use nix::{
convert_ioctl_res, ioctl_read, ioctl_read_buf, ioctl_write_int, ioctl_write_ptr,
request_code_read,
convert_ioctl_res, ioctl_none, ioctl_read, ioctl_read_buf, ioctl_write_buf, ioctl_write_int,
ioctl_write_ptr, request_code_read,
};
ioctl_read!(eviocgeffects, b'E', 0x84, ::libc::c_int);
@ -36,6 +36,22 @@ ioctl_write_int!(eviocgrab, b'E', 0x90);
ioctl_write_int!(eviocrevoke, b'E', 0x91);
ioctl_write_int!(eviocsclockid, b'E', 0xa0);
const UINPUT_IOCTL_BASE: u8 = b'U';
ioctl_write_ptr!(ui_dev_setup, UINPUT_IOCTL_BASE, 3, uinput_setup);
ioctl_none!(ui_dev_create, UINPUT_IOCTL_BASE, 1);
ioctl_write_int!(ui_set_evbit, UINPUT_IOCTL_BASE, 100);
ioctl_write_int!(ui_set_keybit, UINPUT_IOCTL_BASE, 101);
ioctl_write_int!(ui_set_relbit, UINPUT_IOCTL_BASE, 102);
ioctl_write_int!(ui_set_absbit, UINPUT_IOCTL_BASE, 103);
ioctl_write_int!(ui_set_mscbit, UINPUT_IOCTL_BASE, 104);
ioctl_write_int!(ui_set_ledbit, UINPUT_IOCTL_BASE, 105);
ioctl_write_int!(ui_set_sndbit, UINPUT_IOCTL_BASE, 106);
ioctl_write_int!(ui_set_ffbit, UINPUT_IOCTL_BASE, 107);
ioctl_write_buf!(ui_set_phys, UINPUT_IOCTL_BASE, 108, u8);
ioctl_write_int!(ui_set_swbit, UINPUT_IOCTL_BASE, 109);
ioctl_write_int!(ui_set_propbit, UINPUT_IOCTL_BASE, 110);
macro_rules! eviocgbit_ioctl {
($mac:ident!($name:ident, $ev:ident, $ty:ty)) => {
eviocgbit_ioctl!($mac!($name, $crate::EventType::$ev.0, $ty));

156
src/uinput.rs Normal file
View file

@ -0,0 +1,156 @@
//! Virtual device emulation for evdev via uinput.
//!
//! This is quite useful when testing/debugging devices, or synchronization.
use crate::constants::EventType;
use crate::inputid::{BusType, InputId};
use crate::{nix_err, sys, AttributeSetRef, InputEvent, Key, RelativeAxisType};
use libc::O_NONBLOCK;
use std::fs::{File, OpenOptions};
use std::io::{self, Write};
use std::os::unix::{fs::OpenOptionsExt, io::AsRawFd};
const UINPUT_PATH: &str = "/dev/uinput";
#[derive(Debug)]
pub struct VirtualDeviceBuilder<'a> {
file: File,
name: &'a [u8],
id: Option<libc::input_id>,
}
impl<'a> VirtualDeviceBuilder<'a> {
pub fn new() -> io::Result<Self> {
let mut options = OpenOptions::new();
// Open in write-only, in nonblocking mode.
let file = options
.write(true)
.custom_flags(O_NONBLOCK)
.open(UINPUT_PATH)?;
Ok(VirtualDeviceBuilder {
file,
name: Default::default(),
id: None,
})
}
#[inline]
pub fn name<S: AsRef<[u8]> + ?Sized>(mut self, name: &'a S) -> Self {
self.name = name.as_ref();
self
}
#[inline]
pub fn input_id(mut self, id: InputId) -> Self {
self.id = Some(id.0);
self
}
pub fn with_keys(self, keys: &AttributeSetRef<Key>) -> io::Result<Self> {
// Run ioctls for setting capability bits
unsafe {
sys::ui_set_evbit(
self.file.as_raw_fd(),
crate::EventType::KEY.0 as nix::sys::ioctl::ioctl_param_type,
)
}
.map_err(nix_err)?;
for bit in keys.iter() {
unsafe {
sys::ui_set_keybit(
self.file.as_raw_fd(),
bit.0 as nix::sys::ioctl::ioctl_param_type,
)
}
.map_err(nix_err)?;
}
Ok(self)
}
pub fn with_relative_axes(self, axes: &AttributeSetRef<RelativeAxisType>) -> io::Result<Self> {
unsafe {
sys::ui_set_evbit(
self.file.as_raw_fd(),
crate::EventType::RELATIVE.0 as nix::sys::ioctl::ioctl_param_type,
)
}
.map_err(nix_err)?;
for bit in axes.iter() {
unsafe {
sys::ui_set_relbit(
self.file.as_raw_fd(),
bit.0 as nix::sys::ioctl::ioctl_param_type,
)
}
.map_err(nix_err)?;
}
Ok(self)
}
pub fn build(self) -> io::Result<VirtualDevice> {
// Populate the uinput_setup struct
let mut usetup = libc::uinput_setup {
id: self.id.unwrap_or(DEFAULT_ID),
name: [0; libc::UINPUT_MAX_NAME_SIZE],
ff_effects_max: 0,
};
// SAFETY: either casting [u8] to [u8], or [u8] to [i8], which is the same size
let name_bytes = unsafe { &*(self.name as *const [u8] as *const [libc::c_char]) };
// Panic if we're doing something really stupid
// + 1 for the null terminator; usetup.name was zero-initialized so there will be null
// bytes after the part we copy into
assert!(name_bytes.len() + 1 < libc::UINPUT_MAX_NAME_SIZE);
usetup.name[..name_bytes.len()].copy_from_slice(name_bytes);
VirtualDevice::new(self.file, &usetup)
}
}
const DEFAULT_ID: libc::input_id = libc::input_id {
bustype: BusType::BUS_USB.0,
vendor: 0x1234, /* sample vendor */
product: 0x5678, /* sample product */
version: 0x111,
};
pub struct VirtualDevice {
file: File,
}
impl VirtualDevice {
/// Create a new virtual device.
fn new(file: File, usetup: &libc::uinput_setup) -> io::Result<Self> {
unsafe { sys::ui_dev_setup(file.as_raw_fd(), usetup) }.map_err(nix_err)?;
unsafe { sys::ui_dev_create(file.as_raw_fd()) }.map_err(nix_err)?;
Ok(VirtualDevice { file })
}
#[inline]
fn write_raw(&mut self, messages: &[InputEvent]) -> io::Result<()> {
let bytes = unsafe { crate::cast_to_bytes(messages) };
self.file.write_all(bytes)
}
/// Post a set of messages to the virtual device.
///
/// This inserts a SYN_REPORT for you, because apparently uinput requires that for the
/// kernel to realize we're done.
pub fn emit(&mut self, messages: &[InputEvent]) -> io::Result<()> {
self.write_raw(messages)?;
// Now we have to write a SYN_REPORT as well.
let syn = InputEvent::new(EventType::SYNCHRONIZATION, 0, 0);
self.write_raw(&[syn])?;
Ok(())
}
}