First draft of the test tool test-scenarios that should make it easier to test new scenarios in case we have an issue.

This commit is contained in:
Johannes Leupolz 2026-03-26 21:19:40 +00:00
parent b5889e6189
commit 9070c4e836
13 changed files with 916 additions and 0 deletions

View file

@ -12,6 +12,9 @@ name = "test-keyboard"
[[bin]]
name = "test-ok"
[[bin]]
name = "test-scenarios"
[dependencies]
uinput-ioctls = { path = "../uinput-ioctls" }
nix = { version = "0.30", features = ["ioctl","socket","signal"] } # ioctl & libc bindings
@ -24,6 +27,7 @@ serde_json = "1.0"
[features]
requires-privileges = []
requires-rootless = []
requires-uinput = []
requires-bwrap = []
requires-podman = []

View file

@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use clap::{Parser, Subcommand};
use vuinputd_tests::scenarios::{
ScenarioArgs, 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, */
};
#[derive(Parser)]
#[command(name = "test-scenarios")]
#[command(about = "Test scenarios for vuinputd", long_about = None)]
struct Cli {
/// Run scenarios in IPC mode (communicate with vuinputd daemon)
#[arg(short, long, default_value_t = false)]
ipc: bool,
/// Path to uinput device
#[arg(short, long, default_value = "/dev/uinput")]
dev_path: String,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
/// Basic keyboard test
BasicKeyboard,
/// Basic mouse test
BasicMouse,
/// Basic PS4 gamepad test
BasicPs4Gamepad,
/// Basic Xbox gamepad test
BasicXboxGamepad,
/*
/// Reuse keyboard test (create, destroy, recreate)
ReuseKeyboard,
/// Reuse Xbox gamepad test (create, destroy, recreate)
ReuseXboxGamepad,
/// Stress test for keyboard (3000 events)
StressKeyboard,
/// Stress test for Xbox gamepad (3000 events)
StressXboxGamepad,
*/
}
fn main() -> Result<(), std::io::Error> {
let cli = Cli::parse();
let args = ScenarioArgs {
ipc: cli.ipc,
dev_path: Some(cli.dev_path),
};
match cli.command {
Commands::BasicKeyboard => BasicKeyboard::run(&args),
Commands::BasicMouse => BasicMouse::run(&args),
Commands::BasicPs4Gamepad => BasicPs4Gamepad::run(&args),
Commands::BasicXboxGamepad => BasicXboxGamepad::run(&args),
/*
Commands::ReuseKeyboard => ReuseKeyboard::run(&args),
Commands::ReuseXboxGamepad => ReuseXboxGamepad::run(&args),
Commands::StressKeyboard => StressKeyboard::run(&args),
Commands::StressXboxGamepad => StressXboxGamepad::run(&args),
*/
}
}

View file

