diff --git a/vuinputd-tests/src/bin/test-scenarios.rs b/vuinputd-tests/src/bin/test-scenarios.rs index 39402d7..408376a 100644 --- a/vuinputd-tests/src/bin/test-scenarios.rs +++ b/vuinputd-tests/src/bin/test-scenarios.rs @@ -5,10 +5,7 @@ use clap::{Parser, Subcommand}; use vuinputd_tests::scenarios::{ basic_keyboard::BasicKeyboard, basic_mouse::BasicMouse, basic_ps4_gamepad::BasicPs4Gamepad, - basic_xbox_gamepad::BasicXboxGamepad, - ff_xbox_gamepad::FfXboxGamepad, /* - reuse_keyboard::ReuseKeyboard, reuse_xbox_gamepad::ReuseXboxGamepad, - ScenarioArgs, stress_keyboard::StressKeyboard, stress_xbox_gamepad::StressXboxGamepad, */ + basic_xbox_gamepad::BasicXboxGamepad, ff_xbox_gamepad::FfXboxGamepad, BasicMouseAbsolute, ScenarioArgs, }; @@ -36,6 +33,9 @@ enum Commands { /// Basic mouse test BasicMouse, + /// Basic mouse (absolute) test + BasicMouseAbsolute, + /// Basic PS4 gamepad test BasicPs4Gamepad, @@ -70,6 +70,7 @@ fn main() -> Result<(), std::io::Error> { match cli.command { Commands::BasicKeyboard => BasicKeyboard::run(&args), Commands::BasicMouse => BasicMouse::run(&args), + Commands::BasicMouseAbsolute => BasicMouseAbsolute::run(&args), Commands::BasicPs4Gamepad => BasicPs4Gamepad::run(&args), Commands::BasicXboxGamepad => BasicXboxGamepad::run(&args), Commands::FfXboxGamepad => FfXboxGamepad::run(&args), diff --git a/vuinputd-tests/src/devices/device_base.rs b/vuinputd-tests/src/devices/device_base.rs index bdd38a7..8359db5 100644 --- a/vuinputd-tests/src/devices/device_base.rs +++ b/vuinputd-tests/src/devices/device_base.rs @@ -23,6 +23,16 @@ pub const SYN_REPORT: u16 = 0; pub const BUS_USB: u16 = 0x03; pub const SYS_INPUT_DIR: &str = "/sys/devices/virtual/input/"; +// Absolute Axes +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; +pub const ABS_HAT0X: u16 = 0x10; +pub const ABS_HAT0Y: u16 = 0x11; + /// Struct holding device state pub struct DeviceState { pub uinput_fd: i32, @@ -99,7 +109,8 @@ pub trait Device: Sized { val: i32, ) -> io::Result { let event_device_fd = self.get_event_device()?; - let event = emit_read_and_log(event_device_fd, self.uinput_fd(), ev_type, code, val, false)?; + let event = + emit_read_and_log(event_device_fd, self.uinput_fd(), ev_type, code, val, false)?; self.state_mut().events.push(event.clone()); Ok(event) } diff --git a/vuinputd-tests/src/devices/mod.rs b/vuinputd-tests/src/devices/mod.rs index c0e7cbe..14addab 100644 --- a/vuinputd-tests/src/devices/mod.rs +++ b/vuinputd-tests/src/devices/mod.rs @@ -5,6 +5,7 @@ pub mod device_base; pub mod keyboard; pub mod mouse; +pub mod mouse_absolute; pub mod ps4_gamepad; pub mod utils; pub mod xbox_gamepad; @@ -14,6 +15,7 @@ pub use device_base::Device; pub use device_base::DeviceState; pub use keyboard::KeyboardDevice; pub use mouse::MouseDevice; +pub use mouse_absolute::MouseAbsoluteDevice; pub use ps4_gamepad::Ps4GamepadDevice; pub use xbox_gamepad::XboxGamepadDevice; diff --git a/vuinputd-tests/src/devices/mouse_absolute.rs b/vuinputd-tests/src/devices/mouse_absolute.rs new file mode 100644 index 0000000..1101047 --- /dev/null +++ b/vuinputd-tests/src/devices/mouse_absolute.rs @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: MIT +// +// Author: Johannes Leupolz + +use crate::devices::{ + device_base::{fetch_device_node, open_uinput, Device, DeviceState, BUS_USB}, + utils::{ABS_X, ABS_Y}, +}; +use libc::{c_int, close, input_absinfo, open, uinput_abs_setup, INPUT_PROP_DIRECT}; +use std::io; +use uinput_ioctls::*; + +// Mouse codes +pub const BTN_LEFT: u16 = 272; +pub const BTN_RIGHT: u16 = 273; +pub const BTN_MIDDLE: u16 = 274; +pub const REL_X: u16 = 0; +pub const REL_Y: u16 = 1; + +// non linux constants used this way in inputtino +pub const ABS_MAX_WIDTH: i32 = 19200; +pub const ABS_MAX_HEIGHT: i32 = 12000; + +/// Setup absolute mouse device +unsafe fn setup_mouse_absolute(fd: c_int) -> io::Result<()> { + // EV_SYN (implicitly handled by libevdev, but required manually for uinput) + ui_set_evbit(fd, super::EV_SYN.try_into().unwrap())?; + + // INPUT_PROP_DIRECT + ui_set_propbit(fd, INPUT_PROP_DIRECT.try_into().unwrap())?; + + // EV_KEY + ui_set_evbit(fd, super::EV_KEY.try_into().unwrap())?; + ui_set_keybit(fd, BTN_LEFT.try_into().unwrap())?; + + // EV_ABS + ui_set_evbit(fd, super::EV_ABS.try_into().unwrap())?; + ui_set_absbit(fd, ABS_X.try_into().unwrap())?; + ui_set_absbit(fd, ABS_Y.try_into().unwrap())?; + + // Setup absolute axis parameters (min, max, fuzz, flat, resolution) + let abs_x_setup = uinput_abs_setup { + code: ABS_X, + absinfo: input_absinfo { + value: 0, + minimum: 0, + maximum: ABS_MAX_WIDTH, + fuzz: 1, + flat: 0, + resolution: 28, + }, + }; + ui_abs_setup(fd, &abs_x_setup).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("ui_abs_setup X failed: {:?}", e), + ) + })?; + + let abs_y_setup = uinput_abs_setup { + code: ABS_Y, + absinfo: input_absinfo { + value: 0, + minimum: 0, + maximum: ABS_MAX_HEIGHT, + fuzz: 1, + flat: 0, + resolution: 28, + }, + }; + ui_abs_setup(fd, &abs_y_setup).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("ui_abs_setup Y failed: {:?}", e), + ) + })?; + + Ok(()) +} + +pub struct MouseAbsoluteDevice { + state: DeviceState, +} + +impl Device for MouseAbsoluteDevice { + fn name() -> &'static str { + "Mouse Absolute" + } + + fn state(&self) -> &DeviceState { + &self.state + } + + fn state_mut(&mut self) -> &mut DeviceState { + &mut self.state + } + + 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_absolute(fd)? }; + + let temp_device = MouseAbsoluteDevice { + 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, 0)?; + + unsafe { + ui_dev_create(fd).map_err(|e| { + eprintln!("ui_dev_create failed: {:?}", e); + e + })?; + } + + 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(MouseAbsoluteDevice { + state: DeviceState { + uinput_fd: fd, + sysname, + device_name: name.to_string(), + event_device_node, + event_device_fd, + events: Vec::new(), + }, + }) + } + + fn destroy(self) { + unsafe { + ui_dev_destroy(self.state.uinput_fd).unwrap_or_else(|e| { + eprintln!("ui_dev_destroy failed: {:?}", e); + std::process::exit(1); + }); + close(self.state.uinput_fd); + close(self.state.event_device_fd); + } + } +} diff --git a/vuinputd-tests/src/devices/xbox_gamepad.rs b/vuinputd-tests/src/devices/xbox_gamepad.rs index 44be126..bc1f07c 100644 --- a/vuinputd-tests/src/devices/xbox_gamepad.rs +++ b/vuinputd-tests/src/devices/xbox_gamepad.rs @@ -2,13 +2,24 @@ // // Author: Johannes Leupolz -use crate::devices::device_base::{fetch_device_node, open_uinput, Device, DeviceState, BUS_USB}; +use crate::devices::{ + device_base::{fetch_device_node, open_uinput, Device, DeviceState, BUS_USB}, + utils::{ABS_HAT0X, ABS_HAT0Y, ABS_RX, ABS_RY, ABS_RZ, ABS_X, ABS_Y, ABS_Z}, +}; use libc::{c_int, close, ff_effect, input_event, open, uinput_ff_upload, EAGAIN}; -use nix::{ioctl_write_int, ioctl_write_ptr, poll::{PollFd, PollFlags, PollTimeout, poll}}; +use nix::{ + ioctl_write_int, ioctl_write_ptr, + poll::{poll, PollFd, PollFlags, PollTimeout}, +}; use std::{ - io, os::fd::BorrowedFd, sync::{ - Arc, atomic::{AtomicBool, Ordering} - }, thread, time::Duration + io, + os::fd::BorrowedFd, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + thread, + time::Duration, }; use uinput_ioctls::*; @@ -28,16 +39,6 @@ pub const BTN_MODE: u16 = 0x13c; pub const BTN_THUMBL: u16 = 0x13d; pub const BTN_THUMBR: u16 = 0x13e; -// Absolute Axes -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; -pub const ABS_HAT0X: u16 = 0x10; -pub const ABS_HAT0Y: u16 = 0x11; - // Force Feedback // https://github.com/torvalds/linux/blob/master/include/uapi/linux/input.h pub const FF_RUMBLE: u16 = 0x50; @@ -288,7 +289,7 @@ impl Device for XboxGamepadDevice { } impl XboxGamepadDevice { - pub fn read_process_ff_event_from_uinput(&self, shutdown: Arc,use_poll:bool) { + pub fn read_process_ff_event_from_uinput(&self, shutdown: Arc, use_poll: bool) { // Copy the i32 file descriptor so we can move it into the thread safely let fd = self.state().uinput_fd; @@ -296,10 +297,10 @@ impl XboxGamepadDevice { // Buffer for the raw bytes let mut buffer = [0u8; 256]; - let mut pollfds = [ - PollFd::new(unsafe { BorrowedFd::borrow_raw(fd) }, PollFlags::POLLIN), - ]; - + let mut pollfds = [PollFd::new( + unsafe { BorrowedFd::borrow_raw(fd) }, + PollFlags::POLLIN, + )]; loop { if shutdown.load(Ordering::SeqCst) { @@ -348,11 +349,12 @@ impl XboxGamepadDevice { println!("effect type: {}", upload.effect.type_); ui_end_ff_upload(fd, ptr).unwrap(); }; - } - else { - println!("event: {} {} {}",input_event.type_,input_event.code,input_event.value); - crate::devices::utils::emit(fd, input_event.type_,input_event.code,input_event.value).unwrap(); - + } else { + println!( + "event: {} {} {}", + input_event.type_, input_event.code, input_event.value + ); + //crate::devices::utils::emit(fd, input_event.type_,input_event.code,input_event.value).unwrap(); } } else { println!("Read {} bytes", result); diff --git a/vuinputd-tests/src/scenarios/basic_mouse_absolute.rs b/vuinputd-tests/src/scenarios/basic_mouse_absolute.rs new file mode 100644 index 0000000..af7dcd4 --- /dev/null +++ b/vuinputd-tests/src/scenarios/basic_mouse_absolute.rs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +// +// Author: Johannes Leupolz +use std::thread; +use std::time::Duration; + +use crate::devices::{Device, MouseAbsoluteDevice, EV_KEY}; +use crate::scenarios::ScenarioArgs; +use crate::test_log::{LoggedInputEvent, TestLog}; + +const BTN_LEFT: u16 = 272; + +pub struct BasicMouseAbsolute; + +impl BasicMouseAbsolute { + pub fn run(args: &ScenarioArgs) -> Result<(), std::io::Error> { + let device = args + .dev_path + .clone() + .unwrap_or_else(|| "/dev/uinput".to_string()); + + let mut mouse = MouseAbsoluteDevice::create(Some(&device), "Example Mouse (absolute)")?; + eprintln!("sysname: {}", mouse.sysname()); + + thread::sleep(Duration::from_secs(1)); + + 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: mouse.event_log().to_vec(), + }; + let serialized = serde_json::to_string(&eventlog).unwrap(); + println!("Event log: {}", serialized); + + MouseAbsoluteDevice::destroy(mouse); + Ok(()) + } +} diff --git a/vuinputd-tests/src/scenarios/ff_xbox_gamepad.rs b/vuinputd-tests/src/scenarios/ff_xbox_gamepad.rs index b2e26ab..4ddc8bb 100644 --- a/vuinputd-tests/src/scenarios/ff_xbox_gamepad.rs +++ b/vuinputd-tests/src/scenarios/ff_xbox_gamepad.rs @@ -41,7 +41,7 @@ impl FfXboxGamepad { // ensure uploaded effect gets processed let shutdown = Arc::new(AtomicBool::new(false)); - gamepad.read_process_ff_event_from_uinput(shutdown.clone(),false); + gamepad.read_process_ff_event_from_uinput(shutdown.clone(), false); // Upload effect via ioctl let effect_id = upload_effect(gamepad.state().event_device_fd, &mut effect)?; diff --git a/vuinputd-tests/src/scenarios/mod.rs b/vuinputd-tests/src/scenarios/mod.rs index 1d3d2f0..121be5c 100644 --- a/vuinputd-tests/src/scenarios/mod.rs +++ b/vuinputd-tests/src/scenarios/mod.rs @@ -4,6 +4,7 @@ pub mod basic_keyboard; pub mod basic_mouse; +pub mod basic_mouse_absolute; pub mod basic_ps4_gamepad; pub mod basic_xbox_gamepad; pub mod ff_xbox_gamepad; @@ -17,6 +18,7 @@ pub mod stress_xbox_gamepad; // Re-exports for type checking pub use basic_keyboard::BasicKeyboard; pub use basic_mouse::BasicMouse; +pub use basic_mouse_absolute::BasicMouseAbsolute; pub use basic_ps4_gamepad::BasicPs4Gamepad; pub use basic_xbox_gamepad::BasicXboxGamepad; pub use ff_xbox_gamepad::FfXboxGamepad; diff --git a/vuinputd-tests/tests/integration_tests.rs b/vuinputd-tests/tests/integration_tests.rs index e0da2e0..a165797 100644 --- a/vuinputd-tests/tests/integration_tests.rs +++ b/vuinputd-tests/tests/integration_tests.rs @@ -194,7 +194,6 @@ fn test_keyboard_in_container_with_vuinput_placement_on_host() { assert!(out.status.success()); } - #[ignore = "not implemented yet"] #[cfg(all( feature = "requires-privileges", @@ -221,7 +220,7 @@ fn test_gamepad_with_ff_in_container() { .expect("failed to create IPC"); let out = builder - .command(test_scenarios, &["--ipc","ff-xbox-gamepad"]) + .command(test_scenarios, &["--ipc", "ff-xbox-gamepad"]) .run() .unwrap_or_else(|e| panic!("failed to run bwrap!: {e}")); @@ -230,4 +229,40 @@ fn test_gamepad_with_ff_in_container() { println!("stderr: {}", str::from_utf8(&out.stderr).unwrap()); assert!(out.status.success()); -} \ No newline at end of file +} +#[ignore = "not implemented yet"] +#[cfg(all( + feature = "requires-privileges", + feature = "requires-uinput", + feature = "requires-bwrap" +))] +#[test] +fn test_mouse_absolute_in_container() { + let _guard: run_vuinputd::VuinputdGuard = run_vuinputd::ensure_vuinputd_running(&[]); + + let test_scenarios = env!("CARGO_BIN_EXE_test-scenarios"); + + let (builder, _ipc) = bwrap::BwrapBuilder::new() + .unshare_net() + .ro_bind("/", "/") + .tmpfs("/tmp") + // dev needs to be writable for the new devices + .dev() + // run needs to be writable for the udev devices + .tmpfs("/run") + .dev_bind("/dev/vuinput-test", "/dev/uinput") + .die_with_parent() + .with_ipc() + .expect("failed to create IPC"); + + let out = builder + .command(test_scenarios, &["--ipc", "basic-mouse-absolute"]) + .run() + .unwrap_or_else(|e| panic!("failed to run bwrap!: {e}")); + + println!("Output"); + println!("stdout: {}", str::from_utf8(&out.stdout).unwrap()); + println!("stderr: {}", str::from_utf8(&out.stderr).unwrap()); + + assert!(out.status.success()); +}