Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rust bindings #11

Open
wants to merge 10 commits into
base: stable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@
!include
!cmake/
!docker/startup.sh
!share/

# Rust bindings
!bindings/rust/
7 changes: 7 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 ()

Expand Down
1 change: 1 addition & 0 deletions bindings/rust/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
target/
13 changes: 12 additions & 1 deletion bindings/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -13,3 +16,11 @@ path = "src/lib.rs"
[build-dependencies]
bindgen = "0.69.4"
cmake = "0.1"
pkg-config = "0.3.30"

[dev-dependencies]
input = "0.9.0"
rustix = { version = "0.38.18", features = ["fs"] }
approx = "0.5.1"
sdl2 = "0.37.0"
serial_test = "3.1.1"
82 changes: 43 additions & 39 deletions bindings/rust/build.rs
Original file line number Diff line number Diff line change
@@ -1,64 +1,68 @@
extern crate bindgen;
extern crate pkg_config;

use std::env;
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 {
println!("cargo:rustc-link-lib=evdev");
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!");
}
5 changes: 5 additions & 0 deletions bindings/rust/src/c_bindings.rs
Original file line number Diff line number Diff line change
@@ -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"));
76 changes: 76 additions & 0 deletions bindings/rust/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use std::ffi::{CString};
use crate::c_bindings;

#[allow(dead_code)]
pub struct InputtinoDeviceDefinition {
pub def: c_bindings::InputtinoDeviceDefinition,
// Keep those around since we are passing them as pointers
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 = CString::new(name).unwrap();
let phys = CString::new(phys).unwrap();
let uniq = CString::new(uniq).unwrap();
let def = c_bindings::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, name, phys, uniq }
}
}

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 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 = crate::c_bindings::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)
}
}
};
}
72 changes: 72 additions & 0 deletions bindings/rust/src/joypad_switch.rs
Original file line number Diff line number Diff line change
@@ -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<dyn FnMut(i32, i32) -> ()>,
}

impl InputtinoSwitchJoypad {
pub fn new(device: &InputtinoDeviceDefinition) -> Result<Self, String> {
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<Vec<String>, 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);
}
72 changes: 72 additions & 0 deletions bindings/rust/src/joypad_xbox.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
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<dyn FnMut(i32, i32) -> ()>,
}

impl InputtinoXOneJoypad {
pub fn new(device: &InputtinoDeviceDefinition) -> Result<Self, String> {
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<Vec<String>, 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) {
let joypad: &mut InputtinoXOneJoypad = &mut *(user_data as *mut InputtinoXOneJoypad);
((*joypad).on_rumble_fn)(left_motor, right_motor);
}
Loading
Loading