@ -0,0 +1,342 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use super::Device;
use std::{ffi::CStr, fs::File};
use std::io;
use uinput_ioctls::*;
/// Key codes. Those are used by udev to recognize a device as a keyboard.
pub const KEY_ESC: u16 = 1;
pub const KEY_1: u16 = 2;
pub const KEY_2: u16 = 3;
pub const KEY_3: u16 = 4;
pub const KEY_4: u16 = 5;
pub const KEY_5: u16 = 6;
pub const KEY_6: u16 = 7;
pub const KEY_7: u16 = 8;
pub const KEY_8: u16 = 9;
pub const KEY_9: u16 = 10;
pub const KEY_0: u16 = 11;
pub const KEY_MINUS: u16 = 12;
pub const KEY_EQUAL: u16 = 13;
pub const KEY_BACKSPACE: u16 = 14;
pub const KEY_TAB: u16 = 15;
pub const KEY_Q: u16 = 16;
pub const KEY_W: u16 = 17;
pub const KEY_E: u16 = 18;
pub const KEY_R: u16 = 19;
pub const KEY_T: u16 = 20;
pub const KEY_Y: u16 = 21;
pub const KEY_U: u16 = 22;
pub const KEY_I: u16 = 23;
pub const KEY_O: u16 = 24;
pub const KEY_P: u16 = 25;
pub const KEY_LEFTBRACE: u16 = 26;
pub const KEY_RIGHTBRACE: u16 = 27;
pub const KEY_ENTER: u16 = 28;
pub const KEY_LEFTCTRL: u16 = 29;
pub const KEY_A: u16 = 30;
pub const KEY_S: u16 = 31;
/// Space and other common keys
pub const KEY_D: u16 = 32;
pub const KEY_F: u16 = 33;
pub const KEY_G: u16 = 34;
pub const KEY_H: u16 = 35;
pub const KEY_J: u16 = 36;
pub const KEY_K: u16 = 37;
pub const KEY_L: u16 = 38;
pub const KEY_SEMICOLON: u16 = 39;
pub const KEY_APOSTROPHE: u16 = 40;
pub const KEY_GRAVE: u16 = 41;
pub const KEY_LEFTSHIFT: u16 = 42;
pub const KEY_BACKSLASH: u16 = 43;
pub const KEY_Z: u16 = 44;
pub const KEY_X: u16 = 45;
pub const KEY_C: u16 = 46;
pub const KEY_V: u16 = 47;
pub const KEY_B: u16 = 48;
pub const KEY_N: u16 = 49;
pub const KEY_M: u16 = 50;
pub const KEY_COMMA: u16 = 51;
pub const KEY_DOT: u16 = 52;
pub const KEY_SLASH: u16 = 53;
pub const KEY_RIGHTSHIFT: u16 = 54;
pub const KEY_KPASTERISK: u16 = 55;
pub const KEY_LEFTALT: u16 = 56;
pub const KEY_SPACE: u16 = 57;
pub const KEY_CAPSLOCK: u16 = 58;
/// Function keys
pub const KEY_F1: u16 = 59;
pub const KEY_F2: u16 = 60;
pub const KEY_F3: u16 = 61;
pub const KEY_F4: u16 = 62;
pub const KEY_F5: u16 = 63;
pub const KEY_F6: u16 = 64;
pub const KEY_F7: u16 = 65;
pub const KEY_F8: u16 = 66;
pub const KEY_F9: u16 = 67;
pub const KEY_F10: u16 = 68;
pub const KEY_NUMLOCK: u16 = 69;
pub const KEY_SCROLLLOCK: u16 = 70;
pub const KEY_KP7: u16 = 71;
pub const KEY_KP8: u16 = 72;
pub const KEY_KP9: u16 = 73;
pub const KEY_KPMINUS: u16 = 74;
pub const KEY_KP4: u16 = 75;
pub const KEY_KP5: u16 = 76;
pub const KEY_KP6: u16 = 77;
pub const KEY_KPPLUS: u16 = 78;
pub const KEY_KP1: u16 = 79;
pub const KEY_KP2: u16 = 80;
pub const KEY_KP3: u16 = 81;
pub const KEY_KP0: u16 = 82;
pub const KEY_KPDOT: u16 = 83;
/// Arrow keys and navigation
pub const KEY_ZENKAKUHANKAKU: u16 = 85;
pub const KEY_102ND: u16 = 86;
pub const KEY_F11: u16 = 87;
pub const KEY_F12: u16 = 88;
pub const KEY_RO: u16 = 89;
pub const KEY_KATAKANA: u16 = 90;
pub const KEY_HIRAGANA: u16 = 91;
pub const KEY_HENKAN: u16 = 92;
pub const KEY_KATAKANAHIRAGANA: u16 = 93;
pub const KEY_MUHENKAN: u16 = 94;
pub const KEY_KPJPCOMMA: u16 = 95;
pub const KEY_KPENTER: u16 = 96;
pub const KEY_RIGHTCTRL: u16 = 97;
pub const KEY_KPSLASH: u16 = 98;
pub const KEY_SYSRQ: u16 = 99;
pub const KEY_RIGHTALT: u16 = 100;
pub const KEY_LINEFEED: u16 = 101;
pub const KEY_HOME: u16 = 102;
pub const KEY_UP: u16 = 103;
pub const KEY_PAGEUP: u16 = 104;
pub const KEY_LEFT: u16 = 105;
pub const KEY_RIGHT: u16 = 106;
pub const KEY_END: u16 = 107;
pub const KEY_DOWN: u16 = 108;
pub const KEY_PAGEDOWN: u16 = 109;
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> {
// 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
// EV_KEY
ui_set_evbit(fd, super::EV_KEY.try_into().unwrap())?;
// All standard keys (1..101+)
let all_keys = [
// Modifier + main keys
KEY_ESC,
KEY_1,
KEY_2,
KEY_3,
KEY_4,
KEY_5,
KEY_6,
KEY_7,
KEY_8,
KEY_9,
KEY_0,
KEY_MINUS,
KEY_EQUAL,
KEY_BACKSPACE,
KEY_TAB,
KEY_Q,
KEY_W,
KEY_E,
KEY_R,
KEY_T,
KEY_Y,
KEY_U,
KEY_I,
KEY_O,
KEY_P,
KEY_LEFTBRACE,
KEY_RIGHTBRACE,
KEY_ENTER,
KEY_LEFTCTRL,
KEY_A,
KEY_S,
KEY_D,
KEY_F,
KEY_G,
KEY_H,
KEY_J,
KEY_K,
KEY_L,
KEY_SEMICOLON,
KEY_APOSTROPHE,
KEY_GRAVE,
KEY_LEFTSHIFT,
KEY_BACKSLASH,
KEY_Z,
KEY_X,
KEY_C,
KEY_V,
KEY_B,
KEY_N,
KEY_M,
KEY_COMMA,
KEY_DOT,
KEY_SLASH,
KEY_RIGHTSHIFT,
KEY_KPASTERISK,
KEY_LEFTALT,
KEY_SPACE,
KEY_CAPSLOCK,
// Function keys
KEY_F1,
KEY_F2,
KEY_F3,
KEY_F4,
KEY_F5,
KEY_F6,
KEY_F7,
KEY_F8,
KEY_F9,
KEY_F10,
KEY_F11,
KEY_F12,
KEY_NUMLOCK,
KEY_SCROLLLOCK,
// Keypad
KEY_KP7,
KEY_KP8,
KEY_KP9,
KEY_KPMINUS,
KEY_KP4,
KEY_KP5,
KEY_KP6,
KEY_KPPLUS,
KEY_KP1,
KEY_KP2,
KEY_KP3,
KEY_KP0,
KEY_KPDOT,
KEY_KPENTER,
KEY_KPSLASH,
KEY_KPJPCOMMA,
// Arrows / navigation
KEY_HOME,
KEY_UP,
KEY_PAGEUP,
KEY_LEFT,
KEY_RIGHT,
KEY_END,
KEY_DOWN,
KEY_PAGEDOWN,
KEY_INSERT,
KEY_DELETE,
KEY_RIGHTCTRL,
KEY_RIGHTALT,
// Optional Japanese / additional keys
KEY_ZENKAKUHANKAKU,
KEY_102ND,
KEY_RO,
KEY_KATAKANA,
KEY_HIRAGANA,
KEY_HENKAN,
KEY_KATAKANAHIRAGANA,
KEY_MUHENKAN,
KEY_LINEFEED,
KEY_SYSRQ,
];
for &key in all_keys.iter() {
ui_set_keybit(fd, key.try_into().unwrap())?;
}
Ok(())
}
pub struct KeyboardDevice;
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 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 create(fd: i32) -> Result<String, io::Error> {
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)
}
}
fn destroy(fd: i32) {
unsafe {
ui_dev_destroy(fd).unwrap_or_else(|e| {
eprintln!("ui_dev_destroy failed: {:?}", e);
std::process::exit(1);
});
close(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

@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use std::fs::File;
use std::io;
pub mod keyboard;
pub mod mouse;
pub mod ps4_gamepad;
pub mod xbox_gamepad;
pub mod utils;
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;
/// 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);
}

View file

@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use super::{ Device};
use std::{ffi::CStr, fs::File};
use std::io;
use libc::c_int;
use uinput_ioctls::*;
// Mouse codes
pub const BTN_LEFT: u16 = 272;
pub const BTN_RIGHT: u16 = 273;
pub const BTN_MIDDLE: u16 = 274;
pub const REL_X: u16 = 0;
pub const REL_Y: u16 = 1;
/// Setup mouse device
unsafe fn setup_mouse(fd: c_int) -> io::Result<()> {
// EV_SYN
ui_set_evbit(fd, super::EV_SYN.try_into().unwrap())?;
// EV_KEY
ui_set_evbit(fd, super::EV_KEY.try_into().unwrap())?;
ui_set_keybit(fd, BTN_LEFT.try_into().unwrap())?;
ui_set_keybit(fd, BTN_RIGHT.try_into().unwrap())?;
ui_set_keybit(fd, BTN_MIDDLE.try_into().unwrap())?;
// EV_REL
ui_set_evbit(fd, super::EV_REL.try_into().unwrap())?;
ui_set_relbit(fd, REL_X.try_into().unwrap())?;
ui_set_relbit(fd, REL_Y.try_into().unwrap())?;
Ok(())
}
pub struct MouseDevice;
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 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 create(fd: i32) -> Result<String, io::Error> {
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)
}
}
fn destroy(fd: i32) {
unsafe {
ui_dev_destroy(fd).unwrap_or_else(|e| {
eprintln!("ui_dev_destroy failed: {:?}", e);
std::process::exit(1);
});
close(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

@ -0,0 +1,143 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
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::{self, File, OpenOptions};
use std::io::{self, ErrorKind};
use std::mem::{self, size_of, zeroed};
use std::os::fd::AsRawFd;
use std::os::raw::{c_char, c_void};
use std::ptr;
pub use uinput_ioctls::*;
use crate::test_log::{LoggedInputEvent, TestLog};
// 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)
}

View file

@ -2,6 +2,8 @@
//
// Author: Johannes Leupolz <dev@leupolz.eu>
// TODO: Use https://varlink.org/ which also supports bridges over ssh, which is nice
use std::{
io,
os::{

View file

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

View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use std::thread;
use std::time::Duration;
use crate::devices::keyboard::KeyboardDevice;
use crate::scenarios::ScenarioArgs;
use crate::devices::{Device, utils};
use crate::test_log::{TestLog};
const KEY_SPACE: u16 = 57;
pub struct BasicKeyboard;
impl BasicKeyboard {
pub fn run(args: &ScenarioArgs) -> Result<(), std::io::Error> {
let device = args.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);
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 eventlog = TestLog { events: vec![ev1, ev2] };
let serialized = serde_json::to_string(&eventlog).unwrap();
println!("Event log: {}", serialized);
KeyboardDevice::destroy(fd);
Ok(())
}
}

View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use std::thread;
use std::time::Duration;
use crate::devices::mouse::MouseDevice;
use crate::scenarios::ScenarioArgs;
use crate::devices::{Device, utils};
use crate::test_log::{LoggedInputEvent, TestLog};
const BTN_LEFT: u16 = 272;
pub struct BasicMouse;
impl BasicMouse {
pub fn run(args: &ScenarioArgs) -> Result<(), std::io::Error> {
let device = args.dev_path.clone().unwrap_or_else(|| "/dev/uinput".to_string());
let fd = MouseDevice::setup(Some(&device), "Example Mouse")?;
let sysname = MouseDevice::create(fd)?;
eprintln!("sysname: {}", 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 eventlog = TestLog { events: vec![ev1, ev2] };
let serialized = serde_json::to_string(&eventlog).unwrap();
println!("Event log: {}", serialized);
MouseDevice::destroy(fd);
Ok(())
}
}

View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
use std::thread;
use std::time::Duration;
use crate::devices::ps4_gamepad::Ps4GamepadDevice;
use crate::scenarios::ScenarioArgs;
use crate::devices::{Device, utils};
use crate::test_log::{LoggedInputEvent, TestLog};
const BTN_SOUTH: u16 = 304;
pub struct BasicPs4Gamepad;
impl BasicPs4Gamepad {
pub fn run(args: &ScenarioArgs) -> Result<(), std::io::Error> {
let device = args.dev_path.clone().unwrap_or_else(|| "/dev/uinput".to_string());
let fd = Ps4GamepadDevice::setup(Some(&device), "PS4 Gamepad")?;
let sysname = Ps4GamepadDevice::create(fd)?;
eprintln!("sysname: {}", 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 eventlog = TestLog { events: vec![ev1, ev2] };
let serialized = serde_json::to_string(&eventlog).unwrap();
println!("Event log: {}", serialized);
Ps4GamepadDevice::destroy(fd);
Ok(())
}
}

View file

@ -0,0 +1,41 @@
// 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::scenarios::ScenarioArgs;
use crate::devices::{Device, utils};
use crate::test_log::{LoggedInputEvent, TestLog};
const BTN_A: u16 = 304;
pub struct BasicXboxGamepad;
impl BasicXboxGamepad {
pub fn run(args: &ScenarioArgs) -> Result<(), std::io::Error> {
let device = args.dev_path.clone().unwrap_or_else(|| "/dev/uinput".to_string());
let fd = XboxGamepadDevice::setup(Some(&device), "Xbox Gamepad")?;
let sysname = XboxGamepadDevice::create(fd)?;
eprintln!("sysname: {}", 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 eventlog = TestLog { events: vec![ev1, ev2] };
let serialized = serde_json::to_string(&eventlog).unwrap();
println!("Event log: {}", serialized);
XboxGamepadDevice::destroy(fd);
Ok(())
}
}

View file

@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
//
// Author: Johannes Leupolz <dev@leupolz.eu>
pub mod basic_keyboard;
pub mod basic_mouse;
pub mod basic_ps4_gamepad;
pub mod basic_xbox_gamepad;
/*
pub mod reuse_keyboard;
pub mod reuse_xbox_gamepad;
pub mod stress_keyboard;
pub mod stress_xbox_gamepad;
*/
// Re-exports for type checking
pub use basic_keyboard::BasicKeyboard;
pub use basic_mouse::BasicMouse;
pub use basic_ps4_gamepad::BasicPs4Gamepad;
pub use basic_xbox_gamepad::BasicXboxGamepad;
/*
pub use reuse_keyboard::ReuseKeyboard;
pub use reuse_xbox_gamepad::ReuseXboxGamepad;
pub use stress_keyboard::StressKeyboard;
pub use stress_xbox_gamepad::StressXboxGamepad;
*/
/// Common scenario arguments passed from CLI
#[derive(Debug, Clone)]
pub struct ScenarioArgs {
pub ipc: bool,
pub dev_path: Option<String>,
}