mirror of
https://github.com/joleuger/vuinputd.git
synced 2026-07-17 16:36:03 +00:00
Refactored test-scenarios.rs. Added stub for force feedback test.
This commit is contained in:
parent
7182cac6fc
commit
e4eb1d77da
16 changed files with 678 additions and 438 deletions
|
|
@ -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),
|
||||
|
|
|
|||
262
vuinputd-tests/src/devices/device_base.rs
Normal file
262
vuinputd-tests/src/devices/device_base.rs
Normal 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"))
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
51
vuinputd-tests/src/scenarios/ff_xbox_gamepad.rs
Normal file
51
vuinputd-tests/src/scenarios/ff_xbox_gamepad.rs
Normal 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(())
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct LoggedInputEvent {
|
||||
pub tv_sec: i64,
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue