use aethermap_common::tracing;
use std::fs::OpenOptions;
use std::mem;
use std::os::unix::io::{AsRawFd, RawFd};
use std::sync::{Arc, RwLock};
use tracing::{debug, error, info, warn};
const EV_SYN: u16 = 0x00;
const EV_ABS: u16 = 0x03;
const SYN_REPORT: u16 = 0x00;
pub const ABS_X: u16 = 0x00;
pub const ABS_Y: u16 = 0x01;
pub const ABS_Z: u16 = 0x02;
pub const ABS_RX: u16 = 0x03;
pub const ABS_RY: u16 = 0x04;
pub const ABS_RZ: u16 = 0x05;
const UINPUT_IOCTL_BASE: u8 = b'U';
const UI_SET_EVBIT: u64 = 0x40045564; const UI_SET_ABSBIT: u64 = 0x40045567; const UI_DEV_CREATE: u64 = 0x5501; const UI_DEV_DESTROY: u64 = 0x5502;
const BUS_USB: u16 = 0x03;
const XBOX360_VENDOR_ID: u16 = 0x045e; const XBOX360_PRODUCT_ID: u16 = 0x028e; const XBOX360_VERSION: u16 = 0x0110;
const DEFAULT_DEVICE_NAME: &str = "Aethermap Virtual Gamepad";
const AXIS_MIN: i32 = -32768;
const AXIS_MAX: i32 = 32767;
const AXIS_FUZZ: i32 = 0;
const AXIS_FLAT: i32 = 0;
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct InputEvent {
time: libc::timeval,
type_: u16,
code: u16,
value: i32,
}
#[repr(C)]
#[allow(dead_code)]
struct UinputUserDev {
name: [u8; 80],
id: InputId,
ff_effects_max: u32,
absmax: [i32; 64],
absmin: [i32; 64],
absfuzz: [i32; 64],
absflat: [i32; 64],
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct InputId {
bustype: u16,
vendor: u16,
product: u16,
version: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u16)]
pub enum GamepadAxis {
ABS_X = 0,
ABS_Y = 1,
ABS_Z = 2,
ABS_RX = 3,
ABS_RY = 4,
ABS_RZ = 5,
}
impl GamepadAxis {
pub fn code(self) -> u16 {
self as u16
}
pub fn index(self) -> usize {
self as usize
}
}
#[derive(Clone)]
pub struct GamepadVirtualDevice {
uinput_fd: Arc<RwLock<Option<RawFd>>>,
device_name: String,
vendor_id: u16,
product_id: u16,
version: u16,
}
impl Default for GamepadVirtualDevice {
fn default() -> Self {
Self::new()
}
}
impl GamepadVirtualDevice {
pub fn new() -> Self {
info!("Creating new GamepadVirtualDevice with Xbox 360 identifiers");
Self {
uinput_fd: Arc::new(RwLock::new(None)),
device_name: DEFAULT_DEVICE_NAME.to_string(),
vendor_id: XBOX360_VENDOR_ID,
product_id: XBOX360_PRODUCT_ID,
version: XBOX360_VERSION,
}
}
pub fn with_ids(name: &str, vendor_id: u16, product_id: u16) -> Self {
let truncated_name = if name.len() > 79 {
warn!("Device name too long, truncating to 79 characters");
&name[..79]
} else {
name
};
info!(
"Creating GamepadVirtualDevice: name={}, vendor={:04x}, product={:04x}",
truncated_name, vendor_id, product_id
);
Self {
uinput_fd: Arc::new(RwLock::new(None)),
device_name: truncated_name.to_string(),
vendor_id,
product_id,
version: XBOX360_VERSION,
}
}
pub fn is_created(&self) -> bool {
self.uinput_fd
.try_read()
.map(|fd| fd.is_some())
.unwrap_or(false)
}
pub fn create(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if self.is_created() {
warn!("Gamepad virtual device already created");
return Ok(());
}
info!(
"Creating virtual gamepad device: {} ({:04x}:{:04x})",
self.device_name, self.vendor_id, self.product_id
);
let uinput_file = OpenOptions::new()
.write(true)
.open("/dev/uinput")
.map_err(|e| {
error!(
"Failed to open /dev/uinput: {}. Ensure you have root privileges and uinput module is loaded.",
e
);
format!("Failed to open /dev/uinput: {}", e)
})?;
let fd = uinput_file.as_raw_fd();
mem::forget(uinput_file);
unsafe {
if libc::ioctl(fd, UI_SET_EVBIT, EV_ABS as libc::c_int) < 0 {
return Err("Failed to set EV_ABS bit".into());
}
if libc::ioctl(fd, UI_SET_EVBIT, EV_SYN as libc::c_int) < 0 {
return Err("Failed to set EV_SYN bit".into());
}
if libc::ioctl(fd, UI_SET_ABSBIT, ABS_X as libc::c_int) < 0 {
return Err("Failed to set ABS_X bit".into());
}
if libc::ioctl(fd, UI_SET_ABSBIT, ABS_Y as libc::c_int) < 0 {
return Err("Failed to set ABS_Y bit".into());
}
for axis in [ABS_Z, ABS_RX, ABS_RY, ABS_RZ] {
if libc::ioctl(fd, UI_SET_ABSBIT, axis as libc::c_int) < 0 {
warn!("Failed to set ABS bit for axis {}", axis);
}
}
}
let mut dev: UinputUserDev = unsafe { mem::zeroed() };
let name_bytes = self.device_name.as_bytes();
let name_len = name_bytes.len().min(79);
dev.name[..name_len].copy_from_slice(&name_bytes[..name_len]);
dev.id.bustype = BUS_USB;
dev.id.vendor = self.vendor_id;
dev.id.product = self.product_id;
dev.id.version = self.version;
for axis in [ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ] {
let idx = axis as usize;
dev.absmin[idx] = AXIS_MIN;
dev.absmax[idx] = AXIS_MAX;
dev.absfuzz[idx] = AXIS_FUZZ;
dev.absflat[idx] = AXIS_FLAT;
}
unsafe {
let dev_ptr = &dev as *const UinputUserDev as *const u8;
let dev_slice = std::slice::from_raw_parts(dev_ptr, mem::size_of::<UinputUserDev>());
if libc::write(fd, dev_slice.as_ptr() as *const libc::c_void, dev_slice.len()) < 0 {
return Err("Failed to write uinput device structure".into());
}
if libc::ioctl(fd, UI_DEV_CREATE) < 0 {
return Err("Failed to create uinput device".into());
}
}
info!(
"Successfully created virtual gamepad: {} ({:04x}:{:04x})",
self.device_name, self.vendor_id, self.product_id
);
{
let mut uinput_fd = self.uinput_fd.try_write()
.map_err(|_| "Lock poisoned on uinput_fd write")?;
*uinput_fd = Some(fd);
}
Ok(())
}
pub fn emit_axis(
&self,
axis: GamepadAxis,
value: i32,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let fd = {
let uinput_fd = self
.uinput_fd
.try_read()
.map_err(|_| "Lock poisoned on uinput_fd read")?;
uinput_fd.ok_or("Gamepad virtual device not created")?
};
debug!("Emitting axis: {:?} = {}", axis, value);
let mut event: InputEvent = unsafe { mem::zeroed() };
unsafe {
libc::gettimeofday(&mut event.time, std::ptr::null_mut());
}
event.type_ = EV_ABS;
event.code = axis.code();
event.value = value;
unsafe {
let event_ptr = &event as *const InputEvent as *const u8;
let event_slice = std::slice::from_raw_parts(event_ptr, mem::size_of::<InputEvent>());
let written = libc::write(fd, event_slice.as_ptr() as *const libc::c_void, event_slice.len());
if written < 0 {
return Err(format!("Failed to write axis event: {}", std::io::Error::last_os_error()).into());
}
}
self.sync()?;
Ok(())
}
fn sync(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let fd = {
let uinput_fd = self
.uinput_fd
.try_read()
.map_err(|_| "Lock poisoned on uinput_fd read")?;
uinput_fd.ok_or("Gamepad virtual device not created")?
};
let mut event: InputEvent = unsafe { mem::zeroed() };
unsafe {
libc::gettimeofday(&mut event.time, std::ptr::null_mut());
}
event.type_ = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
unsafe {
let event_ptr = &event as *const InputEvent as *const u8;
let event_slice = std::slice::from_raw_parts(event_ptr, mem::size_of::<InputEvent>());
let written = libc::write(fd, event_slice.as_ptr() as *const libc::c_void, event_slice.len());
if written < 0 {
return Err(format!("Failed to write sync event: {}", std::io::Error::last_os_error()).into());
}
}
Ok(())
}
pub fn vendor_id(&self) -> u16 {
self.vendor_id
}
pub fn product_id(&self) -> u16 {
self.product_id
}
pub fn device_name(&self) -> &str {
&self.device_name
}
pub fn destroy(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let fd = {
let mut uinput_fd = self.uinput_fd.try_write()
.map_err(|_| "Lock poisoned on uinput_fd write")?;
uinput_fd.take()
};
if let Some(fd) = fd {
info!("Destroying virtual gamepad device: {}", self.device_name);
unsafe {
libc::ioctl(fd, UI_DEV_DESTROY);
libc::close(fd);
}
}
Ok(())
}
}
impl Drop for GamepadVirtualDevice {
fn drop(&mut self) {
if let Ok(fd_guard) = self.uinput_fd.try_read() {
if let Some(fd) = *fd_guard {
info!("Auto-destroying virtual gamepad device: {}", self.device_name);
unsafe {
libc::ioctl(fd, UI_DEV_DESTROY);
libc::close(fd);
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gamepad_axis_codes() {
assert_eq!(GamepadAxis::ABS_X.code(), 0);
assert_eq!(GamepadAxis::ABS_Y.code(), 1);
assert_eq!(GamepadAxis::ABS_Z.code(), 2);
assert_eq!(GamepadAxis::ABS_RX.code(), 3);
assert_eq!(GamepadAxis::ABS_RY.code(), 4);
assert_eq!(GamepadAxis::ABS_RZ.code(), 5);
}
#[test]
fn test_gamepad_axis_indices() {
assert_eq!(GamepadAxis::ABS_X.index(), 0);
assert_eq!(GamepadAxis::ABS_Y.index(), 1);
assert_eq!(GamepadAxis::ABS_Z.index(), 2);
assert_eq!(GamepadAxis::ABS_RX.index(), 3);
assert_eq!(GamepadAxis::ABS_RY.index(), 4);
assert_eq!(GamepadAxis::ABS_RZ.index(), 5);
}
#[test]
fn test_virtual_device_creation() {
let device = GamepadVirtualDevice::new();
assert!(!device.is_created());
assert_eq!(device.vendor_id, XBOX360_VENDOR_ID);
assert_eq!(device.product_id, XBOX360_PRODUCT_ID);
assert_eq!(device.device_name, DEFAULT_DEVICE_NAME);
}
#[test]
fn test_virtual_device_default() {
let device = GamepadVirtualDevice::default();
assert!(!device.is_created());
assert_eq!(device.vendor_id, XBOX360_VENDOR_ID);
assert_eq!(device.product_id, XBOX360_PRODUCT_ID);
}
#[test]
fn test_virtual_device_with_custom_ids() {
let device = GamepadVirtualDevice::with_ids("Custom Gamepad", 0x1234, 0x5678);
assert_eq!(device.vendor_id, 0x1234);
assert_eq!(device.product_id, 0x5678);
assert_eq!(device.device_name, "Custom Gamepad");
}
#[test]
fn test_long_device_name_truncation() {
let long_name = "A".repeat(100);
let device = GamepadVirtualDevice::with_ids(&long_name, 0x1234, 0x5678);
assert_eq!(device.device_name.len(), 79);
}
#[test]
fn test_gamepad_device_creation() {
let device = GamepadVirtualDevice::new();
assert_eq!(device.vendor_id(), 0x045e);
assert_eq!(device.product_id(), 0x028e);
}
#[test]
fn test_gamepad_axis_values() {
assert_eq!(GamepadAxis::ABS_X as u16, 0);
assert_eq!(GamepadAxis::ABS_Y as u16, 1);
assert_eq!(GamepadAxis::ABS_Z as u16, 2);
assert_eq!(GamepadAxis::ABS_RX as u16, 3);
assert_eq!(GamepadAxis::ABS_RY as u16, 4);
assert_eq!(GamepadAxis::ABS_RZ as u16, 5);
}
#[test]
fn test_device_name() {
let device = GamepadVirtualDevice::new();
assert!(!device.device_name().is_empty(), "Device name should not be empty");
assert!(device.device_name().contains("Gamepad"), "Name should contain 'Gamepad'");
}
}