From 2a348ce32d131ebf6b9f45e9f4e9cb960b8397b0 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Wed, 31 Jul 2024 20:48:58 +0100 Subject: [PATCH 01/10] feat: better Rust bindings, properly wrapped mouse --- bindings/rust/src/common.rs | 27 ++++++++ bindings/rust/src/lib.rs | 61 ++++++----------- bindings/rust/src/mouse.rs | 129 ++++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 39 deletions(-) create mode 100644 bindings/rust/src/common.rs create mode 100644 bindings/rust/src/mouse.rs diff --git a/bindings/rust/src/common.rs b/bindings/rust/src/common.rs new file mode 100644 index 0000000..0328565 --- /dev/null +++ b/bindings/rust/src/common.rs @@ -0,0 +1,27 @@ +pub struct InputtinoDeviceDefinition { + pub def: super::InputtinoDeviceDefinition, +} + +impl InputtinoDeviceDefinition { + pub fn new(name: &str, vendor_id: u16, product_id: u16, version: u16, phys: &str, uniq: &str) -> Self { + let name = std::ffi::CString::new(name).unwrap(); + let phys = std::ffi::CString::new(phys).unwrap(); + let uniq = std::ffi::CString::new(uniq).unwrap(); + let def = super::InputtinoDeviceDefinition { + name: name.as_ptr(), + vendor_id: vendor_id, + product_id: product_id, + version: version, + device_phys: phys.as_ptr(), // TODO: optional, if not present random MAC address + device_uniq: uniq.as_ptr(), + }; + InputtinoDeviceDefinition { def } + } +} + +pub unsafe extern "C" fn error_handler_fn(error_message: *const ::core::ffi::c_char, + user_data: *mut ::core::ffi::c_void) { + let error_str = std::ffi::CStr::from_ptr(error_message); + let user_data = user_data as *mut std::ffi::CString; + *user_data = std::ffi::CString::from(error_str); +} diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index 03ef284..7acee45 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -4,47 +4,30 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs")); -#[cfg(test)] -mod tests{ +pub mod common; +pub mod mouse; - use std::ffi::{CStr, CString}; - use super::*; +#[cfg(test)] +mod tests { + use crate::*; #[test] - fn test_inputtino_mouse(){ - let device_name = CString::new("Rusty Mouse").unwrap(); - let device_phys = CString::new("Rusty Mouse Phys").unwrap(); - let device_uniq = CString::new("Rusty Mouse Uniq").unwrap(); - let def = InputtinoDeviceDefinition { - name: device_name.as_ptr(), - vendor_id: 0, - product_id: 0, - version: 0, - device_phys: device_phys.as_ptr(), - device_uniq: device_uniq.as_ptr(), - }; - let _error_handler_fn = | error_message: *const ::core::ffi::c_char, user_data: *mut ::core::ffi::c_void | { - unsafe{ println!("Error: {:?}", CStr::from_ptr(error_message).to_str().unwrap()); } - }; - let error_handler = InputtinoErrorHandler { - eh: None, // TODO: InputtinoErrorHandlerFn::new(error_handler_fn) ??? - user_data: std::ptr::null_mut(), - }; - - unsafe{ - let mouse = inputtino_mouse_create(&def, &error_handler); - assert!(!mouse.is_null()); - - let mut nodes_count: core::ffi::c_int = 0; - let nodes = inputtino_mouse_get_nodes(mouse, & mut nodes_count); - assert!(nodes_count == 2); - assert!(!nodes.is_null()); - // Check that the nodes start with /dev/input/event - assert!(CString::from_raw(*nodes.offset(0)).to_str().unwrap().starts_with("/dev/input/event")); - assert!(CString::from_raw(*nodes.offset(1)).to_str().unwrap().starts_with("/dev/input/event")); - - inputtino_mouse_destroy(mouse); - } + fn test_inputtino_mouse() { + let device = crate::common::InputtinoDeviceDefinition::new("Rusty Mouse", 0, 0, 0, "Rusty Mouse Phys", "Rusty Mouse Uniq"); + let mouse = crate::mouse::InputtinoMouse::new(&device).unwrap(); + let nodes = mouse.get_nodes().unwrap(); + assert_eq!(nodes.len(), 2); + + // Check that the nodes start with /dev/input/event + assert!(nodes[0].starts_with("/dev/input/event")); + assert!(nodes[1].starts_with("/dev/input/event")); + + // TODO: test the followings with libinput + mouse.move_rel(10, 10); + mouse.move_abs(100, 100, 1920, 1080); + mouse.press_button(INPUTTINO_MOUSE_BUTTON::LEFT); + mouse.release_button(INPUTTINO_MOUSE_BUTTON::LEFT); + mouse.scroll_vertical(100); + mouse.scroll_horizontal(100); } - } diff --git a/bindings/rust/src/mouse.rs b/bindings/rust/src/mouse.rs new file mode 100644 index 0000000..4dec33b --- /dev/null +++ b/bindings/rust/src/mouse.rs @@ -0,0 +1,129 @@ +use std::ffi::{CString}; +use crate::{inputtino_mouse_create, inputtino_mouse_destroy, inputtino_mouse_get_nodes, inputtino_mouse_move, inputtino_mouse_move_absolute, inputtino_mouse_press_button, inputtino_mouse_release_button, inputtino_mouse_scroll_horizontal, inputtino_mouse_scroll_vertical}; +use crate::common::{InputtinoDeviceDefinition, error_handler_fn}; + +pub struct InputtinoMouse { + mouse: *mut super::InputtinoMouse, +} + +impl InputtinoMouse { + pub fn new(device: &InputtinoDeviceDefinition) -> Result { + let error_str = std::ptr::null_mut(); + let error_handler = super::InputtinoErrorHandler { + eh: Some(error_handler_fn), + user_data: error_str, + }; + unsafe { + let mouse = inputtino_mouse_create(&device.def, &error_handler); + if mouse.is_null() { // TODO: test this + let error_msg = (error_str as *mut std::ffi::CString).as_ref().unwrap().to_str().unwrap(); + Err("Failed to create Mouse: ".to_string() + error_msg) + } else { + Ok(InputtinoMouse { mouse }) + } + } + } + + pub fn get_nodes(&self) -> Result, String> { + unsafe { + let mut nodes_count: core::ffi::c_int = 0; + let nodes = inputtino_mouse_get_nodes(self.mouse, &mut nodes_count); + if nodes.is_null() { + return Err("Failed to get nodes".to_string()); + } + + let mut result = Vec::new(); + for i in 0..nodes_count { + let node = CString::from_raw(*nodes.offset(i as isize)); + result.push(node.to_str().unwrap().to_string()); + } + Ok(result) + } + } + + pub fn move_rel(&self, x: i32, y: i32) { + unsafe { + inputtino_mouse_move(self.mouse, x, y); + } + } + + pub fn move_abs(&self, x: i32, y: i32, screen_width: i32, screen_height: i32) { + unsafe { + inputtino_mouse_move_absolute(self.mouse, x, y, screen_width, screen_height); + } + } + + pub fn press_button(&self, button: super::INPUTTINO_MOUSE_BUTTON) { + unsafe { + inputtino_mouse_press_button(self.mouse, button); + } + } + + pub fn release_button(&self, button: super::INPUTTINO_MOUSE_BUTTON) { + unsafe { + inputtino_mouse_release_button(self.mouse, button); + } + } + + pub fn scroll_vertical(&self, amount: i32) { + unsafe { + inputtino_mouse_scroll_vertical(self.mouse, amount); + } + } + + pub fn scroll_horizontal(&self, amount: i32) { + unsafe { + inputtino_mouse_scroll_horizontal(self.mouse, amount); + } + } +} + +impl Drop for InputtinoMouse { + fn drop(&mut self) { + unsafe { + inputtino_mouse_destroy(self.mouse); + } + } +} + +#[cfg(test)] +mod tests { + use std::ffi::{CString}; + use super::*; + + #[test] + fn test_inputtino_c_mouse() { + let device_name = CString::new("Rusty Mouse").unwrap(); + let device_phys = CString::new("Rusty Mouse Phys").unwrap(); + let device_uniq = CString::new("Rusty Mouse Uniq").unwrap(); + let def = crate::InputtinoDeviceDefinition { + name: device_name.as_ptr(), + vendor_id: 0, + product_id: 0, + version: 0, + device_phys: device_phys.as_ptr(), + device_uniq: device_uniq.as_ptr(), + }; + // TODO: test this somehow + let error_str = std::ptr::null_mut(); + let error_handler = crate::InputtinoErrorHandler { + eh: Some(error_handler_fn), + user_data: error_str, + }; + + unsafe { + let mouse = inputtino_mouse_create(&def, &error_handler); + assert!(!mouse.is_null()); + + let mut nodes_count: core::ffi::c_int = 0; + let nodes = inputtino_mouse_get_nodes(mouse, &mut nodes_count); + assert_eq!(nodes_count, 2); + assert!(!nodes.is_null()); + // Check that the nodes start with /dev/input/event + assert!(CString::from_raw(*nodes.offset(0)).to_str().unwrap().starts_with("/dev/input/event")); + assert!(CString::from_raw(*nodes.offset(1)).to_str().unwrap().starts_with("/dev/input/event")); + + inputtino_mouse_destroy(mouse); + } + } +} From 2394ecfa0ce9597a098d056d865665a821438545 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Wed, 31 Jul 2024 21:58:41 +0100 Subject: [PATCH 02/10] fix: absolute mouse, remove hardcoded name --- src/uinput/mouse.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uinput/mouse.cpp b/src/uinput/mouse.cpp index 30b15f5..3b70e15 100644 --- a/src/uinput/mouse.cpp +++ b/src/uinput/mouse.cpp @@ -63,14 +63,14 @@ static Result create_mouse(const DeviceDefinition &device) return libevdev_uinput_ptr{uidev, ::libevdev_uinput_destroy}; } -static Result create_mouse_abs() { +static Result create_mouse_abs(const DeviceDefinition &device) { libevdev *dev = libevdev_new(); libevdev_uinput *uidev; - libevdev_set_name(dev, "Wolf mouse (abs) virtual device"); - libevdev_set_id_vendor(dev, 0xAB00); - libevdev_set_id_product(dev, 0xAB02); - libevdev_set_id_version(dev, 0xAB00); + libevdev_set_name(dev, (device.name + " (absolute)").c_str()); + libevdev_set_id_vendor(dev, device.vendor_id); + libevdev_set_id_product(dev, device.product_id); + libevdev_set_id_version(dev, device.version); libevdev_set_id_bustype(dev, BUS_USB); libevdev_enable_property(dev, INPUT_PROP_DIRECT); From 60ad2756ff2da8c051979769e8cc16c8e3d56a92 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Wed, 31 Jul 2024 21:59:54 +0100 Subject: [PATCH 03/10] fix: absolute mouse, remove hardcoded name --- src/uinput/mouse.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uinput/mouse.cpp b/src/uinput/mouse.cpp index 3b70e15..7c4b549 100644 --- a/src/uinput/mouse.cpp +++ b/src/uinput/mouse.cpp @@ -114,7 +114,7 @@ Result Mouse::create(const DeviceDefinition &device) { return Error(mouse_rel_or_error.getErrorMessage()); } - auto mouse_abs_or_error = create_mouse_abs(); + auto mouse_abs_or_error = create_mouse_abs(device); if (mouse_abs_or_error) { mouse._state->mouse_abs = std::move(*mouse_abs_or_error); } else { From 4c280b630750d3021ccef34701ec85cf2fb0de67 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Thu, 1 Aug 2024 17:09:55 +0100 Subject: [PATCH 04/10] feat: completed Rust mouse API + tests --- bindings/rust/Cargo.toml | 5 ++ bindings/rust/src/common.rs | 7 +- bindings/rust/src/lib.rs | 25 ------ bindings/rust/tests/mouse.rs | 151 +++++++++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+), 26 deletions(-) create mode 100644 bindings/rust/tests/mouse.rs diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index e1aa98f..f0d060f 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -13,3 +13,8 @@ path = "src/lib.rs" [build-dependencies] bindgen = "0.69.4" cmake = "0.1" + +[dev-dependencies] +input = "0.9.0" +rustix = { version = "0.38.18", features = ["fs"] } +approx = "0.5.1" diff --git a/bindings/rust/src/common.rs b/bindings/rust/src/common.rs index 0328565..ba0f0dc 100644 --- a/bindings/rust/src/common.rs +++ b/bindings/rust/src/common.rs @@ -1,5 +1,10 @@ +#[allow(dead_code)] pub struct InputtinoDeviceDefinition { pub def: super::InputtinoDeviceDefinition, + // Keep those around since we are passing them as pointers + name: std::ffi::CString, + phys: std::ffi::CString, + uniq: std::ffi::CString, } impl InputtinoDeviceDefinition { @@ -15,7 +20,7 @@ impl InputtinoDeviceDefinition { device_phys: phys.as_ptr(), // TODO: optional, if not present random MAC address device_uniq: uniq.as_ptr(), }; - InputtinoDeviceDefinition { def } + InputtinoDeviceDefinition { def, name, phys, uniq } } } diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index 7acee45..8074090 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -6,28 +6,3 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs")); pub mod common; pub mod mouse; - -#[cfg(test)] -mod tests { - use crate::*; - - #[test] - fn test_inputtino_mouse() { - let device = crate::common::InputtinoDeviceDefinition::new("Rusty Mouse", 0, 0, 0, "Rusty Mouse Phys", "Rusty Mouse Uniq"); - let mouse = crate::mouse::InputtinoMouse::new(&device).unwrap(); - let nodes = mouse.get_nodes().unwrap(); - assert_eq!(nodes.len(), 2); - - // Check that the nodes start with /dev/input/event - assert!(nodes[0].starts_with("/dev/input/event")); - assert!(nodes[1].starts_with("/dev/input/event")); - - // TODO: test the followings with libinput - mouse.move_rel(10, 10); - mouse.move_abs(100, 100, 1920, 1080); - mouse.press_button(INPUTTINO_MOUSE_BUTTON::LEFT); - mouse.release_button(INPUTTINO_MOUSE_BUTTON::LEFT); - mouse.scroll_vertical(100); - mouse.scroll_horizontal(100); - } -} diff --git a/bindings/rust/tests/mouse.rs b/bindings/rust/tests/mouse.rs new file mode 100644 index 0000000..c543c47 --- /dev/null +++ b/bindings/rust/tests/mouse.rs @@ -0,0 +1,151 @@ +#[macro_use] +extern crate approx; + +use input::{Event, Libinput, LibinputInterface}; +use rustix::{ + fs::{open, OFlags, Mode} +}; +use std::os::unix::{io::OwnedFd}; +use std::path::Path; +use input::event::{DeviceEvent, PointerEvent}; +use input::event::pointer::{Axis, ButtonState}; +use inputtino_rs::{ + INPUTTINO_MOUSE_BUTTON, + mouse::InputtinoMouse, + common::InputtinoDeviceDefinition, +}; + +struct NixInterface; + +impl LibinputInterface for NixInterface { + fn open_restricted(&mut self, path: &Path, flags: i32) -> Result { + open(path, OFlags::from_bits_truncate(flags as u32), Mode::empty()) + .map_err(|err| err.raw_os_error()) + } + fn close_restricted(&mut self, fd: OwnedFd) { + let _ = fd; + } +} + +fn wait_next_event(input: &mut Libinput) -> Option { + for _ in 0..10 { // loop maximum 10 times to avoid infinite loop + input.dispatch().unwrap(); + if let Some(event) = input.next() { + return Some(event); + } + std::thread::sleep(std::time::Duration::from_millis(50)); + } + return None; +} + +#[test] +fn test_inputtino_mouse() { + let device = InputtinoDeviceDefinition::new("Rusty Mouse", 0xAB, 0xCD, 0xEF, "Rusty Mouse Phys", "Rusty Mouse Uniq"); + let mouse = InputtinoMouse::new(&device).unwrap(); + let nodes = mouse.get_nodes().unwrap(); + { + assert_eq!(nodes.len(), 2); + + // Check that the nodes start with /dev/input/event + assert!(nodes[0].starts_with("/dev/input/event")); + assert!(nodes[1].starts_with("/dev/input/event")); + } + + let mut input = Libinput::new_from_path(NixInterface); + let dev_rel = input.path_add_device(nodes[0].as_str()).expect("to get the device"); + let dev_abs = input.path_add_device(nodes[1].as_str()).expect("to get the device"); + + { + assert_eq!(dev_rel.name(), "Rusty Mouse"); + assert_eq!(dev_abs.name(), "Rusty Mouse (absolute)"); + assert_eq!(dev_rel.id_vendor(), 0xAB); + assert_eq!(dev_abs.id_vendor(), 0xAB); + assert_eq!(dev_rel.id_product(), 0xCD); + assert_eq!(dev_abs.id_product(), 0xCD); + for event in &mut input { + assert!(matches!(event, Event::Device(DeviceEvent::Added(_)))); + } + } + + { // Test mouse relative motion + mouse.move_rel(10, 20); + + let ev = wait_next_event(&mut input).unwrap(); + assert!(matches!(ev, Event::Pointer(PointerEvent::Motion(_)))); + match ev { + Event::Pointer(PointerEvent::Motion(ev)) => { + assert_eq!(ev.dx_unaccelerated(), 10.0); + assert_eq!(ev.dy_unaccelerated(), 20.0); + } + _ => unreachable!(), + } + } + + { // Test mouse absolute motion + mouse.move_abs(100, 200, 1920, 1080); + + let ev = wait_next_event(&mut input).unwrap(); + assert!(matches!(ev, Event::Pointer(PointerEvent::MotionAbsolute(_)))); + match ev { + Event::Pointer(PointerEvent::MotionAbsolute(ev)) => { + assert_relative_eq!(ev.absolute_x_transformed(1920), 100.0, max_relative=0.1); + assert_relative_eq!(ev.absolute_y_transformed(1080), 200.0, max_relative=0.1); + } + _ => unreachable!(), + } + } + + { // Test mouse button press + mouse.press_button(INPUTTINO_MOUSE_BUTTON::LEFT); + + let ev = wait_next_event(&mut input).unwrap(); + assert!(matches!(ev, Event::Pointer(PointerEvent::Button(_)))); + match ev { + Event::Pointer(PointerEvent::Button(ev)) => { + assert_eq!(ev.button(), 272); + assert_eq!(ev.button_state(), ButtonState::Pressed); + } + _ => unreachable!(), + } + } + + { + mouse.release_button(INPUTTINO_MOUSE_BUTTON::LEFT); + + let ev = wait_next_event(&mut input).unwrap(); + assert!(matches!(ev, Event::Pointer(PointerEvent::Button(_)))); + match ev { + Event::Pointer(PointerEvent::Button(ev)) => { + assert_eq!(ev.button(), 272); + assert_eq!(ev.button_state(), ButtonState::Released); + } + _ => unreachable!(), + } + } + + { + mouse.scroll_vertical(100); + + let ev = wait_next_event(&mut input).unwrap(); + assert!(matches!(ev, Event::Pointer(PointerEvent::ScrollWheel(_)))); + match ev { + Event::Pointer(PointerEvent::ScrollWheel(ev)) => { + assert_eq!(ev.scroll_value_v120(Axis::Vertical), -100.0); + } + _ => unreachable!(), + } + } + + { + mouse.scroll_horizontal(100); + + let ev = wait_next_event(&mut input).unwrap(); + assert!(matches!(ev, Event::Pointer(PointerEvent::ScrollWheel(_)))); + match ev { + Event::Pointer(PointerEvent::ScrollWheel(ev)) => { + assert_eq!(ev.scroll_value_v120(Axis::Horizontal), 100.0); + } + _ => unreachable!(), + } + } +} From 1d124d8bad7216be9e28056fe93abfe65f6d0d00 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Thu, 1 Aug 2024 17:32:41 +0100 Subject: [PATCH 05/10] feat: completed Rust keyboard API + tests --- bindings/rust/src/common.rs | 59 ++++++++++++++++++++++++++---- bindings/rust/src/keyboard.rs | 44 ++++++++++++++++++++++ bindings/rust/src/lib.rs | 1 + bindings/rust/src/mouse.rs | 31 +++------------- bindings/rust/tests/common/mod.rs | 33 +++++++++++++++++ bindings/rust/tests/keyboard.rs | 61 +++++++++++++++++++++++++++++++ bindings/rust/tests/mouse.rs | 47 ++++++------------------ 7 files changed, 207 insertions(+), 69 deletions(-) create mode 100644 bindings/rust/src/keyboard.rs create mode 100644 bindings/rust/tests/common/mod.rs create mode 100644 bindings/rust/tests/keyboard.rs diff --git a/bindings/rust/src/common.rs b/bindings/rust/src/common.rs index ba0f0dc..0a562f0 100644 --- a/bindings/rust/src/common.rs +++ b/bindings/rust/src/common.rs @@ -1,17 +1,19 @@ +use std::ffi::{CString}; + #[allow(dead_code)] pub struct InputtinoDeviceDefinition { pub def: super::InputtinoDeviceDefinition, // Keep those around since we are passing them as pointers - name: std::ffi::CString, - phys: std::ffi::CString, - uniq: std::ffi::CString, + name: CString, + phys: CString, + uniq: CString, } impl InputtinoDeviceDefinition { pub fn new(name: &str, vendor_id: u16, product_id: u16, version: u16, phys: &str, uniq: &str) -> Self { - let name = std::ffi::CString::new(name).unwrap(); - let phys = std::ffi::CString::new(phys).unwrap(); - let uniq = std::ffi::CString::new(uniq).unwrap(); + let name = CString::new(name).unwrap(); + let phys = CString::new(phys).unwrap(); + let uniq = CString::new(uniq).unwrap(); let def = super::InputtinoDeviceDefinition { name: name.as_ptr(), vendor_id: vendor_id, @@ -27,6 +29,47 @@ impl InputtinoDeviceDefinition { pub unsafe extern "C" fn error_handler_fn(error_message: *const ::core::ffi::c_char, user_data: *mut ::core::ffi::c_void) { let error_str = std::ffi::CStr::from_ptr(error_message); - let user_data = user_data as *mut std::ffi::CString; - *user_data = std::ffi::CString::from(error_str); + let user_data = user_data as *mut CString; + *user_data = CString::from(error_str); +} + + +#[macro_export] +macro_rules! get_nodes { + ( $fn_call:expr,$var:expr ) => { + { + let mut nodes_count: core::ffi::c_int = 0; + let nodes = $fn_call($var, &mut nodes_count); + if nodes.is_null() { + return Err("Failed to get nodes".to_string()); + } + + let mut result = Vec::new(); + for i in 0..nodes_count { + let node = std::ffi::CString::from_raw(*nodes.offset(i as isize)); + result.push(node.to_str().unwrap().to_string()); + } + Ok(result) + } + }; +} + +#[macro_export] +macro_rules! make_device { + ($fn_call:expr, $device:expr) => { + { + let error_str = std::ptr::null_mut(); + let error_handler = super::InputtinoErrorHandler { + eh: Some(error_handler_fn), + user_data: error_str, + }; + let device = $fn_call(&$device.def, &error_handler); + if device.is_null() { // TODO: test this + let error_msg = (error_str as *mut std::ffi::CString).as_ref().unwrap().to_str().unwrap(); + Err("Failed to create Mouse: ".to_string() + error_msg) + } else { + Ok(device) + } + } + }; } diff --git a/bindings/rust/src/keyboard.rs b/bindings/rust/src/keyboard.rs new file mode 100644 index 0000000..7e3d0ed --- /dev/null +++ b/bindings/rust/src/keyboard.rs @@ -0,0 +1,44 @@ +use crate::common::{error_handler_fn, InputtinoDeviceDefinition}; +use crate::{get_nodes, inputtino_keyboard_create, inputtino_keyboard_get_nodes, make_device}; + +pub struct InputtinoKeyboard { + kb: *mut super::InputtinoKeyboard, +} + +impl InputtinoKeyboard { + pub fn new(device: &InputtinoDeviceDefinition) -> Result { + unsafe { + let dev = make_device!(inputtino_keyboard_create, device); + match dev { + Ok(kb) => Ok(InputtinoKeyboard { kb }), + Err(e) => Err(e), + } + } + } + + pub fn get_nodes(&self) -> Result, String> { + unsafe { + get_nodes!(inputtino_keyboard_get_nodes, self.kb) + } + } + + pub fn press_key(&self, key: i16) { + unsafe { + super::inputtino_keyboard_press(self.kb, key); + } + } + + pub fn release_key(&self, key: i16) { + unsafe { + super::inputtino_keyboard_release(self.kb, key); + } + } +} + +impl Drop for InputtinoKeyboard { + fn drop(&mut self) { + unsafe { + super::inputtino_keyboard_destroy(self.kb); + } + } +} diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index 8074090..3ed6f65 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -6,3 +6,4 @@ include!(concat!(env!("OUT_DIR"), "/bindings.rs")); pub mod common; pub mod mouse; +pub mod keyboard; diff --git a/bindings/rust/src/mouse.rs b/bindings/rust/src/mouse.rs index 4dec33b..0f49db3 100644 --- a/bindings/rust/src/mouse.rs +++ b/bindings/rust/src/mouse.rs @@ -1,5 +1,4 @@ -use std::ffi::{CString}; -use crate::{inputtino_mouse_create, inputtino_mouse_destroy, inputtino_mouse_get_nodes, inputtino_mouse_move, inputtino_mouse_move_absolute, inputtino_mouse_press_button, inputtino_mouse_release_button, inputtino_mouse_scroll_horizontal, inputtino_mouse_scroll_vertical}; +use crate::{get_nodes, inputtino_mouse_create, inputtino_mouse_destroy, inputtino_mouse_get_nodes, inputtino_mouse_move, inputtino_mouse_move_absolute, inputtino_mouse_press_button, inputtino_mouse_release_button, inputtino_mouse_scroll_horizontal, inputtino_mouse_scroll_vertical, make_device}; use crate::common::{InputtinoDeviceDefinition, error_handler_fn}; pub struct InputtinoMouse { @@ -8,36 +7,18 @@ pub struct InputtinoMouse { impl InputtinoMouse { pub fn new(device: &InputtinoDeviceDefinition) -> Result { - let error_str = std::ptr::null_mut(); - let error_handler = super::InputtinoErrorHandler { - eh: Some(error_handler_fn), - user_data: error_str, - }; unsafe { - let mouse = inputtino_mouse_create(&device.def, &error_handler); - if mouse.is_null() { // TODO: test this - let error_msg = (error_str as *mut std::ffi::CString).as_ref().unwrap().to_str().unwrap(); - Err("Failed to create Mouse: ".to_string() + error_msg) - } else { - Ok(InputtinoMouse { mouse }) + let dev = make_device!(inputtino_mouse_create, device); + match dev { + Ok(mouse) => Ok(InputtinoMouse { mouse }), + Err(e) => Err(e), } } } pub fn get_nodes(&self) -> Result, String> { unsafe { - let mut nodes_count: core::ffi::c_int = 0; - let nodes = inputtino_mouse_get_nodes(self.mouse, &mut nodes_count); - if nodes.is_null() { - return Err("Failed to get nodes".to_string()); - } - - let mut result = Vec::new(); - for i in 0..nodes_count { - let node = CString::from_raw(*nodes.offset(i as isize)); - result.push(node.to_str().unwrap().to_string()); - } - Ok(result) + get_nodes!(inputtino_mouse_get_nodes, self.mouse) } } diff --git a/bindings/rust/tests/common/mod.rs b/bindings/rust/tests/common/mod.rs new file mode 100644 index 0000000..2d135ee --- /dev/null +++ b/bindings/rust/tests/common/mod.rs @@ -0,0 +1,33 @@ +use std::os::fd::OwnedFd; +use std::path::Path; +use input::{Event, Libinput, LibinputInterface}; +use rustix::fs::{Mode, OFlags, open}; + +pub struct NixInterface; + +impl LibinputInterface for NixInterface { + fn open_restricted(&mut self, path: &Path, flags: i32) -> Result { + open(path, OFlags::from_bits_truncate(flags as u32), Mode::empty()) + .map_err(|err| err.raw_os_error()) + } + fn close_restricted(&mut self, fd: OwnedFd) { + let _ = fd; + } +} + +pub trait SyncEvent { + fn wait_next_event(&mut self) -> Option; +} + +impl SyncEvent for Libinput { + fn wait_next_event(&mut self) -> Option { + for _ in 0..10 { // loop maximum 10 times to avoid infinite loop + self.dispatch().unwrap(); + if let Some(event) = self.next() { + return Some(event); + } + std::thread::sleep(std::time::Duration::from_millis(50)); + } + return None; + } +} diff --git a/bindings/rust/tests/keyboard.rs b/bindings/rust/tests/keyboard.rs new file mode 100644 index 0000000..2113218 --- /dev/null +++ b/bindings/rust/tests/keyboard.rs @@ -0,0 +1,61 @@ +use input::{Event, Libinput}; +use input::event::{DeviceEvent}; +use input::event::keyboard::KeyboardEventTrait; +use inputtino_rs::{common::InputtinoDeviceDefinition, + keyboard::InputtinoKeyboard}; +mod common; +use crate::common::{NixInterface, SyncEvent}; + + +#[test] +fn test_inputtino_keyboard() { + let device = InputtinoDeviceDefinition::new("Rusty Keyboard", 0xAB, 0xCD, 0xEF, "Rusty Keyboard Phys", "Rusty Keyboard Uniq"); + let keyboard = InputtinoKeyboard::new(&device).unwrap(); + let nodes = keyboard.get_nodes().unwrap(); + { + assert_eq!(nodes.len(), 1); + + // Check that the nodes start with /dev/input/event + assert!(nodes[0].starts_with("/dev/input/event")); + } + + let mut input = Libinput::new_from_path(NixInterface); + let kb_dev = input.path_add_device(nodes[0].as_str()).expect("to get the device"); + + { + assert_eq!(kb_dev.name(), "Rusty Keyboard"); + assert_eq!(kb_dev.id_vendor(), 0xAB); + assert_eq!(kb_dev.id_product(), 0xCD); + for event in &mut input { + assert!(matches!(event, Event::Device(DeviceEvent::Added(_)))); + } + } + + { // Test keyboard key press + keyboard.press_key(0x41); // KEY_A + + let ev = input.wait_next_event().unwrap(); + assert!(matches!(ev, Event::Keyboard(_))); + match ev { + Event::Keyboard(ev) => { + assert_eq!(ev.key(), 30); // KEY_A + assert_eq!(ev.key_state(), input::event::keyboard::KeyState::Pressed); + } + _ => unreachable!(), + } + } + + { // Test keyboard key release + keyboard.release_key(0x41); + + let ev = input.wait_next_event().unwrap(); + assert!(matches!(ev, Event::Keyboard(_))); + match ev { + Event::Keyboard(ev) => { + assert_eq!(ev.key(), 30); // KEY_A + assert_eq!(ev.key_state(), input::event::keyboard::KeyState::Released); + } + _ => unreachable!(), + } + } +} diff --git a/bindings/rust/tests/mouse.rs b/bindings/rust/tests/mouse.rs index c543c47..a5484f0 100644 --- a/bindings/rust/tests/mouse.rs +++ b/bindings/rust/tests/mouse.rs @@ -1,42 +1,17 @@ #[macro_use] extern crate approx; -use input::{Event, Libinput, LibinputInterface}; -use rustix::{ - fs::{open, OFlags, Mode} -}; -use std::os::unix::{io::OwnedFd}; -use std::path::Path; -use input::event::{DeviceEvent, PointerEvent}; -use input::event::pointer::{Axis, ButtonState}; use inputtino_rs::{ INPUTTINO_MOUSE_BUTTON, mouse::InputtinoMouse, common::InputtinoDeviceDefinition, }; +use input::{Event, Libinput}; +use input::event::{DeviceEvent, PointerEvent}; +use input::event::pointer::{Axis, ButtonState}; -struct NixInterface; - -impl LibinputInterface for NixInterface { - fn open_restricted(&mut self, path: &Path, flags: i32) -> Result { - open(path, OFlags::from_bits_truncate(flags as u32), Mode::empty()) - .map_err(|err| err.raw_os_error()) - } - fn close_restricted(&mut self, fd: OwnedFd) { - let _ = fd; - } -} - -fn wait_next_event(input: &mut Libinput) -> Option { - for _ in 0..10 { // loop maximum 10 times to avoid infinite loop - input.dispatch().unwrap(); - if let Some(event) = input.next() { - return Some(event); - } - std::thread::sleep(std::time::Duration::from_millis(50)); - } - return None; -} +mod common; +use crate::common::{NixInterface, SyncEvent}; #[test] fn test_inputtino_mouse() { @@ -70,7 +45,7 @@ fn test_inputtino_mouse() { { // Test mouse relative motion mouse.move_rel(10, 20); - let ev = wait_next_event(&mut input).unwrap(); + let ev = input.wait_next_event().unwrap(); assert!(matches!(ev, Event::Pointer(PointerEvent::Motion(_)))); match ev { Event::Pointer(PointerEvent::Motion(ev)) => { @@ -84,7 +59,7 @@ fn test_inputtino_mouse() { { // Test mouse absolute motion mouse.move_abs(100, 200, 1920, 1080); - let ev = wait_next_event(&mut input).unwrap(); + let ev = input.wait_next_event().unwrap(); assert!(matches!(ev, Event::Pointer(PointerEvent::MotionAbsolute(_)))); match ev { Event::Pointer(PointerEvent::MotionAbsolute(ev)) => { @@ -98,7 +73,7 @@ fn test_inputtino_mouse() { { // Test mouse button press mouse.press_button(INPUTTINO_MOUSE_BUTTON::LEFT); - let ev = wait_next_event(&mut input).unwrap(); + let ev = input.wait_next_event().unwrap(); assert!(matches!(ev, Event::Pointer(PointerEvent::Button(_)))); match ev { Event::Pointer(PointerEvent::Button(ev)) => { @@ -112,7 +87,7 @@ fn test_inputtino_mouse() { { mouse.release_button(INPUTTINO_MOUSE_BUTTON::LEFT); - let ev = wait_next_event(&mut input).unwrap(); + let ev = input.wait_next_event().unwrap(); assert!(matches!(ev, Event::Pointer(PointerEvent::Button(_)))); match ev { Event::Pointer(PointerEvent::Button(ev)) => { @@ -126,7 +101,7 @@ fn test_inputtino_mouse() { { mouse.scroll_vertical(100); - let ev = wait_next_event(&mut input).unwrap(); + let ev = input.wait_next_event().unwrap(); assert!(matches!(ev, Event::Pointer(PointerEvent::ScrollWheel(_)))); match ev { Event::Pointer(PointerEvent::ScrollWheel(ev)) => { @@ -139,7 +114,7 @@ fn test_inputtino_mouse() { { mouse.scroll_horizontal(100); - let ev = wait_next_event(&mut input).unwrap(); + let ev = input.wait_next_event().unwrap(); assert!(matches!(ev, Event::Pointer(PointerEvent::ScrollWheel(_)))); match ev { Event::Pointer(PointerEvent::ScrollWheel(ev)) => { From 3a4b6a4d1bfe75e0cdd783f8dbb7eb39fe4d5cee Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Thu, 1 Aug 2024 18:01:27 +0100 Subject: [PATCH 06/10] feat: added pkgconfig configuration --- CMakeLists.txt | 7 +++++++ share/pkgconfig/libinputtino.pc.in | 17 +++++++++++++++++ tests/CMakeLists.txt | 2 ++ 3 files changed, 26 insertions(+) create mode 100644 share/pkgconfig/libinputtino.pc.in diff --git a/CMakeLists.txt b/CMakeLists.txt index f0182c5..67d3303 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -163,6 +163,13 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/include/inputtino/${export_file_name}" COMPONENT libinputtino-dev DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/inputtino") + configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/share/pkgconfig/libinputtino.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/libinputtino.pc + @ONLY + ) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libinputtino.pc + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) endif () endif () diff --git a/share/pkgconfig/libinputtino.pc.in b/share/pkgconfig/libinputtino.pc.in new file mode 100644 index 0000000..eb05b2e --- /dev/null +++ b/share/pkgconfig/libinputtino.pc.in @@ -0,0 +1,17 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/@PROJECT_NAME@ + +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@INSTALL_LIB_DIR@ +includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@/@PROJECT_NAME@ + +Name: libinputtino +Description: @PROJECT_DESCRIPTION@ +URL: @PROJECT_HOMEPAGE_URL@ +Version: @PROJECT_VERSION@ + +Requires: +Libs: -L${libdir} -llibevdev +Cflags: -I${includedir} + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 68a926d..2c370d7 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,5 @@ +include(FetchContent) + # Testing library FetchContent_Declare( Catch2 From 5df6e1e68e0932f9c519a87c7095797f966293d8 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Thu, 1 Aug 2024 17:55:07 +0100 Subject: [PATCH 07/10] feat: allow Rust build when using the shared library --- bindings/rust/Cargo.toml | 6 ++- bindings/rust/build.rs | 82 +++++++++++++++++++++------------------- 2 files changed, 48 insertions(+), 40 deletions(-) diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index f0d060f..ce50bed 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -1,10 +1,13 @@ [package] name = "inputtino" -version = "0.1.0" +version = "2024.8.1" edition = "2021" license = "MIT" rust-version = "1.72" links = "libinputtino" +homepage = "https://github.com/games-on-whales/inputtino" +authors = ["ABeltramo"] +description = "Rust bindings for inputtino" [lib] name = "inputtino_rs" @@ -13,6 +16,7 @@ path = "src/lib.rs" [build-dependencies] bindgen = "0.69.4" cmake = "0.1" +pkg-config = "0.3.30" [dev-dependencies] input = "0.9.0" diff --git a/bindings/rust/build.rs b/bindings/rust/build.rs index ce2c5c9..b8f5883 100644 --- a/bindings/rust/build.rs +++ b/bindings/rust/build.rs @@ -1,4 +1,5 @@ extern crate bindgen; +extern crate pkg_config; use std::env; use std::path::PathBuf; @@ -6,26 +7,48 @@ use std::path::PathBuf; use cmake::Config; fn main() { - let build_static = false; + // Options + let build_c_bindings = env::var("INPUTTINO_BUILD_C_BINDINGS").unwrap_or("FALSE".to_string()) == "TRUE"; + let build_static = env::var("INPUTTINO_BUILD_STATIC").unwrap_or("FALSE".to_string()) == "TRUE"; - // This is the directory where the `c` library is located. - let libdir_path = PathBuf::from("../../") - // Canonicalize the path as `rustc-link-search` requires an absolute - // path. - .canonicalize() - .expect("cannot canonicalize path"); + // The bindgen::Builder is the main entry point + // to bindgen, and lets you build up options for + // the resulting bindings. + let mut bindings = bindgen::Builder::default() + .use_core() + .default_enum_style(bindgen::EnumVariation::Rust { + non_exhaustive: false, + }) + // Set the INPUTTINO_STATIC_DEFINE macro + .clang_arg(if build_static { "-D INPUTTINO_STATIC_DEFINE=1" } else { "" }) + // The input header we would like to generate bindings for. + .header("wrapper.hpp"); - // Compile the library using CMake - let dst = Config::new(libdir_path) - .target("libinputtino") - .define("BUILD_SHARED_LIBS", if build_static { "OFF" } else { "ON" }) - .define("LIBINPUTTINO_INSTALL", "ON") - .define("BUILD_TESTING", "OFF") - .define("BUILD_SERVER", "OFF") - .define("BUILD_C_BINDINGS", "ON") - .profile("Release") - .define("CMAKE_CONFIGURATION_TYPES", "Release") - .build(); + if build_c_bindings { + let libdir_path = PathBuf::from("../../") + // Canonicalize the path as `rustc-link-search` requires an absolute + // path. + .canonicalize() + .expect("cannot canonicalize path"); + + // Compile the library using CMake + let dst = Config::new(libdir_path) + .target("libinputtino") + .define("BUILD_SHARED_LIBS", if build_static { "OFF" } else { "ON" }) + .define("LIBINPUTTINO_INSTALL", "ON") + .define("BUILD_TESTING", "OFF") + .define("BUILD_SERVER", "OFF") + .define("BUILD_C_BINDINGS", "ON") + .profile("Release") + .define("CMAKE_CONFIGURATION_TYPES", "Release") + .build(); + + println!("cargo:rustc-link-search=native={}/lib", dst.display()); + bindings = bindings.clang_arg(format!("-I{}/include/", dst.display())) + } else { + let lib = pkg_config::probe_library("libinputtino").unwrap(); + bindings = bindings.clang_arg(format!("-I{}", lib.include_paths[0].display())); + } // Dependencies if !build_static { @@ -33,32 +56,13 @@ fn main() { println!("cargo:rustc-link-lib=stdc++"); } - //libinputtino - println!("cargo:rustc-link-search=native={}/lib", dst.display()); println!("cargo:rustc-link-lib={}libinputtino", if build_static { "static=" } else { "" }); - // The bindgen::Builder is the main entry point - // to bindgen, and lets you build up options for - // the resulting bindings. - let bindings = bindgen::Builder::default() - .use_core() - .default_enum_style(bindgen::EnumVariation::Rust { - non_exhaustive: false, - }) - // Add the include directory - .clang_arg(format!("-I{}/include/", dst.display())) - // Set the INPUTTINO_STATIC_DEFINE macro - .clang_arg(if build_static {"-D INPUTTINO_STATIC_DEFINE=1"} else {""}) - // The input header we would like to generate bindings for. - .header("wrapper.hpp") - // Finish the builder and generate the bindings. - .generate() - // Unwrap the Result and panic on failure. - .expect("Unable to generate bindings"); + let out = bindings.generate().expect("Unable to generate bindings"); // Write the bindings to the $OUT_DIR/bindings.rs file. let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs"); - bindings + out .write_to_file(out_path) .expect("Couldn't write bindings!"); } From 0b57533b54832d6e28a5290205ff4bf20028845c Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Thu, 1 Aug 2024 21:22:04 +0100 Subject: [PATCH 08/10] feat: added basic Dockerfile for compiling from scratch --- .dockerignore | 4 +++ bindings/rust/.dockerignore | 1 + docker/Dockerfile | 70 +++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 bindings/rust/.dockerignore create mode 100644 docker/Dockerfile diff --git a/.dockerignore b/.dockerignore index 92245fe..604a1c7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,7 @@ !include !cmake/ !docker/startup.sh +!share/ + +# Rust bindings +!bindings/rust/ diff --git a/bindings/rust/.dockerignore b/bindings/rust/.dockerignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/bindings/rust/.dockerignore @@ -0,0 +1 @@ +target/ diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..87f4d1c --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,70 @@ +ARG BASE_IMAGE=ghcr.io/games-on-whales/base:edge + +#################################### +FROM $BASE_IMAGE AS build-libinputtino + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + ccache \ + ninja-build \ + cmake \ + clang \ + pkg-config \ + git \ + libevdev-dev \ + && rm -rf /var/lib/apt/lists/* + + +COPY . /inputtino/ +WORKDIR /inputtino + +ENV CCACHE_DIR=/cache/ccache +ENV CMAKE_BUILD_DIR=/cache/cmake-build +RUN --mount=type=cache,target=/cache/ccache \ + cmake -B$CMAKE_BUILD_DIR \ + -DCMAKE_INSTALL_PREFIX:PATH=/usr \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTING=OFF \ + -DBUILD_C_BINDINGS=ON \ + -DLIBINPUTTINO_INSTALL=ON \ + -DBUILD_SHARED_LIBS=ON \ + -G Ninja && \ + ninja -C $CMAKE_BUILD_DIR install + +#################################### +FROM $BASE_IMAGE AS base-libinputtino + +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends libevdev2 \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=build-libinputtino /usr/include/inputtino /usr/include/inputtino +COPY --from=build-libinputtino /usr/share/pkgconfig/libinputtino.pc /usr/share/pkgconfig/libinputtino.pc +COPY --from=build-libinputtino /usr/lib/x86_64-linux-gnu/liblibinputtino* /usr/lib/x86_64-linux-gnu/ + +#################################### +FROM base-libinputtino AS build-rust-bindings + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update -y && \ + apt-get install -y --no-install-recommends \ + ca-certificates \ + pkg-config \ + build-essential \ + clang \ + curl \ + && curl https://sh.rustup.rs -sSf | bash -s -- -y \ + && rm -rf /var/lib/apt/lists/* + +ENV PATH="${HOME}/.cargo/bin:${PATH}" + +COPY ./bindings/rust /inputtino-rust +WORKDIR /inputtino-rust + + +RUN cargo build --release + From 7f197ff13ad9187ee5f0887747773a37630fbd40 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Sun, 4 Aug 2024 10:24:57 +0100 Subject: [PATCH 09/10] feat: added xbox joypad, separated c_bindings Added test using SDL --- bindings/rust/Cargo.toml | 1 + bindings/rust/src/c_bindings.rs | 5 ++ bindings/rust/src/common.rs | 7 +- bindings/rust/src/joypad_xbox.rs | 73 ++++++++++++++++++ bindings/rust/src/keyboard.rs | 11 +-- bindings/rust/src/lib.rs | 8 +- bindings/rust/src/mouse.rs | 15 ++-- bindings/rust/tests/joypad_xbox.rs | 120 +++++++++++++++++++++++++++++ bindings/rust/tests/mouse.rs | 3 +- 9 files changed, 221 insertions(+), 22 deletions(-) create mode 100644 bindings/rust/src/c_bindings.rs create mode 100644 bindings/rust/src/joypad_xbox.rs create mode 100644 bindings/rust/tests/joypad_xbox.rs diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index ce50bed..3a60998 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -22,3 +22,4 @@ pkg-config = "0.3.30" input = "0.9.0" rustix = { version = "0.38.18", features = ["fs"] } approx = "0.5.1" +sdl2 = "0.37.0" diff --git a/bindings/rust/src/c_bindings.rs b/bindings/rust/src/c_bindings.rs new file mode 100644 index 0000000..37c7abd --- /dev/null +++ b/bindings/rust/src/c_bindings.rs @@ -0,0 +1,5 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(dead_code)] +include!(concat!(env!("OUT_DIR"), "/bindings.rs")); diff --git a/bindings/rust/src/common.rs b/bindings/rust/src/common.rs index 0a562f0..798ec27 100644 --- a/bindings/rust/src/common.rs +++ b/bindings/rust/src/common.rs @@ -1,8 +1,9 @@ use std::ffi::{CString}; +use crate::c_bindings; #[allow(dead_code)] pub struct InputtinoDeviceDefinition { - pub def: super::InputtinoDeviceDefinition, + pub def: c_bindings::InputtinoDeviceDefinition, // Keep those around since we are passing them as pointers name: CString, phys: CString, @@ -14,7 +15,7 @@ impl InputtinoDeviceDefinition { let name = CString::new(name).unwrap(); let phys = CString::new(phys).unwrap(); let uniq = CString::new(uniq).unwrap(); - let def = super::InputtinoDeviceDefinition { + let def = c_bindings::InputtinoDeviceDefinition { name: name.as_ptr(), vendor_id: vendor_id, product_id: product_id, @@ -59,7 +60,7 @@ macro_rules! make_device { ($fn_call:expr, $device:expr) => { { let error_str = std::ptr::null_mut(); - let error_handler = super::InputtinoErrorHandler { + let error_handler = crate::c_bindings::InputtinoErrorHandler { eh: Some(error_handler_fn), user_data: error_str, }; diff --git a/bindings/rust/src/joypad_xbox.rs b/bindings/rust/src/joypad_xbox.rs new file mode 100644 index 0000000..5e88352 --- /dev/null +++ b/bindings/rust/src/joypad_xbox.rs @@ -0,0 +1,73 @@ +use std::ffi::{c_int, c_void}; +use crate::{get_nodes, make_device}; +use crate::common::{InputtinoDeviceDefinition, error_handler_fn}; +use crate::c_bindings::{inputtino_joypad_xone_create, inputtino_joypad_xone_destroy, inputtino_joypad_xone_get_nodes, inputtino_joypad_xone_set_on_rumble, inputtino_joypad_xone_set_pressed_buttons, inputtino_joypad_xone_set_stick, inputtino_joypad_xone_set_triggers}; + +// re-export INPUTTINO_JOYPAD_BTN and INPUTTINO_JOYPAD_STICK_POSITION +pub use crate::c_bindings::{INPUTTINO_JOYPAD_BTN, INPUTTINO_JOYPAD_STICK_POSITION}; + +pub struct InputtinoXOneJoypad { + joypad: *mut crate::c_bindings::InputtinoXOneJoypad, + on_rumble_fn: Box ()>, +} + +impl InputtinoXOneJoypad { + pub fn new(device: &InputtinoDeviceDefinition) -> Result { + unsafe { + let dev = make_device!(inputtino_joypad_xone_create, device); + match dev { + Ok(joypad) => { + Ok(InputtinoXOneJoypad { joypad, on_rumble_fn: Box::new(|_, _| {}) }) + } + Err(e) => Err(e), + } + } + } + + pub fn get_nodes(&self) -> Result, String> { + unsafe { + get_nodes!(inputtino_joypad_xone_get_nodes, self.joypad) + } + } + + pub fn set_pressed(&self, buttons: i32) { + unsafe { + inputtino_joypad_xone_set_pressed_buttons(self.joypad, buttons); + } + } + + pub fn set_triggers(&self, left_trigger: i16, right_trigger: i16) { + unsafe { + inputtino_joypad_xone_set_triggers(self.joypad, left_trigger, right_trigger); + } + } + + pub fn set_stick(&self, stick_type: INPUTTINO_JOYPAD_STICK_POSITION, x: i16, y: i16) { + unsafe { + inputtino_joypad_xone_set_stick(self.joypad, stick_type, x, y); + } + } + + pub fn set_on_rumble(&mut self, on_rumble_fn: impl FnMut(i32, i32) -> () + 'static) { + self.on_rumble_fn = Box::new(on_rumble_fn); + unsafe { + let state_ptr = self as *const _ as *mut c_void; + inputtino_joypad_xone_set_on_rumble(self.joypad, Some(on_rumble_c_fn), state_ptr); + } + } +} + +impl Drop for InputtinoXOneJoypad { + fn drop(&mut self) { + unsafe { + inputtino_joypad_xone_destroy(self.joypad); + } + } +} + +#[allow(dead_code)] +pub unsafe extern "C" fn on_rumble_c_fn(left_motor: c_int, right_motor: c_int, user_data: *mut ::core::ffi::c_void) { + println!("on_rumble_c_fn {:?}-{:?}", left_motor, right_motor); + let joypad: &mut InputtinoXOneJoypad = &mut *(user_data as *mut InputtinoXOneJoypad); + ((*joypad).on_rumble_fn)(left_motor, right_motor); +} diff --git a/bindings/rust/src/keyboard.rs b/bindings/rust/src/keyboard.rs index 7e3d0ed..d24902a 100644 --- a/bindings/rust/src/keyboard.rs +++ b/bindings/rust/src/keyboard.rs @@ -1,8 +1,9 @@ use crate::common::{error_handler_fn, InputtinoDeviceDefinition}; -use crate::{get_nodes, inputtino_keyboard_create, inputtino_keyboard_get_nodes, make_device}; +use crate::{get_nodes, make_device}; +use crate::c_bindings::{inputtino_keyboard_create, inputtino_keyboard_get_nodes, inputtino_keyboard_press, inputtino_keyboard_release, inputtino_keyboard_destroy}; pub struct InputtinoKeyboard { - kb: *mut super::InputtinoKeyboard, + kb: *mut crate::c_bindings::InputtinoKeyboard, } impl InputtinoKeyboard { @@ -24,13 +25,13 @@ impl InputtinoKeyboard { pub fn press_key(&self, key: i16) { unsafe { - super::inputtino_keyboard_press(self.kb, key); + inputtino_keyboard_press(self.kb, key); } } pub fn release_key(&self, key: i16) { unsafe { - super::inputtino_keyboard_release(self.kb, key); + inputtino_keyboard_release(self.kb, key); } } } @@ -38,7 +39,7 @@ impl InputtinoKeyboard { impl Drop for InputtinoKeyboard { fn drop(&mut self) { unsafe { - super::inputtino_keyboard_destroy(self.kb); + inputtino_keyboard_destroy(self.kb); } } } diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index 3ed6f65..8024e0b 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -1,9 +1,5 @@ -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] - -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); - pub mod common; pub mod mouse; pub mod keyboard; +pub mod joypad_xbox; +mod c_bindings; diff --git a/bindings/rust/src/mouse.rs b/bindings/rust/src/mouse.rs index 0f49db3..9ddc9b4 100644 --- a/bindings/rust/src/mouse.rs +++ b/bindings/rust/src/mouse.rs @@ -1,8 +1,11 @@ -use crate::{get_nodes, inputtino_mouse_create, inputtino_mouse_destroy, inputtino_mouse_get_nodes, inputtino_mouse_move, inputtino_mouse_move_absolute, inputtino_mouse_press_button, inputtino_mouse_release_button, inputtino_mouse_scroll_horizontal, inputtino_mouse_scroll_vertical, make_device}; +use crate::{get_nodes, make_device}; use crate::common::{InputtinoDeviceDefinition, error_handler_fn}; +use crate::c_bindings::{inputtino_mouse_create, inputtino_mouse_destroy, inputtino_mouse_get_nodes, inputtino_mouse_move, inputtino_mouse_move_absolute, inputtino_mouse_press_button, inputtino_mouse_release_button, inputtino_mouse_scroll_horizontal, inputtino_mouse_scroll_vertical}; + +pub use crate::c_bindings::{INPUTTINO_MOUSE_BUTTON}; pub struct InputtinoMouse { - mouse: *mut super::InputtinoMouse, + mouse: *mut crate::c_bindings::InputtinoMouse, } impl InputtinoMouse { @@ -34,13 +37,13 @@ impl InputtinoMouse { } } - pub fn press_button(&self, button: super::INPUTTINO_MOUSE_BUTTON) { + pub fn press_button(&self, button: INPUTTINO_MOUSE_BUTTON) { unsafe { inputtino_mouse_press_button(self.mouse, button); } } - pub fn release_button(&self, button: super::INPUTTINO_MOUSE_BUTTON) { + pub fn release_button(&self, button: INPUTTINO_MOUSE_BUTTON) { unsafe { inputtino_mouse_release_button(self.mouse, button); } @@ -77,7 +80,7 @@ mod tests { let device_name = CString::new("Rusty Mouse").unwrap(); let device_phys = CString::new("Rusty Mouse Phys").unwrap(); let device_uniq = CString::new("Rusty Mouse Uniq").unwrap(); - let def = crate::InputtinoDeviceDefinition { + let def = crate::c_bindings::InputtinoDeviceDefinition { name: device_name.as_ptr(), vendor_id: 0, product_id: 0, @@ -87,7 +90,7 @@ mod tests { }; // TODO: test this somehow let error_str = std::ptr::null_mut(); - let error_handler = crate::InputtinoErrorHandler { + let error_handler = crate::c_bindings::InputtinoErrorHandler { eh: Some(error_handler_fn), user_data: error_str, }; diff --git a/bindings/rust/tests/joypad_xbox.rs b/bindings/rust/tests/joypad_xbox.rs new file mode 100644 index 0000000..523853e --- /dev/null +++ b/bindings/rust/tests/joypad_xbox.rs @@ -0,0 +1,120 @@ +use inputtino_rs::common::InputtinoDeviceDefinition; +use inputtino_rs::joypad_xbox::{InputtinoXOneJoypad, INPUTTINO_JOYPAD_BTN, INPUTTINO_JOYPAD_STICK_POSITION}; + +#[test] +fn test_xbox_joypad() { + let device = InputtinoDeviceDefinition::new("Rusty XOne controller", 0x045e, 0x02dd, 0x0100, "00:11:22:33:44", "00:11:22:33:44"); + let mut joypad = InputtinoXOneJoypad::new(&device).unwrap(); + + let nodes = joypad.get_nodes().unwrap(); + { + assert_eq!(nodes.len(), 2); + assert!(nodes[0].starts_with("/dev/input/event")); + assert!(nodes[1].starts_with("/dev/input/js")); + } + + let sdl = sdl2::init().unwrap(); + let joystick_subsystem = sdl.game_controller().unwrap(); + let mut sdl_js = joystick_subsystem.open(0).unwrap(); + let mut event_pump = sdl.event_pump().unwrap(); + + for event in event_pump.poll_iter() { + match event { + sdl2::event::Event::JoyDeviceAdded { which, .. } => { + assert_eq!(which, 0); + } + sdl2::event::Event::ControllerDeviceAdded { which, .. } => { + assert_eq!(which, 0); + } + _ => panic!("Unexpected event : {:?}", event), + } + } + + assert_eq!(sdl_js.name(), "Xbox One Controller"); + assert!(sdl_js.has_rumble()); + + { + joypad.set_pressed(INPUTTINO_JOYPAD_BTN::A as i32); + for event in event_pump.wait_timeout_iter(50) { + match event { + sdl2::event::Event::ControllerButtonDown { button, .. } => { + assert_eq!(button, sdl2::controller::Button::A); + } + sdl2::event::Event::JoyButtonDown { button_idx, .. } => { + assert_eq!(button_idx, sdl2::controller::Button::A as u8); + break; + } + _ => panic!("Unexpected event : {:?}", event), + } + } + } + + { + joypad.set_triggers(0, 0); + for event in event_pump.wait_timeout_iter(50) { + match event { + sdl2::event::Event::ControllerAxisMotion { axis, value, .. } => { + assert_eq!(axis, sdl2::controller::Axis::TriggerLeft); + assert_eq!(value, 0); + } + sdl2::event::Event::JoyAxisMotion { axis_idx, value, .. } => { + assert_eq!(axis_idx, sdl2::controller::Axis::TriggerLeft as u8); + assert_eq!(value, 0); + break; + } + _ => panic!("Unexpected event : {:?}", event), + } + } + } + + { + joypad.set_stick(INPUTTINO_JOYPAD_STICK_POSITION::LS, 0, 0); + for event in event_pump.wait_timeout_iter(50) { + match event { + sdl2::event::Event::ControllerAxisMotion { axis, value, .. } => { + assert_eq!(axis, sdl2::controller::Axis::LeftX); + assert_eq!(value, 0); + } + sdl2::event::Event::JoyAxisMotion { axis_idx, value, .. } => { + assert_eq!(axis_idx, sdl2::controller::Axis::LeftX as u8); + assert_eq!(value, 0); + break; + } + _ => panic!("Unexpected event : {:?}", event), + } + } + } + + { + joypad.set_stick(INPUTTINO_JOYPAD_STICK_POSITION::RS, 0, 0); + for event in event_pump.wait_timeout_iter(50) { + match event { + sdl2::event::Event::ControllerAxisMotion { axis, value, .. } => { + assert_eq!(axis, sdl2::controller::Axis::RightX); + assert_eq!(value, 0); + } + sdl2::event::Event::JoyAxisMotion { axis_idx, value, .. } => { + assert_eq!(axis_idx, sdl2::controller::Axis::RightX as u8); + assert_eq!(value, 0); + break; + } + _ => panic!("Unexpected event : {:?}", event), + } + } + } + + { + joypad.set_on_rumble(move |left, right| { + assert_eq!(left, 100); + assert_eq!(right, 200); + }); + let res = sdl_js.set_rumble(100, 200, 150); + assert!(res.is_ok()); + std::thread::sleep(std::time::Duration::from_millis(25)); + joypad.set_on_rumble(move |left, right| { + assert_eq!(left, 0); + assert_eq!(right, 0); + }); + std::thread::sleep(std::time::Duration::from_millis(125)); + } +} diff --git a/bindings/rust/tests/mouse.rs b/bindings/rust/tests/mouse.rs index a5484f0..dfff4e2 100644 --- a/bindings/rust/tests/mouse.rs +++ b/bindings/rust/tests/mouse.rs @@ -2,8 +2,7 @@ extern crate approx; use inputtino_rs::{ - INPUTTINO_MOUSE_BUTTON, - mouse::InputtinoMouse, + mouse::{InputtinoMouse, INPUTTINO_MOUSE_BUTTON}, common::InputtinoDeviceDefinition, }; use input::{Event, Libinput}; From 866c3c2760ee007b69bb5782e204e543730ed1c6 Mon Sep 17 00:00:00 2001 From: ABeltramo Date: Tue, 6 Aug 2024 20:38:40 +0100 Subject: [PATCH 10/10] feat: added switch joypad --- bindings/rust/Cargo.toml | 1 + bindings/rust/src/joypad_switch.rs | 72 +++++++++++ bindings/rust/src/joypad_xbox.rs | 1 - bindings/rust/src/lib.rs | 3 + .../rust/tests/{joypad_xbox.rs => joypads.rs} | 122 ++++++++++++++++++ 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 bindings/rust/src/joypad_switch.rs rename bindings/rust/tests/{joypad_xbox.rs => joypads.rs} (50%) diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml index 3a60998..8e85b06 100644 --- a/bindings/rust/Cargo.toml +++ b/bindings/rust/Cargo.toml @@ -23,3 +23,4 @@ input = "0.9.0" rustix = { version = "0.38.18", features = ["fs"] } approx = "0.5.1" sdl2 = "0.37.0" +serial_test = "3.1.1" diff --git a/bindings/rust/src/joypad_switch.rs b/bindings/rust/src/joypad_switch.rs new file mode 100644 index 0000000..af04003 --- /dev/null +++ b/bindings/rust/src/joypad_switch.rs @@ -0,0 +1,72 @@ +use std::ffi::{c_int, c_void}; +use crate::c_bindings::{inputtino_joypad_switch_create, inputtino_joypad_switch_destroy, inputtino_joypad_switch_get_nodes, inputtino_joypad_switch_set_on_rumble, inputtino_joypad_switch_set_pressed_buttons, inputtino_joypad_switch_set_stick, inputtino_joypad_switch_set_triggers}; +use crate::common::{InputtinoDeviceDefinition, error_handler_fn}; +use crate::{get_nodes, make_device}; + +// re-export INPUTTINO_JOYPAD_BTN and INPUTTINO_JOYPAD_STICK_POSITION +pub use crate::c_bindings::{INPUTTINO_JOYPAD_BTN, INPUTTINO_JOYPAD_STICK_POSITION}; + +pub struct InputtinoSwitchJoypad { + joypad: *mut crate::c_bindings::InputtinoSwitchJoypad, + on_rumble_fn: Box ()>, +} + +impl InputtinoSwitchJoypad { + pub fn new(device: &InputtinoDeviceDefinition) -> Result { + unsafe { + let dev = make_device!(inputtino_joypad_switch_create, device); + match dev { + Ok(joypad) => { + Ok(InputtinoSwitchJoypad { joypad, on_rumble_fn: Box::new(|_, _| {}) }) + } + Err(e) => Err(e), + } + } + } + + pub fn get_nodes(&self) -> Result, String> { + unsafe { + get_nodes!(inputtino_joypad_switch_get_nodes, self.joypad) + } + } + + pub fn set_pressed(&self, buttons: i32) { + unsafe { + inputtino_joypad_switch_set_pressed_buttons(self.joypad, buttons); + } + } + + pub fn set_triggers(&self, left_trigger: i16, right_trigger: i16) { + unsafe { + inputtino_joypad_switch_set_triggers(self.joypad, left_trigger, right_trigger); + } + } + + pub fn set_stick(&self, stick_type: INPUTTINO_JOYPAD_STICK_POSITION, x: i16, y: i16) { + unsafe { + inputtino_joypad_switch_set_stick(self.joypad, stick_type, x, y); + } + } + + pub fn set_on_rumble(&mut self, on_rumble_fn: impl FnMut(i32, i32) -> () + 'static) { + self.on_rumble_fn = Box::new(on_rumble_fn); + unsafe { + let state_ptr = self as *const _ as *mut c_void; + inputtino_joypad_switch_set_on_rumble(self.joypad, Some(on_rumble_c_fn), state_ptr); + } + } +} + +impl Drop for InputtinoSwitchJoypad { + fn drop(&mut self) { + unsafe { + inputtino_joypad_switch_destroy(self.joypad); + } + } +} + +#[allow(dead_code)] +pub unsafe extern "C" fn on_rumble_c_fn(left_motor: c_int, right_motor: c_int, user_data: *mut ::core::ffi::c_void) { + let joypad: &mut InputtinoSwitchJoypad = &mut *(user_data as *mut InputtinoSwitchJoypad); + ((*joypad).on_rumble_fn)(left_motor, right_motor); +} diff --git a/bindings/rust/src/joypad_xbox.rs b/bindings/rust/src/joypad_xbox.rs index 5e88352..f5b5dd1 100644 --- a/bindings/rust/src/joypad_xbox.rs +++ b/bindings/rust/src/joypad_xbox.rs @@ -67,7 +67,6 @@ impl Drop for InputtinoXOneJoypad { #[allow(dead_code)] pub unsafe extern "C" fn on_rumble_c_fn(left_motor: c_int, right_motor: c_int, user_data: *mut ::core::ffi::c_void) { - println!("on_rumble_c_fn {:?}-{:?}", left_motor, right_motor); let joypad: &mut InputtinoXOneJoypad = &mut *(user_data as *mut InputtinoXOneJoypad); ((*joypad).on_rumble_fn)(left_motor, right_motor); } diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs index 8024e0b..2be6c25 100644 --- a/bindings/rust/src/lib.rs +++ b/bindings/rust/src/lib.rs @@ -2,4 +2,7 @@ pub mod common; pub mod mouse; pub mod keyboard; pub mod joypad_xbox; +pub mod joypad_switch; + +// Private low level automatic c bindings mod c_bindings; diff --git a/bindings/rust/tests/joypad_xbox.rs b/bindings/rust/tests/joypads.rs similarity index 50% rename from bindings/rust/tests/joypad_xbox.rs rename to bindings/rust/tests/joypads.rs index 523853e..2516e8e 100644 --- a/bindings/rust/tests/joypad_xbox.rs +++ b/bindings/rust/tests/joypads.rs @@ -1,7 +1,10 @@ +use serial_test::serial; use inputtino_rs::common::InputtinoDeviceDefinition; +use inputtino_rs::joypad_switch::InputtinoSwitchJoypad; use inputtino_rs::joypad_xbox::{InputtinoXOneJoypad, INPUTTINO_JOYPAD_BTN, INPUTTINO_JOYPAD_STICK_POSITION}; #[test] +#[serial] fn test_xbox_joypad() { let device = InputtinoDeviceDefinition::new("Rusty XOne controller", 0x045e, 0x02dd, 0x0100, "00:11:22:33:44", "00:11:22:33:44"); let mut joypad = InputtinoXOneJoypad::new(&device).unwrap(); @@ -118,3 +121,122 @@ fn test_xbox_joypad() { std::thread::sleep(std::time::Duration::from_millis(125)); } } + +#[test] +#[serial] +fn test_switch_joypad() { + let device = InputtinoDeviceDefinition::new("Rusty Switch controller", 0x045e, 0x02dd, 0x0100, "00:11:22:33:44", "00:11:22:33:44"); + let mut joypad = InputtinoSwitchJoypad::new(&device).unwrap(); + + let nodes = joypad.get_nodes().unwrap(); + { + assert_eq!(nodes.len(), 2); + assert!(nodes[0].starts_with("/dev/input/event")); + assert!(nodes[1].starts_with("/dev/input/js")); + } + + let sdl = sdl2::init().unwrap(); + let joystick_subsystem = sdl.game_controller().unwrap(); + let mut sdl_js = joystick_subsystem.open(0).unwrap(); + let mut event_pump = sdl.event_pump().unwrap(); + + for event in event_pump.poll_iter() { + match event { + sdl2::event::Event::JoyDeviceAdded { which, .. } => { + assert_eq!(which, 0); + } + sdl2::event::Event::ControllerDeviceAdded { which, .. } => { + assert_eq!(which, 0); + } + _ => panic!("Unexpected event : {:?}", event), + } + } + + assert_eq!(sdl_js.name(), "Xbox One Controller"); + assert!(sdl_js.has_rumble()); + + { + joypad.set_pressed(INPUTTINO_JOYPAD_BTN::A as i32); + for event in event_pump.wait_timeout_iter(50) { + match event { + sdl2::event::Event::ControllerButtonDown { button, .. } => { + assert_eq!(button, sdl2::controller::Button::B); + } + sdl2::event::Event::JoyButtonDown { button_idx, .. } => { + assert_eq!(button_idx, sdl2::controller::Button::B as u8); + break; + } + _ => panic!("Unexpected event : {:?}", event), + } + } + } + + { + joypad.set_triggers(0, 0); + for event in event_pump.wait_timeout_iter(50) { + match event { + sdl2::event::Event::ControllerAxisMotion { axis, value, .. } => { + assert_eq!(axis, sdl2::controller::Axis::TriggerLeft); + assert_eq!(value, 0); + } + sdl2::event::Event::JoyAxisMotion { axis_idx, value, .. } => { + assert_eq!(axis_idx, sdl2::controller::Axis::TriggerLeft as u8); + assert_eq!(value, 0); + break; + } + _ => panic!("Unexpected event : {:?}", event), + } + } + } + + { + joypad.set_stick(INPUTTINO_JOYPAD_STICK_POSITION::LS, 0, 0); + for event in event_pump.wait_timeout_iter(50) { + match event { + sdl2::event::Event::ControllerAxisMotion { axis, value, .. } => { + assert_eq!(axis, sdl2::controller::Axis::LeftX); + assert_eq!(value, 0); + } + sdl2::event::Event::JoyAxisMotion { axis_idx, value, .. } => { + assert_eq!(axis_idx, sdl2::controller::Axis::LeftX as u8); + assert_eq!(value, 0); + break; + } + _ => panic!("Unexpected event : {:?}", event), + } + } + } + + { + joypad.set_stick(INPUTTINO_JOYPAD_STICK_POSITION::RS, 0, 0); + for event in event_pump.wait_timeout_iter(50) { + match event { + sdl2::event::Event::ControllerAxisMotion { axis, value, .. } => { + assert_eq!(axis, sdl2::controller::Axis::RightX); + assert_eq!(value, 0); + } + sdl2::event::Event::JoyAxisMotion { axis_idx, value, .. } => { + assert_eq!(axis_idx, sdl2::controller::Axis::RightX as u8); + assert_eq!(value, 0); + break; + } + _ => panic!("Unexpected event : {:?}", event), + } + } + } + + { + joypad.set_on_rumble(move |left, right| { + assert_eq!(left, 100); + assert_eq!(right, 200); + }); + let res = sdl_js.set_rumble(100, 200, 150); + assert!(res.is_ok()); + std::thread::sleep(std::time::Duration::from_millis(25)); + joypad.set_on_rumble(move |left, right| { + assert_eq!(left, 0); + assert_eq!(right, 0); + }); + std::thread::sleep(std::time::Duration::from_millis(125)); + } +}