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:
parent
79b6c2b403
commit
6c1add8b73
13 changed files with 319 additions and 88 deletions
|
@ -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
28
examples/_pick_device.rs
Normal 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() {}
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()?;
|
||||
|
|
35
examples/virtual_keyboard.rs
Normal file
35
examples/virtual_keyboard.rs
Normal 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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
66
src/lib.rs
66
src/lib.rs
|
@ -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,8 +254,12 @@ 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()) {
|
||||
return Some(dev);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
22
src/sys.rs
22
src/sys.rs
|
@ -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
156
src/uinput.rs
Normal 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(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue