From e4eb1d77dab4cd9c660e332b3cabb3859ce307c1 Mon Sep 17 00:00:00 2001 From: Johannes Leupolz Date: Sun, 29 Mar 2026 22:26:12 +0000 Subject: [PATCH] Refactored test-scenarios.rs. Added stub for force feedback test. --- vuinputd-tests/src/bin/test-scenarios.rs | 11 +- vuinputd-tests/src/devices/device_base.rs | 262 ++++++++++++++++++ vuinputd-tests/src/devices/keyboard.rs | 117 ++++---- vuinputd-tests/src/devices/mod.rs | 30 +- vuinputd-tests/src/devices/mouse.rs | 114 ++++---- vuinputd-tests/src/devices/ps4_gamepad.rs | 114 ++++---- vuinputd-tests/src/devices/utils.rs | 128 +-------- vuinputd-tests/src/devices/xbox_gamepad.rs | 205 +++++++++----- vuinputd-tests/src/lib.rs | 4 +- .../src/scenarios/basic_keyboard.rs | 19 +- vuinputd-tests/src/scenarios/basic_mouse.rs | 19 +- .../src/scenarios/basic_ps4_gamepad.rs | 19 +- .../src/scenarios/basic_xbox_gamepad.rs | 19 +- .../src/scenarios/ff_xbox_gamepad.rs | 51 ++++ vuinputd-tests/src/scenarios/mod.rs | 2 + vuinputd-tests/src/test_log.rs | 2 +- 16 files changed, 678 insertions(+), 438 deletions(-) create mode 100644 vuinputd-tests/src/devices/device_base.rs create mode 100644 vuinputd-tests/src/scenarios/ff_xbox_gamepad.rs diff --git a/vuinputd-tests/src/bin/test-scenarios.rs b/vuinputd-tests/src/bin/test-scenarios.rs index 6b1b145..39402d7 100644 --- a/vuinputd-tests/src/bin/test-scenarios.rs +++ b/vuinputd-tests/src/bin/test-scenarios.rs @@ -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), diff --git a/vuinputd-tests/src/devices/device_base.rs b/vuinputd-tests/src/devices/device_base.rs new file mode 100644 index 0000000..314bda3 --- /dev/null +++ b/vuinputd-tests/src/devices/device_base.rs @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: MIT +// +// Author: Johannes Leupolz + +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, +} + +/// 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; + + /// 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; + + /// 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 { + 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 { + 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 { + &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 { + 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::(); + + 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 { + 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 { + 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::(), + ) + }; + if ret as usize != size_of::() { + 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 { + 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 { + 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")) +} diff --git a/vuinputd-tests/src/devices/keyboard.rs b/vuinputd-tests/src/devices/keyboard.rs index 2dd1376..49b7c1d 100644 --- a/vuinputd-tests/src/devices/keyboard.rs +++ b/vuinputd-tests/src/devices/keyboard.rs @@ -2,9 +2,11 @@ // // Author: Johannes Leupolz -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 { - 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 { - 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 { + fn get_event_device(&self) -> Result { + Ok(self.state.event_device_fd) + } + + fn create(device: Option<&str>, name: &str) -> Result { + 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; diff --git a/vuinputd-tests/src/devices/mod.rs b/vuinputd-tests/src/devices/mod.rs index 5d8fd9b..c0e7cbe 100644 --- a/vuinputd-tests/src/devices/mod.rs +++ b/vuinputd-tests/src/devices/mod.rs @@ -2,38 +2,20 @@ // // Author: Johannes Leupolz -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; - - /// Phase 1: open uinput, configure keys, call ui_dev_setup - fn setup(device: Option<&str>, name: &str) -> Result; - - /// Phase 2: call ui_dev_create, get sysname and devnode - fn create(fd: i32) -> Result; - - /// 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}; diff --git a/vuinputd-tests/src/devices/mouse.rs b/vuinputd-tests/src/devices/mouse.rs index 352e792..60a1ed4 100644 --- a/vuinputd-tests/src/devices/mouse.rs +++ b/vuinputd-tests/src/devices/mouse.rs @@ -2,10 +2,9 @@ // // Author: Johannes Leupolz -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 { - 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 { - 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 { + fn get_event_device(&self) -> Result { + Ok(self.state.event_device_fd) + } + + fn create(device: Option<&str>, name: &str) -> Result { + 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; diff --git a/vuinputd-tests/src/devices/ps4_gamepad.rs b/vuinputd-tests/src/devices/ps4_gamepad.rs index da549bc..11f02b0 100644 --- a/vuinputd-tests/src/devices/ps4_gamepad.rs +++ b/vuinputd-tests/src/devices/ps4_gamepad.rs @@ -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 { - 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 { - 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 { + fn get_event_device(&self) -> Result { + Ok(self.state.event_device_fd) + } + + fn create(device: Option<&str>, name: &str) -> Result { + 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; \ No newline at end of file diff --git a/vuinputd-tests/src/devices/utils.rs b/vuinputd-tests/src/devices/utils.rs index ff4fdec..5fe5b26 100644 --- a/vuinputd-tests/src/devices/utils.rs +++ b/vuinputd-tests/src/devices/utils.rs @@ -2,6 +2,7 @@ // // Author: Johannes Leupolz +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::(); - - //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 { - 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 { - 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 { - 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::(), - ) - }; - if ret as usize != mem::size_of::() { - 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 { - 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}; diff --git a/vuinputd-tests/src/devices/xbox_gamepad.rs b/vuinputd-tests/src/devices/xbox_gamepad.rs index 0caaf57..19ba583 100644 --- a/vuinputd-tests/src/devices/xbox_gamepad.rs +++ b/vuinputd-tests/src/devices/xbox_gamepad.rs @@ -2,10 +2,9 @@ // // Author: Johannes Leupolz -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 { - 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 { - 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 { + fn get_event_device(&self) -> Result { + Ok(self.state.event_device_fd) + } + + fn create(device: Option<&str>, name: &str) -> Result { + 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; diff --git a/vuinputd-tests/src/lib.rs b/vuinputd-tests/src/lib.rs index c3afdc5..0e1a7af 100644 --- a/vuinputd-tests/src/lib.rs +++ b/vuinputd-tests/src/lib.rs @@ -2,10 +2,10 @@ // // Author: Johannes Leupolz -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; diff --git a/vuinputd-tests/src/scenarios/basic_keyboard.rs b/vuinputd-tests/src/scenarios/basic_keyboard.rs index 2abfd85..2641187 100644 --- a/vuinputd-tests/src/scenarios/basic_keyboard.rs +++ b/vuinputd-tests/src/scenarios/basic_keyboard.rs @@ -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(()) } } diff --git a/vuinputd-tests/src/scenarios/basic_mouse.rs b/vuinputd-tests/src/scenarios/basic_mouse.rs index be23d64..5643451 100644 --- a/vuinputd-tests/src/scenarios/basic_mouse.rs +++ b/vuinputd-tests/src/scenarios/basic_mouse.rs @@ -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(()) } } diff --git a/vuinputd-tests/src/scenarios/basic_ps4_gamepad.rs b/vuinputd-tests/src/scenarios/basic_ps4_gamepad.rs index 3224b76..e7e7ee5 100644 --- a/vuinputd-tests/src/scenarios/basic_ps4_gamepad.rs +++ b/vuinputd-tests/src/scenarios/basic_ps4_gamepad.rs @@ -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(()) } } diff --git a/vuinputd-tests/src/scenarios/basic_xbox_gamepad.rs b/vuinputd-tests/src/scenarios/basic_xbox_gamepad.rs index d7fbb74..48cd156 100644 --- a/vuinputd-tests/src/scenarios/basic_xbox_gamepad.rs +++ b/vuinputd-tests/src/scenarios/basic_xbox_gamepad.rs @@ -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(()) } } diff --git a/vuinputd-tests/src/scenarios/ff_xbox_gamepad.rs b/vuinputd-tests/src/scenarios/ff_xbox_gamepad.rs new file mode 100644 index 0000000..de255ad --- /dev/null +++ b/vuinputd-tests/src/scenarios/ff_xbox_gamepad.rs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +// +// Author: Johannes Leupolz + +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(()) + } +} diff --git a/vuinputd-tests/src/scenarios/mod.rs b/vuinputd-tests/src/scenarios/mod.rs index 1e82bd8..1d3d2f0 100644 --- a/vuinputd-tests/src/scenarios/mod.rs +++ b/vuinputd-tests/src/scenarios/mod.rs @@ -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; diff --git a/vuinputd-tests/src/test_log.rs b/vuinputd-tests/src/test_log.rs index 80e9fc2..940fd0d 100644 --- a/vuinputd-tests/src/test_log.rs +++ b/vuinputd-tests/src/test_log.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct LoggedInputEvent { pub tv_sec: i64,