Refactored test-scenarios.rs. Added stub for force feedback test.

This commit is contained in:
Johannes Leupolz 2026-03-29 22:26:12 +00:00
parent 7182cac6fc
commit e4eb1d77da
16 changed files with 678 additions and 438 deletions

View file

@ -5,9 +5,10 @@
use clap::{Parser, Subcommand};
use vuinputd_tests::scenarios::{
basic_keyboard::BasicKeyboard, basic_mouse::BasicMouse, basic_ps4_gamepad::BasicPs4Gamepad,
basic_xbox_gamepad::BasicXboxGamepad, /*
reuse_keyboard::ReuseKeyboard, reuse_xbox_gamepad::ReuseXboxGamepad,
ScenarioArgs, stress_keyboard::StressKeyboard, stress_xbox_gamepad::StressXboxGamepad, */
basic_xbox_gamepad::BasicXboxGamepad,
ff_xbox_gamepad::FfXboxGamepad, /*
reuse_keyboard::ReuseKeyboard, reuse_xbox_gamepad::ReuseXboxGamepad,
ScenarioArgs, stress_keyboard::StressKeyboard, stress_xbox_gamepad::StressXboxGamepad, */
ScenarioArgs,
};
@ -40,6 +41,9 @@ enum Commands {
/// Basic Xbox gamepad test
BasicXboxGamepad,
/// Force feedback / Vibration Xbox gamepad test
FfXboxGamepad,
/*
/// Reuse keyboard test (create, destroy, recreate)
ReuseKeyboard,
@ -68,6 +72,7 @@ fn main() -> Result<(), std::io::Error> {
Commands::BasicMouse => BasicMouse::run(&args),
Commands::BasicPs4Gamepad => BasicPs4Gamepad::run(&args),
Commands::BasicXboxGamepad => BasicXboxGamepad::run(&args),
Commands::FfXboxGamepad => FfXboxGamepad::run(&args),
/*
Commands::ReuseKeyboard => ReuseKeyboard::run(&args),
Commands::ReuseXboxGamepad => ReuseXboxGamepad::run(&args),

View file

@ -0,0 +1,262 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use crate::test_log::LoggedInputEvent;
use libc::{c_int, close, open, write, O_NONBLOCK, O_WRONLY};
use libc::{input_event, timespec, uinput_setup, CLOCK_MONOTONIC};
use std::ffi::{CStr, CString};
use std::fs::File;
use std::io;
use std::mem::{size_of, zeroed};
use std::os::fd::AsRawFd;
use std::os::raw::{c_char, c_void};
use uinput_ioctls::*;
// Constants (same numeric values as in linux headers)
pub const EV_SYN: u16 = 0x00;
pub const EV_KEY: u16 = 0x01;
pub const EV_REL: u16 = 0x02;
pub const EV_ABS: u16 = 0x03;
pub const EV_FF: u16 = 0x15;
pub const SYN_REPORT: u16 = 0;
pub const BUS_USB: u16 = 0x03;
pub const SYS_INPUT_DIR: &str = "/sys/devices/virtual/input/";
/// Struct holding device state
pub struct DeviceState {
pub uinput_fd: i32,
pub sysname: String,
pub device_name: String,
pub event_device_node: String,
pub event_device_fd: i32,
pub events: Vec<LoggedInputEvent>,
}
/// Trait for input devices
pub trait Device: Sized {
fn name() -> &'static str;
/// open uinput, configure keys, call ui_dev_setup, call ui_dev_create, get sysname and devnode
/// open event device
fn create(device: Option<&str>, name: &str) -> Result<Self, io::Error>;
/// call ui_dev_destroy and close fds
fn destroy(self);
/// Get the device state for internal operations
fn state(&self) -> &DeviceState;
/// Get mutable access to device state for updating events
fn state_mut(&mut self) -> &mut DeviceState;
/// Get the uinput file descriptor
fn uinput_fd(&self) -> i32 {
self.state().uinput_fd
}
/// Get the sysname path
fn sysname(&self) -> &str {
&self.state().sysname
}
/// Get the device name
fn device_name(&self) -> &str {
&self.state().device_name
}
fn get_event_device(&self) -> Result<c_int, io::Error>;
/// Emit an event to the device
fn emit(&self, ev_type: u16, code: u16, val: i32) -> io::Result<()> {
emit(self.uinput_fd(), ev_type, code, val)
}
/// Read an event from the event device
fn read_event(&self) -> io::Result<input_event> {
let event_device_fd = self.get_event_device()?;
read_event(event_device_fd)
}
/// Emit and read an event with logging
fn emit_read_and_log(
&mut self,
ev_type: u16,
code: u16,
val: i32,
) -> io::Result<LoggedInputEvent> {
let event_device_fd = self.get_event_device()?;
let event = emit_read_and_log(self.uinput_fd(), event_device_fd, ev_type, code, val)?;
self.state_mut().events.push(event.clone());
Ok(event)
}
/// Get the event log
fn event_log(&self) -> &[LoggedInputEvent] {
&self.state().events
}
/// Reset the event log
fn reset_event_log(&mut self) {
self.state_mut().events.clear();
}
/// Get the event log as mutable slice
fn event_log_mut(&mut self) -> &mut Vec<LoggedInputEvent> {
&mut self.state_mut().events
}
/// Setup the uinput device (calls ui_dev_setup and ui_get_sysname)
fn setup_device(&self, name: &str, vendor: u16, product: u16, bustype: u16) -> io::Result<()> {
unsafe {
let mut usetup: uinput_setup = zeroed();
usetup.id.bustype = bustype;
usetup.id.vendor = vendor;
usetup.id.product = product;
let name_cstr = CString::new(name).unwrap();
let name_ptr = usetup.name.as_mut_ptr() as *mut c_char;
std::ptr::copy_nonoverlapping(
name_cstr.as_ptr(),
name_ptr,
name_cstr.to_bytes_with_nul().len(),
);
let usetup_ptr = &mut usetup as *mut uinput_setup;
ui_dev_setup(self.uinput_fd(), usetup_ptr).map_err(|e| {
eprintln!("ui_dev_setup failed: {:?}", e);
e
})?;
Ok(())
}
}
/// Get the sysname from the uinput fd
fn get_sysname(&self) -> io::Result<String> {
unsafe {
let mut resultbuf: [c_char; 64] = [0; 64];
ui_get_sysname(self.uinput_fd(), resultbuf.as_mut_slice()).map_err(|e| {
eprintln!("ui_get_sysname failed: {:?}", e);
e
})?;
Ok(format!(
"{}{}",
SYS_INPUT_DIR,
CStr::from_ptr(resultbuf.as_ptr()).to_string_lossy()
))
}
}
}
/// Emit an event to the uinput device
pub fn emit(fd: c_int, ev_type: u16, code: u16, val: i32) -> io::Result<()> {
let mut ie: libc::input_event = unsafe { zeroed() };
ie.time.tv_sec = 0;
ie.time.tv_usec = 0;
ie.type_ = ev_type;
ie.code = code;
ie.value = val;
let buf_ptr = &ie as *const libc::input_event as *const c_void;
let bytes = size_of::<libc::input_event>();
let written = unsafe { write(fd, buf_ptr, bytes) };
if written as usize != bytes {
return Err(io::Error::last_os_error());
}
Ok(())
}
/// Emit event, sync, and read back with logging
pub fn emit_read_and_log(
emit_to: c_int,
read_from: c_int,
ev_type: u16,
code: u16,
val: i32,
) -> io::Result<LoggedInputEvent> {
let (time_sent_sec, time_sent_nsec) = monotonic_time();
emit(emit_to, ev_type, code, val)?;
emit(emit_to, EV_SYN, SYN_REPORT, 0)?;
let input_event_recv = read_event(read_from).unwrap();
let _syn_recv = read_event(read_from).unwrap();
let (time_recv_sec, time_recv_nsec) = monotonic_time();
let duration_usec =
(time_recv_sec - time_sent_sec) * 1_000_000 + (time_recv_nsec - time_sent_nsec) / 1000;
let send_and_receive_match = input_event_recv.type_ == ev_type
&& input_event_recv.code == code
&& input_event_recv.value == val;
Ok(LoggedInputEvent {
tv_sec: time_sent_sec,
tv_nsec: time_sent_nsec,
duration_usec,
type_: ev_type,
code,
value: val,
send_and_receive_match,
})
}
/// Read an event from the event device
pub fn read_event(event_dev_fd: c_int) -> io::Result<input_event> {
let mut ev: input_event = unsafe { zeroed() };
let ret = unsafe {
libc::read(
event_dev_fd,
&mut ev as *mut _ as *mut c_void,
size_of::<input_event>(),
)
};
if ret as usize != size_of::<input_event>() {
return Err(io::Error::last_os_error());
}
Ok(ev)
}
/// Get monotonic time
pub fn monotonic_time() -> (i64, i64) {
let mut ts = timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe {
libc::clock_gettime(CLOCK_MONOTONIC, &mut ts);
}
(ts.tv_sec, ts.tv_nsec)
}
/// Open uinput device
pub fn open_uinput(device: Option<&str>) -> io::Result<i32> {
let device = match device {
Some(dev_path) => dev_path,
_ => "/dev/uinput",
};
let path = std::ffi::CString::new(device).unwrap();
let fd = unsafe { open(path.as_ptr(), O_WRONLY | O_NONBLOCK) };
if fd < 0 {
eprintln!("error opening uinput");
return Err(io::Error::last_os_error());
}
Ok(fd)
}
/// Fetch the event device node from the sysname path
pub fn fetch_device_node(sysname: &str) -> io::Result<String> {
use std::fs;
use std::io::ErrorKind;
for entry in fs::read_dir(sysname)? {
let entry = entry?;
if let Some(name) = entry.file_name().to_str() {
if name.starts_with("event") {
return Ok(format!("/dev/input/{}", name));
}
}
}
Err(io::Error::new(ErrorKind::NotFound, "no device found"))
}

View file

@ -2,9 +2,11 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use super::Device;
use crate::devices::device_base::{
fetch_device_node, open_uinput, Device, DeviceState, BUS_USB, SYS_INPUT_DIR,
};
use libc::{c_int, close, open};
use std::io;
use std::{ffi::CStr, fs::File};
use uinput_ioctls::*;
/// Key codes. Those are used by udev to recognize a device as a keyboard.
@ -126,7 +128,7 @@ pub const KEY_INSERT: u16 = 110;
pub const KEY_DELETE: u16 = 111;
/// Configure a full 101-key standard keyboard
unsafe fn set_standard_keyboard_keys(fd: i32) -> Result<(), std::io::Error> {
unsafe fn set_standard_keyboard_keys(fd: c_int) -> Result<(), std::io::Error> {
// We need to set more bits so that systemd recognizes a keyboard as a keyboard.
// At least the first 32 bits are ESC, numbers, and Q to D, except KEY_RESERVED need to be considered.
// udev-builtin-input_id.c consideres the mask = 0xFFFFFFFE
@ -260,81 +262,84 @@ unsafe fn set_standard_keyboard_keys(fd: i32) -> Result<(), std::io::Error> {
Ok(())
}
pub struct KeyboardDevice;
pub struct KeyboardDevice {
state: DeviceState,
}
impl Device for KeyboardDevice {
fn name() -> &'static str {
"Keyboard"
}
fn get_event_device(sysname: &str) -> Result<File, io::Error> {
super::utils::fetch_device_node(sysname).and_then(|devnode| File::open(&devnode))
fn state(&self) -> &DeviceState {
&self.state
}
fn setup(device: Option<&str>, name: &str) -> Result<i32, io::Error> {
let fd = super::utils::open_uinput(device)?;
unsafe { set_standard_keyboard_keys(fd) }?;
unsafe {
let mut usetup: libc::uinput_setup = std::mem::zeroed();
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0xbeef;
usetup.id.product = 0xdead;
let name_cstr = CString::new(name).unwrap();
let name_ptr = usetup.name.as_mut_ptr() as *mut c_char;
std::ptr::copy_nonoverlapping(
name_cstr.as_ptr(),
name_ptr,
name_cstr.to_bytes_with_nul().len(),
);
let usetup_ptr = &mut usetup as *mut libc::uinput_setup;
ui_dev_setup(fd, usetup_ptr).map_err(|e| {
eprintln!("ui_dev_setup failed: {:?}", e);
e
})?;
}
Ok(fd)
fn state_mut(&mut self) -> &mut DeviceState {
&mut self.state
}
fn create(fd: i32) -> Result<String, io::Error> {
fn get_event_device(&self) -> Result<c_int, io::Error> {
Ok(self.state.event_device_fd)
}
fn create(device: Option<&str>, name: &str) -> Result<Self, io::Error> {
let fd = open_uinput(device)?;
unsafe { set_standard_keyboard_keys(fd)? };
let temp_device = KeyboardDevice {
state: DeviceState {
uinput_fd: fd,
sysname: String::new(),
device_name: name.to_string(),
event_device_node: String::new(),
event_device_fd: -1,
events: Vec::new(),
},
};
temp_device.setup_device(name, 0xbeef, 0xdead, BUS_USB)?;
unsafe {
ui_dev_create(fd).map_err(|e| {
eprintln!("ui_dev_create failed: {:?}", e);
e
})?;
let mut resultbuf: [c_char; 64] = [0; 64];
ui_get_sysname(fd, resultbuf.as_mut_slice()).map_err(|e| {
eprintln!("ui_get_sysname failed: {:?}", e);
e
})?;
let sysname = format!(
"{}{}",
SYS_INPUT_DIR,
CStr::from_ptr(resultbuf.as_ptr()).to_string_lossy()
);
Ok(sysname)
}
let sysname = temp_device.get_sysname()?;
let event_device_node = fetch_device_node(&sysname)?;
let event_device_fd = unsafe {
open(
event_device_node.as_ptr() as *const i8,
libc::O_RDONLY | libc::O_NONBLOCK,
)
};
if event_device_fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(KeyboardDevice {
state: DeviceState {
uinput_fd: fd,
sysname,
device_name: name.to_string(),
event_device_node,
event_device_fd,
events: Vec::new(),
},
})
}
fn destroy(fd: i32) {
fn destroy(self) {
unsafe {
ui_dev_destroy(fd).unwrap_or_else(|e| {
ui_dev_destroy(self.state.uinput_fd).unwrap_or_else(|e| {
eprintln!("ui_dev_destroy failed: {:?}", e);
std::process::exit(1);
});
close(fd);
close(self.state.uinput_fd);
close(self.state.event_device_fd);
}
}
}
use libc::{c_char, close};
use std::ffi::CString;
const SYS_INPUT_DIR: &str = "/sys/devices/virtual/input/";
const BUS_USB: u16 = 0x03;

View file

@ -2,38 +2,20 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use std::fs::File;
use std::io;
pub mod device_base;
pub mod keyboard;
pub mod mouse;
pub mod ps4_gamepad;
pub mod utils;
pub mod xbox_gamepad;
pub use device_base::Device;
// Keep DeviceState exported for backward compatibility
pub use device_base::DeviceState;
pub use keyboard::KeyboardDevice;
pub use mouse::MouseDevice;
pub use ps4_gamepad::Ps4GamepadDevice;
pub use xbox_gamepad::XboxGamepadDevice;
// Constants (same numeric values as in linux headers)
pub const EV_SYN: u16 = 0x00;
pub const EV_KEY: u16 = 0x01;
pub const EV_REL: u16 = 0x02;
pub const EV_ABS: u16 = 0x03;
pub const EV_FF: u16 = 0x15;
/// Trait for input devices
pub trait Device {
fn name() -> &'static str;
fn get_event_device(sysname: &str) -> Result<File, io::Error>;
/// Phase 1: open uinput, configure keys, call ui_dev_setup
fn setup(device: Option<&str>, name: &str) -> Result<i32, io::Error>;
/// Phase 2: call ui_dev_create, get sysname and devnode
fn create(fd: i32) -> Result<String, io::Error>;
/// Phase 3: call ui_dev_destroy and close fd
fn destroy(fd: i32);
}
// Re-export constants from device_base for backward compatibility
pub use device_base::{BUS_USB, EV_ABS, EV_FF, EV_KEY, EV_REL, EV_SYN, SYN_REPORT, SYS_INPUT_DIR};

View file

@ -2,10 +2,9 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use super::Device;
use libc::c_int;
use crate::devices::device_base::{fetch_device_node, open_uinput, Device, DeviceState, BUS_USB};
use libc::{c_int, close, open};
use std::io;
use std::{ffi::CStr, fs::File};
use uinput_ioctls::*;
// Mouse codes
@ -32,81 +31,84 @@ unsafe fn setup_mouse(fd: c_int) -> io::Result<()> {
Ok(())
}
pub struct MouseDevice;
pub struct MouseDevice {
state: DeviceState,
}
impl Device for MouseDevice {
fn name() -> &'static str {
"Mouse"
}
fn get_event_device(sysname: &str) -> Result<File, io::Error> {
super::utils::fetch_device_node(sysname).and_then(|devnode| File::open(&devnode))
fn state(&self) -> &DeviceState {
&self.state
}
fn setup(device: Option<&str>, name: &str) -> Result<i32, io::Error> {
let fd = super::utils::open_uinput(device)?;
unsafe { setup_mouse(fd) }?;
unsafe {
let mut usetup: libc::uinput_setup = std::mem::zeroed();
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0xbeef;
usetup.id.product = 0xdead;
let name_cstr = CString::new(name).unwrap();
let name_ptr = usetup.name.as_mut_ptr() as *mut c_char;
std::ptr::copy_nonoverlapping(
name_cstr.as_ptr(),
name_ptr,
name_cstr.to_bytes_with_nul().len(),
);
let usetup_ptr = &mut usetup as *mut libc::uinput_setup;
ui_dev_setup(fd, usetup_ptr).map_err(|e| {
eprintln!("ui_dev_setup failed: {:?}", e);
e
})?;
}
Ok(fd)
fn state_mut(&mut self) -> &mut DeviceState {
&mut self.state
}
fn create(fd: i32) -> Result<String, io::Error> {
fn get_event_device(&self) -> Result<c_int, io::Error> {
Ok(self.state.event_device_fd)
}
fn create(device: Option<&str>, name: &str) -> Result<Self, io::Error> {
let fd = open_uinput(device)?;
unsafe { setup_mouse(fd)? };
let temp_device = MouseDevice {
state: DeviceState {
uinput_fd: fd,
sysname: String::new(),
device_name: name.to_string(),
event_device_node: String::new(),
event_device_fd: -1,
events: Vec::new(),
},
};
temp_device.setup_device(name, 0xbeef, 0xdead, BUS_USB)?;
unsafe {
ui_dev_create(fd).map_err(|e| {
eprintln!("ui_dev_create failed: {:?}", e);
e
})?;
let mut resultbuf: [c_char; 64] = [0; 64];
ui_get_sysname(fd, resultbuf.as_mut_slice()).map_err(|e| {
eprintln!("ui_get_sysname failed: {:?}", e);
e
})?;
let sysname = format!(
"{}{}",
SYS_INPUT_DIR,
CStr::from_ptr(resultbuf.as_ptr()).to_string_lossy()
);
Ok(sysname)
}
let sysname = temp_device.get_sysname()?;
let event_device_node = fetch_device_node(&sysname)?;
let event_device_fd = unsafe {
open(
event_device_node.as_ptr() as *const i8,
libc::O_RDONLY | libc::O_NONBLOCK,
)
};
if event_device_fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(MouseDevice {
state: DeviceState {
uinput_fd: fd,
sysname,
device_name: name.to_string(),
event_device_node,
event_device_fd,
events: Vec::new(),
},
})
}
fn destroy(fd: i32) {
fn destroy(self) {
unsafe {
ui_dev_destroy(fd).unwrap_or_else(|e| {
ui_dev_destroy(self.state.uinput_fd).unwrap_or_else(|e| {
eprintln!("ui_dev_destroy failed: {:?}", e);
std::process::exit(1);
});
close(fd);
close(self.state.uinput_fd);
close(self.state.event_device_fd);
}
}
}
use libc::{c_char, close};
use std::ffi::CString;
const SYS_INPUT_DIR: &str = "/sys/devices/virtual/input/";
const BUS_USB: u16 = 0x03;

View file

@ -4,10 +4,9 @@
// this file is ai genrated and contains many mistake that need to be fixed manually
use super::Device;
use libc::c_int;
use crate::devices::device_base::{fetch_device_node, open_uinput, Device, DeviceState, BUS_USB};
use libc::{c_int, close, open};
use std::io;
use std::{ffi::CStr, fs::File};
use uinput_ioctls::*;
// PS4 Gamepad codes
@ -58,7 +57,9 @@ const BTN_X: u16 = 306; // X
const BTN_Y: u16 = 307; // Y
const BTN_TL2: u16 = 319; // LT
pub struct Ps4GamepadDevice;
pub struct Ps4GamepadDevice {
state: DeviceState,
}
/// Setup PS4 gamepad device
unsafe fn setup_ps4_gamepad(fd: c_int) -> io::Result<()> {
@ -105,74 +106,75 @@ impl Device for Ps4GamepadDevice {
"PS4 Gamepad"
}
fn get_event_device(sysname: &str) -> Result<File, io::Error> {
super::utils::fetch_device_node(sysname).and_then(|devnode| File::open(&devnode))
fn state(&self) -> &DeviceState {
&self.state
}
fn setup(device: Option<&str>, name: &str) -> Result<i32, io::Error> {
let fd = super::utils::open_uinput(device)?;
unsafe { setup_ps4_gamepad(fd) }?;
unsafe {
let mut usetup: libc::uinput_setup = std::mem::zeroed();
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0xbeef;
usetup.id.product = 0xdead;
let name_cstr = CString::new(name).unwrap();
let name_ptr = usetup.name.as_mut_ptr() as *mut c_char;
std::ptr::copy_nonoverlapping(
name_cstr.as_ptr(),
name_ptr,
name_cstr.to_bytes_with_nul().len(),
);
let usetup_ptr = &mut usetup as *mut libc::uinput_setup;
ui_dev_setup(fd, usetup_ptr).map_err(|e| {
eprintln!("ui_dev_setup failed: {:?}", e);
e
})?;
}
Ok(fd)
fn state_mut(&mut self) -> &mut DeviceState {
&mut self.state
}
fn create(fd: i32) -> Result<String, io::Error> {
fn get_event_device(&self) -> Result<c_int, io::Error> {
Ok(self.state.event_device_fd)
}
fn create(device: Option<&str>, name: &str) -> Result<Self, io::Error> {
let fd = open_uinput(device)?;
unsafe { setup_ps4_gamepad(fd)? };
let temp_device = Ps4GamepadDevice {
state: DeviceState {
uinput_fd: fd,
sysname: String::new(),
device_name: name.to_string(),
event_device_node: String::new(),
event_device_fd: -1,
events: Vec::new(),
},
};
temp_device.setup_device(name, 0xbeef, 0xdead, BUS_USB)?;
unsafe {
ui_dev_create(fd).map_err(|e| {
eprintln!("ui_dev_create failed: {:?}", e);
e
})?;
let mut resultbuf: [c_char; 64] = [0; 64];
ui_get_sysname(fd, resultbuf.as_mut_slice()).map_err(|e| {
eprintln!("ui_get_sysname failed: {:?}", e);
e
})?;
let sysname = format!(
"{}{}",
SYS_INPUT_DIR,
CStr::from_ptr(resultbuf.as_ptr()).to_string_lossy()
);
Ok(sysname)
}
let sysname = temp_device.get_sysname()?;
let event_device_node = fetch_device_node(&sysname)?;
let event_device_fd = unsafe {
open(
event_device_node.as_ptr() as *const i8,
libc::O_RDONLY | libc::O_NONBLOCK,
)
};
if event_device_fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(Ps4GamepadDevice {
state: DeviceState {
uinput_fd: fd,
sysname,
device_name: name.to_string(),
event_device_node,
event_device_fd,
events: Vec::new(),
},
})
}
fn destroy(fd: i32) {
fn destroy(self) {
unsafe {
ui_dev_destroy(fd).unwrap_or_else(|e| {
ui_dev_destroy(self.state.uinput_fd).unwrap_or_else(|e| {
eprintln!("ui_dev_destroy failed: {:?}", e);
std::process::exit(1);
});
close(fd);
close(self.state.uinput_fd);
close(self.state.event_device_fd);
}
}
}
use libc::{c_char, close};
use std::ffi::CString;
const SYS_INPUT_DIR: &str = "/sys/devices/virtual/input/";
const BUS_USB: u16 = 0x03;

View file

@ -2,6 +2,7 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
pub use crate::devices::device_base::*;
use crate::test_log::{LoggedInputEvent, TestLog};
use libc::{c_int, close, open, write, O_NONBLOCK, O_WRONLY};
use libc::{input_event, timespec, uinput_setup, CLOCK_MONOTONIC};
@ -14,128 +15,5 @@ use std::os::raw::{c_char, c_void};
use std::ptr;
pub use uinput_ioctls::*;
// Constants (same numeric values as in linux headers)
const EV_SYN: u16 = 0x00;
const EV_KEY: u16 = 0x01;
const SYN_REPORT: u16 = 0;
const BUS_USB: u16 = 0x03;
pub fn emit(fd: c_int, ev_type: u16, code: u16, val: i32) -> io::Result<()> {
// libc's input_event struct layout:
// struct input_event {
// struct timeval time;
// __u16 type;
// __u16 code;
// __s32 value;
// };
//
// libc provides input_event as `libc::input_event` on Linux.
let mut ie: libc::input_event = unsafe { zeroed() };
// time fields are ignored by kernel for synthetic events - set zero
ie.time.tv_sec = 0;
ie.time.tv_usec = 0;
ie.type_ = ev_type; // note: in libc the field is `type_`
ie.code = code;
ie.value = val;
// write the struct to the uinput fd
let buf_ptr = &ie as *const libc::input_event as *const c_void;
let bytes = size_of::<libc::input_event>();
//println!("write to {} {} {} {} ",fd,ev_type,code,val);
let written = unsafe { write(fd, buf_ptr, bytes) };
//println!("written");
if written as usize != bytes {
return Err(io::Error::last_os_error());
}
Ok(())
}
// Note that before we can read, a SYN needs to be sent. Thus combine it.
pub fn emit_read_and_log(
emit_to: c_int,
read_from: &File,
ev_type: u16,
code: u16,
val: i32,
) -> io::Result<LoggedInputEvent> {
let (time_sent_sec, time_sent_nsec) = monotonic_time();
emit(emit_to, ev_type, code, val)?;
emit(emit_to, EV_SYN, SYN_REPORT, 0)?;
let input_event_recv = read_event(&read_from).unwrap();
let _syn_recv = read_event(&read_from).unwrap();
let (time_recv_sec, time_recv_nsec) = monotonic_time();
let duration_usec =
(time_recv_sec - time_sent_sec) * 1_000_000 + (time_recv_nsec - time_sent_nsec) / 1000;
let send_and_receive_match = input_event_recv.type_ == ev_type
&& input_event_recv.code == code
&& input_event_recv.value == val;
Ok(LoggedInputEvent {
tv_sec: time_sent_sec,
tv_nsec: time_sent_nsec,
duration_usec: duration_usec,
type_: ev_type,
code: code,
value: val,
send_and_receive_match: send_and_receive_match,
})
}
pub fn fetch_device_node(path: &str) -> io::Result<String> {
println!("Read dir {}", &path);
for entry in fs::read_dir(path)? {
let entry = entry?; // propagate per-entry errors
if let Some(name) = entry.file_name().to_str() {
if name.starts_with("event") {
return Ok(format!("/dev/input/{}", name));
}
}
}
// If no device is found, return an error
Err(io::Error::new(ErrorKind::NotFound, "no device found"))
}
pub fn read_event(event_dev: &File) -> io::Result<input_event> {
let mut ev: input_event = unsafe { mem::zeroed() };
let ret = unsafe {
libc::read(
event_dev.as_raw_fd(),
&mut ev as *mut _ as *mut c_void,
mem::size_of::<input_event>(),
)
};
if ret as usize != mem::size_of::<input_event>() {
return Err(io::Error::last_os_error());
}
Ok(ev)
}
pub fn monotonic_time() -> (i64, i64) {
let mut ts = timespec {
tv_sec: 0,
tv_nsec: 0,
};
unsafe {
libc::clock_gettime(CLOCK_MONOTONIC, &mut ts);
}
(ts.tv_sec, ts.tv_nsec)
}
pub fn open_uinput(device: Option<&str>) -> io::Result<i32> {
let device = match device {
Some(dev_path) => dev_path,
_ => "/dev/uinput",
};
let path = CString::new(device).unwrap();
let fd = unsafe { open(path.as_ptr(), O_WRONLY | O_NONBLOCK) };
if fd < 0 {
eprintln!("error opening uinput");
return Err(io::Error::last_os_error());
}
Ok(fd)
}
// Re-export constants from device_base for backward compatibility
pub use crate::devices::device_base::{BUS_USB, EV_ABS, EV_FF, EV_KEY, EV_REL, EV_SYN, SYN_REPORT};

View file

@ -2,10 +2,9 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use super::Device;
use libc::c_int;
use crate::devices::device_base::{fetch_device_node, open_uinput, Device, DeviceState, BUS_USB};
use libc::{c_int, close, open};
use std::io;
use std::{ffi::CStr, fs::File};
use uinput_ioctls::*;
// Xbox Gamepad codes
@ -67,19 +66,88 @@ unsafe fn setup_xbox_gamepad(fd: c_int) -> io::Result<()> {
// EV_ABS
ui_set_evbit(fd, super::EV_ABS.try_into().unwrap())?;
// EV_ABS dpad
let abs_info_dpad = libc::input_absinfo { value: 0, minimum: -1, maximum: 1, fuzz: 0, flat: 0, resolution: 0 };
ui_abs_setup(fd, &libc::uinput_abs_setup { code: ABS_HAT0Y, absinfo: abs_info_dpad.clone() })?;
ui_abs_setup(fd, &libc::uinput_abs_setup { code: ABS_HAT0X, absinfo: abs_info_dpad.clone() })?;
let abs_info_dpad = libc::input_absinfo {
value: 0,
minimum: -1,
maximum: 1,
fuzz: 0,
flat: 0,
resolution: 0,
};
ui_abs_setup(
fd,
&libc::uinput_abs_setup {
code: ABS_HAT0Y,
absinfo: abs_info_dpad.clone(),
},
)?;
ui_abs_setup(
fd,
&libc::uinput_abs_setup {
code: ABS_HAT0X,
absinfo: abs_info_dpad.clone(),
},
)?;
// EV_ABS stick
let abs_info_stick = libc::input_absinfo { value: 0, minimum: -32768, maximum: 32767, fuzz: 16, flat: 128, resolution: 0 };
ui_abs_setup(fd, &libc::uinput_abs_setup { code: ABS_X, absinfo: abs_info_stick.clone() })?;
ui_abs_setup(fd, &libc::uinput_abs_setup { code: ABS_RX, absinfo: abs_info_stick.clone() })?;
ui_abs_setup(fd, &libc::uinput_abs_setup { code: ABS_Y, absinfo: abs_info_stick.clone() })?;
ui_abs_setup(fd, &libc::uinput_abs_setup { code: ABS_RY, absinfo: abs_info_stick.clone() })?;
let abs_info_stick = libc::input_absinfo {
value: 0,
minimum: -32768,
maximum: 32767,
fuzz: 16,
flat: 128,
resolution: 0,
};
ui_abs_setup(
fd,
&libc::uinput_abs_setup {
code: ABS_X,
absinfo: abs_info_stick.clone(),
},
)?;
ui_abs_setup(
fd,
&libc::uinput_abs_setup {
code: ABS_RX,
absinfo: abs_info_stick.clone(),
},
)?;
ui_abs_setup(
fd,
&libc::uinput_abs_setup {
code: ABS_Y,
absinfo: abs_info_stick.clone(),
},
)?;
ui_abs_setup(
fd,
&libc::uinput_abs_setup {
code: ABS_RY,
absinfo: abs_info_stick.clone(),
},
)?;
// EV_ABS trigger
let abs_info_trigger = libc::input_absinfo { value: 0, minimum: 0, maximum: 255, fuzz: 0, flat: 0, resolution: 0 };
ui_abs_setup(fd, &libc::uinput_abs_setup { code: ABS_Z, absinfo: abs_info_trigger.clone() })?;
ui_abs_setup(fd, &libc::uinput_abs_setup { code: ABS_RZ, absinfo: abs_info_trigger.clone() })?;
let abs_info_trigger = libc::input_absinfo {
value: 0,
minimum: 0,
maximum: 255,
fuzz: 0,
flat: 0,
resolution: 0,
};
ui_abs_setup(
fd,
&libc::uinput_abs_setup {
code: ABS_Z,
absinfo: abs_info_trigger.clone(),
},
)?;
ui_abs_setup(
fd,
&libc::uinput_abs_setup {
code: ABS_RZ,
absinfo: abs_info_trigger.clone(),
},
)?;
// EV_FF
ui_set_evbit(fd, super::EV_FF.try_into().unwrap())?;
@ -93,81 +161,84 @@ unsafe fn setup_xbox_gamepad(fd: c_int) -> io::Result<()> {
Ok(())
}
pub struct XboxGamepadDevice;
pub struct XboxGamepadDevice {
state: DeviceState,
}
impl Device for XboxGamepadDevice {
fn name() -> &'static str {
"Xbox Gamepad"
}
fn get_event_device(sysname: &str) -> Result<File, io::Error> {
super::utils::fetch_device_node(sysname).and_then(|devnode| File::open(&devnode))
fn state(&self) -> &DeviceState {
&self.state
}
fn setup(device: Option<&str>, name: &str) -> Result<i32, io::Error> {
let fd = super::utils::open_uinput(device)?;
unsafe { setup_xbox_gamepad(fd) }?;
unsafe {
let mut usetup: libc::uinput_setup = std::mem::zeroed();
usetup.id.bustype = BUS_USB;
usetup.id.vendor = 0xbeef;
usetup.id.product = 0xdead;
let name_cstr = CString::new(name).unwrap();
let name_ptr = usetup.name.as_mut_ptr() as *mut c_char;
std::ptr::copy_nonoverlapping(
name_cstr.as_ptr(),
name_ptr,
name_cstr.to_bytes_with_nul().len(),
);
let usetup_ptr = &mut usetup as *mut libc::uinput_setup;
ui_dev_setup(fd, usetup_ptr).map_err(|e| {
eprintln!("ui_dev_setup failed: {:?}", e);
e
})?;
}
Ok(fd)
fn state_mut(&mut self) -> &mut DeviceState {
&mut self.state
}
fn create(fd: i32) -> Result<String, io::Error> {
fn get_event_device(&self) -> Result<c_int, io::Error> {
Ok(self.state.event_device_fd)
}
fn create(device: Option<&str>, name: &str) -> Result<Self, io::Error> {
let fd = open_uinput(device)?;
unsafe { setup_xbox_gamepad(fd)? };
let temp_device = XboxGamepadDevice {
state: DeviceState {
uinput_fd: fd,
sysname: String::new(),
device_name: name.to_string(),
event_device_node: String::new(),
event_device_fd: -1,
events: Vec::new(),
},
};
temp_device.setup_device(name, 0xbeef, 0xdead, BUS_USB)?;
unsafe {
ui_dev_create(fd).map_err(|e| {
eprintln!("ui_dev_create failed: {:?}", e);
e
})?;
let mut resultbuf: [c_char; 64] = [0; 64];
ui_get_sysname(fd, resultbuf.as_mut_slice()).map_err(|e| {
eprintln!("ui_get_sysname failed: {:?}", e);
e
})?;
let sysname = format!(
"{}{}",
SYS_INPUT_DIR,
CStr::from_ptr(resultbuf.as_ptr()).to_string_lossy()
);
Ok(sysname)
}
let sysname = temp_device.get_sysname()?;
let event_device_node = fetch_device_node(&sysname)?;
let event_device_fd = unsafe {
open(
event_device_node.as_ptr() as *const i8,
libc::O_RDONLY | libc::O_NONBLOCK,
)
};
if event_device_fd < 0 {
return Err(io::Error::last_os_error());
}
Ok(XboxGamepadDevice {
state: DeviceState {
uinput_fd: fd,
sysname,
device_name: name.to_string(),
event_device_node,
event_device_fd,
events: Vec::new(),
},
})
}
fn destroy(fd: i32) {
fn destroy(self) {
unsafe {
ui_dev_destroy(fd).unwrap_or_else(|e| {
ui_dev_destroy(self.state.uinput_fd).unwrap_or_else(|e| {
eprintln!("ui_dev_destroy failed: {:?}", e);
std::process::exit(1);
});
close(fd);
close(self.state.uinput_fd);
close(self.state.event_device_fd);
}
}
}
use libc::{c_char, close};
use std::ffi::CString;
pub const SYS_INPUT_DIR: &str = "/sys/devices/virtual/input/";
pub const BUS_USB: u16 = 0x03;

View file

@ -2,10 +2,10 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
pub mod devices;
pub mod scenarios;
pub mod bwrap;
pub mod devices;
pub mod ipc;
pub mod podman;
pub mod run_vuinputd;
pub mod scenarios;
pub mod test_log;

View file

@ -6,7 +6,7 @@ use std::thread;
use std::time::Duration;
use crate::devices::keyboard::KeyboardDevice;
use crate::devices::{utils, Device};
use crate::devices::{Device, EV_KEY};
use crate::scenarios::ScenarioArgs;
use crate::test_log::TestLog;
@ -20,26 +20,21 @@ impl BasicKeyboard {
.dev_path
.clone()
.unwrap_or_else(|| "/dev/uinput".to_string());
let fd = KeyboardDevice::setup(Some(&device), "Example Keyboard")?;
let sysname = KeyboardDevice::create(fd)?;
eprintln!("sysname: {}", sysname);
let mut keyboard = KeyboardDevice::create(Some(&device), "Example Keyboard")?;
eprintln!("sysname: {}", keyboard.sysname());
thread::sleep(Duration::from_secs(1));
let event_device = std::fs::OpenOptions::new()
.read(true)
.open(&utils::fetch_device_node(&sysname)?)?;
let ev1 = utils::emit_read_and_log(fd, &event_device, 0x01, KEY_SPACE, 1)?;
let ev2 = utils::emit_read_and_log(fd, &event_device, 0x01, KEY_SPACE, 0)?;
let _ev1 = keyboard.emit_read_and_log(EV_KEY, KEY_SPACE, 1)?;
let _ev2 = keyboard.emit_read_and_log(EV_KEY, KEY_SPACE, 0)?;
let eventlog = TestLog {
events: vec![ev1, ev2],
events: keyboard.event_log().to_vec(),
};
let serialized = serde_json::to_string(&eventlog).unwrap();
println!("Event log: {}", serialized);
KeyboardDevice::destroy(fd);
KeyboardDevice::destroy(keyboard);
Ok(())
}
}

View file

@ -5,7 +5,7 @@ use std::thread;
use std::time::Duration;
use crate::devices::mouse::MouseDevice;
use crate::devices::{utils, Device};
use crate::devices::{Device, EV_KEY};
use crate::scenarios::ScenarioArgs;
use crate::test_log::{LoggedInputEvent, TestLog};
@ -20,26 +20,21 @@ impl BasicMouse {
.clone()
.unwrap_or_else(|| "/dev/uinput".to_string());
let fd = MouseDevice::setup(Some(&device), "Example Mouse")?;
let sysname = MouseDevice::create(fd)?;
eprintln!("sysname: {}", sysname);
let mut mouse = MouseDevice::create(Some(&device), "Example Mouse")?;
eprintln!("sysname: {}", mouse.sysname());
thread::sleep(Duration::from_secs(1));
let event_device = std::fs::OpenOptions::new()
.read(true)
.open(&utils::fetch_device_node(&sysname)?)?;
let ev1 = utils::emit_read_and_log(fd, &event_device, 0x01, BTN_LEFT, 1)?;
let ev2 = utils::emit_read_and_log(fd, &event_device, 0x01, BTN_LEFT, 0)?;
let _ev1 = mouse.emit_read_and_log(EV_KEY, BTN_LEFT, 1)?;
let _ev2 = mouse.emit_read_and_log(EV_KEY, BTN_LEFT, 0)?;
let eventlog = TestLog {
events: vec![ev1, ev2],
events: mouse.event_log().to_vec(),
};
let serialized = serde_json::to_string(&eventlog).unwrap();
println!("Event log: {}", serialized);
MouseDevice::destroy(fd);
MouseDevice::destroy(mouse);
Ok(())
}
}

View file

@ -5,7 +5,7 @@ use std::thread;
use std::time::Duration;
use crate::devices::ps4_gamepad::Ps4GamepadDevice;
use crate::devices::{utils, Device};
use crate::devices::{Device, EV_KEY};
use crate::scenarios::ScenarioArgs;
use crate::test_log::{LoggedInputEvent, TestLog};
@ -20,26 +20,21 @@ impl BasicPs4Gamepad {
.clone()
.unwrap_or_else(|| "/dev/uinput".to_string());
let fd = Ps4GamepadDevice::setup(Some(&device), "PS4 Gamepad")?;
let sysname = Ps4GamepadDevice::create(fd)?;
eprintln!("sysname: {}", sysname);
let mut gamepad = Ps4GamepadDevice::create(Some(&device), "PS4 Gamepad")?;
eprintln!("sysname: {}", gamepad.sysname());
thread::sleep(Duration::from_secs(1));
let event_device = std::fs::OpenOptions::new()
.read(true)
.open(&utils::fetch_device_node(&sysname)?)?;
let ev1 = utils::emit_read_and_log(fd, &event_device, 0x01, BTN_SOUTH, 1)?;
let ev2 = utils::emit_read_and_log(fd, &event_device, 0x01, BTN_SOUTH, 0)?;
let _ev1 = gamepad.emit_read_and_log(EV_KEY, BTN_SOUTH, 1)?;
let _ev2 = gamepad.emit_read_and_log(EV_KEY, BTN_SOUTH, 0)?;
let eventlog = TestLog {
events: vec![ev1, ev2],
events: gamepad.event_log().to_vec(),
};
let serialized = serde_json::to_string(&eventlog).unwrap();
println!("Event log: {}", serialized);
Ps4GamepadDevice::destroy(fd);
Ps4GamepadDevice::destroy(gamepad);
Ok(())
}
}

View file

@ -6,7 +6,7 @@ use std::thread;
use std::time::Duration;
use crate::devices::xbox_gamepad::XboxGamepadDevice;
use crate::devices::{utils, Device};
use crate::devices::{Device, EV_KEY};
use crate::scenarios::ScenarioArgs;
use crate::test_log::{LoggedInputEvent, TestLog};
@ -21,26 +21,21 @@ impl BasicXboxGamepad {
.clone()
.unwrap_or_else(|| "/dev/uinput".to_string());
let fd = XboxGamepadDevice::setup(Some(&device), "Xbox Gamepad")?;
let sysname = XboxGamepadDevice::create(fd)?;
eprintln!("sysname: {}", sysname);
let mut gamepad = XboxGamepadDevice::create(Some(&device), "Xbox Gamepad")?;
eprintln!("sysname: {}", gamepad.sysname());
thread::sleep(Duration::from_secs(1));
let event_device = std::fs::OpenOptions::new()
.read(true)
.open(&utils::fetch_device_node(&sysname)?)?;
let ev1 = utils::emit_read_and_log(fd, &event_device, 0x01, BTN_A, 1)?;
let ev2 = utils::emit_read_and_log(fd, &event_device, 0x01, BTN_A, 0)?;
let _ev1 = gamepad.emit_read_and_log(EV_KEY, BTN_A, 1)?;
let _ev2 = gamepad.emit_read_and_log(EV_KEY, BTN_A, 0)?;
let eventlog = TestLog {
events: vec![ev1, ev2],
events: gamepad.event_log().to_vec(),
};
let serialized = serde_json::to_string(&eventlog).unwrap();
println!("Event log: {}", serialized);
XboxGamepadDevice::destroy(fd);
XboxGamepadDevice::destroy(gamepad);
Ok(())
}
}

View file

@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use std::thread;
use std::time::Duration;
use crate::devices::xbox_gamepad::XboxGamepadDevice;
use crate::devices::{Device, EV_FF};
use crate::scenarios::ScenarioArgs;
use crate::test_log::{LoggedInputEvent, TestLog};
const BTN_A: u16 = 304;
pub struct FfXboxGamepad;
impl FfXboxGamepad {
pub fn run(args: &ScenarioArgs) -> Result<(), std::io::Error> {
let device = args
.dev_path
.clone()
.unwrap_or_else(|| "/dev/uinput".to_string());
let mut gamepad = XboxGamepadDevice::create(Some(&device), "Xbox Gamepad")?;
eprintln!("sysname: {}", gamepad.sysname());
thread::sleep(Duration::from_secs(1));
let effect = libc::ff_effect {
type_: todo!(),
id: todo!(),
direction: todo!(),
trigger: todo!(),
replay: todo!(),
u: todo!(),
};
let _ev_play_effect = gamepad.emit_read_and_log(EV_FF, effect.id.try_into().unwrap(), 3)?;
thread::sleep(Duration::from_secs(1));
let _ev_stop_effect = gamepad.emit_read_and_log(EV_FF, effect.id.try_into().unwrap(), 0)?;
let eventlog = TestLog {
events: gamepad.event_log().to_vec(),
};
let serialized = serde_json::to_string(&eventlog).unwrap();
println!("Event log: {}", serialized);
XboxGamepadDevice::destroy(gamepad);
Ok(())
}
}

View file

@ -6,6 +6,7 @@ pub mod basic_keyboard;
pub mod basic_mouse;
pub mod basic_ps4_gamepad;
pub mod basic_xbox_gamepad;
pub mod ff_xbox_gamepad;
/*
pub mod reuse_keyboard;
pub mod reuse_xbox_gamepad;
@ -18,6 +19,7 @@ pub use basic_keyboard::BasicKeyboard;
pub use basic_mouse::BasicMouse;
pub use basic_ps4_gamepad::BasicPs4Gamepad;
pub use basic_xbox_gamepad::BasicXboxGamepad;
pub use ff_xbox_gamepad::FfXboxGamepad;
/*
pub use reuse_keyboard::ReuseKeyboard;
pub use reuse_xbox_gamepad::ReuseXboxGamepad;

View file

@ -4,7 +4,7 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct LoggedInputEvent {
pub tv_sec: i64,