#![doc = include_str!("../README.md")]
use std::{
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Duration,
};
use arci::{gamepad::*, *};
use indexmap::IndexMap;
use schemars::{gen::SchemaGenerator, schema::Schema, JsonSchema};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use tracing::{debug, error, info};
#[serde_as]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(deny_unknown_fields)]
pub struct Map {
#[serde_as(as = "Vec<(_, _)>")]
#[serde(default = "default_button_map")]
button_map: IndexMap<gilrs::Button, Button>,
#[serde_as(as = "Vec<(_, _)>")]
#[serde(default = "default_axis_map")]
axis_map: IndexMap<gilrs::Axis, Axis>,
#[serde_as(as = "Vec<(_, _)>")]
#[serde(default = "default_axis_value_map")]
axis_value_map: IndexMap<Axis, f64>,
}
impl Map {
pub fn new() -> Self {
Self {
button_map: default_button_map(),
axis_map: default_axis_map(),
axis_value_map: default_axis_value_map(),
}
}
pub fn new_playstation() -> Self {
let mut button_map = default_button_map();
button_map.insert(gilrs::Button::East, Button::South);
button_map.insert(gilrs::Button::C, Button::East);
button_map.insert(gilrs::Button::North, Button::North);
button_map.insert(gilrs::Button::South, Button::West);
button_map.insert(gilrs::Button::West, Button::LeftTrigger);
button_map.insert(gilrs::Button::LeftTrigger, Button::LeftTrigger2);
button_map.insert(gilrs::Button::Z, Button::RightTrigger);
button_map.insert(gilrs::Button::RightTrigger, Button::RightTrigger2);
button_map.insert(gilrs::Button::LeftTrigger2, Button::Select);
button_map.insert(gilrs::Button::RightTrigger2, Button::Start);
button_map.insert(gilrs::Button::Select, Button::LeftThumb);
button_map.insert(gilrs::Button::Start, Button::RightThumb);
let mut axis_map = default_axis_map();
axis_map.insert(gilrs::Axis::LeftZ, Axis::RightStickX);
axis_map.insert(gilrs::Axis::RightZ, Axis::RightStickY);
let mut axis_value_map = default_axis_value_map();
axis_value_map.insert(Axis::RightStickX, -1.0);
axis_value_map.insert(Axis::RightStickY, -1.0);
Self {
button_map,
axis_map,
axis_value_map,
}
}
fn convert_button(&self, b: gilrs::Button) -> Button {
if let Some(e) = self.button_map.get(&b) {
debug!("convert_button {b:?} -> {e:?}");
*e
} else {
debug!("unknown map {b:?}");
Button::Unknown
}
}
fn convert_axis(&self, a: gilrs::Axis, v: f32) -> (Axis, f64) {
if let Some(e) = self.axis_map.get(&a) {
debug!("convert_axis {a:?} -> {e:?}");
(*e, v as f64 * self.axis_value_map.get(e).unwrap_or(&1.0))
} else {
debug!("unknown map {a:?}");
(Axis::Unknown, 0.0)
}
}
fn convert_event(&self, e: gilrs::EventType) -> Option<GamepadEvent> {
match e {
gilrs::EventType::ButtonPressed(b, _c) => {
Some(GamepadEvent::ButtonPressed(self.convert_button(b)))
}
gilrs::EventType::ButtonReleased(b, _c) => {
Some(GamepadEvent::ButtonReleased(self.convert_button(b)))
}
gilrs::EventType::AxisChanged(a, v, _c) => {
let (axis, value) = self.convert_axis(a, v);
Some(GamepadEvent::AxisChanged(axis, value))
}
_ => None,
}
}
}
impl Default for Map {
fn default() -> Self {
Self::new()
}
}
impl JsonSchema for Map {
fn schema_name() -> String {
"Map".to_string()
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
#[allow(dead_code)]
#[derive(JsonSchema)]
enum GilrsButton {
South,
East,
North,
West,
C,
Z,
LeftTrigger,
LeftTrigger2,
RightTrigger,
RightTrigger2,
Select,
Start,
Mode,
LeftThumb,
RightThumb,
DPadUp,
DPadDown,
DPadLeft,
DPadRight,
Unknown,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
enum GilrsAxis {
LeftStickX,
LeftStickY,
LeftZ,
RightStickX,
RightStickY,
RightZ,
DPadX,
DPadY,
Unknown,
}
#[allow(dead_code)]
#[derive(JsonSchema)]
struct MapRepr {
button_map: Vec<(GilrsButton, Button)>,
axis_map: Vec<(GilrsAxis, Axis)>,
axis_value_map: Vec<(Axis, f64)>,
}
MapRepr::json_schema(gen)
}
}
fn default_button_map() -> IndexMap<gilrs::Button, Button> {
let mut button_map = IndexMap::new();
button_map.insert(gilrs::Button::South, Button::South);
button_map.insert(gilrs::Button::East, Button::East);
button_map.insert(gilrs::Button::North, Button::North);
button_map.insert(gilrs::Button::West, Button::West);
button_map.insert(gilrs::Button::LeftTrigger, Button::LeftTrigger);
button_map.insert(gilrs::Button::LeftTrigger2, Button::LeftTrigger2);
button_map.insert(gilrs::Button::RightTrigger, Button::RightTrigger);
button_map.insert(gilrs::Button::RightTrigger2, Button::RightTrigger2);
button_map.insert(gilrs::Button::Select, Button::Select);
button_map.insert(gilrs::Button::Start, Button::Start);
button_map.insert(gilrs::Button::Mode, Button::Mode);
button_map.insert(gilrs::Button::LeftThumb, Button::LeftThumb);
button_map.insert(gilrs::Button::RightThumb, Button::RightThumb);
button_map.insert(gilrs::Button::DPadUp, Button::DPadUp);
button_map.insert(gilrs::Button::DPadDown, Button::DPadDown);
button_map.insert(gilrs::Button::DPadLeft, Button::DPadLeft);
button_map.insert(gilrs::Button::DPadRight, Button::DPadRight);
button_map
}
fn default_axis_map() -> IndexMap<gilrs::Axis, Axis> {
let mut axis_map = IndexMap::new();
axis_map.insert(gilrs::Axis::LeftStickX, Axis::LeftStickX);
axis_map.insert(gilrs::Axis::LeftStickY, Axis::LeftStickY);
axis_map.insert(gilrs::Axis::RightStickX, Axis::RightStickX);
axis_map.insert(gilrs::Axis::RightStickY, Axis::RightStickY);
axis_map.insert(gilrs::Axis::DPadX, Axis::DPadX);
axis_map.insert(gilrs::Axis::DPadY, Axis::DPadY);
axis_map
}
fn default_axis_value_map() -> IndexMap<Axis, f64> {
let mut axis_value_map = IndexMap::new();
axis_value_map.insert(Axis::RightStickX, -1.0);
axis_value_map.insert(Axis::LeftStickX, -1.0);
axis_value_map
}
#[derive(Debug)]
pub struct GilGamepad {
rx: flume::Receiver<GamepadEvent>,
is_running: Arc<AtomicBool>,
}
impl GilGamepad {
pub fn new(id: usize, map: Map) -> Self {
let (tx, rx) = flume::unbounded();
let is_running = Arc::new(AtomicBool::new(true));
let is_running_cloned = is_running.clone();
std::thread::spawn(move || {
let mut gil = gilrs::Gilrs::new().unwrap();
#[cfg_attr(target_os = "macos", allow(unused_mut))]
let mut selected_gamepad_id = None;
#[cfg(not(target_os = "macos"))]
{
let mut is_found = false;
for (connected_id, gamepad) in gil.gamepads() {
info!("{} is {:?}", gamepad.name(), gamepad.power_info());
if id == Into::<usize>::into(connected_id) {
is_found = true;
selected_gamepad_id = Some(connected_id);
}
}
if !is_found {
panic!("No Gamepad id={id} is found");
}
}
let mut is_connected = true;
tx.send(GamepadEvent::Connected).unwrap();
while is_running_cloned.load(Ordering::Relaxed) {
if let Some(gamepad_id) = selected_gamepad_id {
let gamepad = gil.gamepad(gamepad_id);
if is_connected && !gamepad.is_connected() {
error!("gamepad [{}] is disconnected", gamepad.name());
is_connected = false;
tx.send(GamepadEvent::Disconnected).unwrap();
} else if !is_connected && gamepad.is_connected() {
info!("gamepad [{}] is connected", gamepad.name());
is_connected = true;
tx.send(GamepadEvent::Connected).unwrap();
}
}
match gil.next_event() {
Some(gilrs::Event {
id: recv_id, event, ..
}) => {
if id == Into::<usize>::into(recv_id) {
if let Some(e) = map.convert_event(event) {
tx.send(e).unwrap();
}
}
}
None => {
std::thread::sleep(Duration::from_secs_f64(0.01));
}
}
}
});
Self { rx, is_running }
}
pub fn new_from_config(config: GilGamepadConfig) -> Self {
Self::new(config.device_id, config.map)
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Default, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GilGamepadConfig {
#[serde(default)]
device_id: usize,
#[serde(default)]
map: Map,
}
#[async_trait]
impl Gamepad for GilGamepad {
async fn next_event(&self) -> GamepadEvent {
match self.rx.recv_async().await {
Ok(e) => e,
Err(e) => {
error!("recv error: {e}");
GamepadEvent::Unknown
}
}
}
fn stop(&self) {
self.is_running.store(false, Ordering::Relaxed);
}
}
impl Drop for GilGamepad {
fn drop(&mut self) {
self.stop();
}
}
#[cfg(test)]
mod test {
use assert_approx_eq::assert_approx_eq;
use super::*;
#[test]
fn test_playstation_map() {
let m = Map::new_playstation();
assert_eq!(
m.convert_button(gilrs::Button::North),
arci::gamepad::Button::North
);
assert_eq!(
m.convert_button(gilrs::Button::South),
arci::gamepad::Button::West
);
let (axis, value) = m.convert_axis(gilrs::Axis::RightStickX, 0.2);
assert_eq!(axis, arci::gamepad::Axis::RightStickX);
assert_approx_eq!(value, -0.2);
let (a, v) = m.convert_axis(gilrs::Axis::RightZ, 0.1);
assert_eq!(a, arci::gamepad::Axis::RightStickY);
assert!((v - -0.1).abs() < 0.00001);
}
#[test]
fn test_default_map() {
let m = Map::default();
assert_eq!(
m.convert_button(gilrs::Button::North),
arci::gamepad::Button::North
);
assert_eq!(
m.convert_button(gilrs::Button::South),
arci::gamepad::Button::South
);
let (a, v) = m.convert_axis(gilrs::Axis::RightStickY, 0.1);
assert_eq!(a, arci::gamepad::Axis::RightStickY);
assert!((v - 0.1).abs() < 0.00001);
}
#[test]
fn test_make_map() {
let m = Map {
button_map: IndexMap::new(),
axis_map: IndexMap::new(),
axis_value_map: IndexMap::new(),
};
assert_eq!(
m.convert_button(gilrs::Button::North),
arci::gamepad::Button::Unknown,
);
let (a, v) = m.convert_axis(gilrs::Axis::RightStickY, 0.1);
assert_eq!(a, arci::gamepad::Axis::Unknown);
assert!((v - 0.0).abs() < 0.00001);
}
}