#![cfg_attr(target_os = "windows", allow(dead_code))]
mod parser;
use crate::ev::{self, Axis, AxisOrBtn, Button};
use crate::utils::PATH_SEPARATOR;
use gilrs_core::native_ev_codes as nec;
use gilrs_core::EvCode;
use std::collections::HashMap;
use std::env;
use std::error::Error;
use std::fmt::{Display, Formatter, Result as FmtResult, Write as _};
use fnv::FnvHashMap;
use uuid::Uuid;
use vec_map::VecMap;
use self::parser::{Error as ParserError, ErrorKind as ParserErrorKind, Parser, Token};
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd"))]
const SDL_PLATFORM_NAME: &str = "Linux";
#[cfg(target_os = "macos")]
const SDL_PLATFORM_NAME: &str = "Mac OS X";
#[cfg(target_os = "windows")]
const SDL_PLATFORM_NAME: &str = "Windows";
#[cfg(all(
not(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd")),
not(target_os = "macos"),
not(target_os = "windows")
))]
const SDL_PLATFORM_NAME: &str = "Unknown";
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Mapping {
mappings: FnvHashMap<EvCode, AxisOrBtn>,
name: String,
default: bool,
hats_mapped: u8,
}
impl Mapping {
pub fn new() -> Self {
Mapping {
mappings: FnvHashMap::default(),
name: String::new(),
default: false,
hats_mapped: 0,
}
}
pub fn default(gamepad: &gilrs_core::Gamepad) -> Self {
use self::Axis as Ax;
use self::AxisOrBtn::*;
macro_rules! fnv_map {
( $( $key:expr => $elem:expr ),* ) => {
{
let mut map = FnvHashMap::default();
$(
map.insert($key, $elem);
)*
map
}
};
}
let mut mappings = fnv_map![
nec::BTN_SOUTH => Btn(Button::South),
nec::BTN_EAST => Btn(Button::East),
nec::BTN_C => Btn(Button::C),
nec::BTN_NORTH => Btn(Button::North),
nec::BTN_WEST => Btn(Button::West),
nec::BTN_Z => Btn(Button::Z),
nec::BTN_LT => Btn(Button::LeftTrigger),
nec::BTN_RT => Btn(Button::RightTrigger),
nec::BTN_LT2 => Btn(Button::LeftTrigger2),
nec::BTN_RT2 => Btn(Button::RightTrigger2),
nec::BTN_SELECT => Btn(Button::Select),
nec::BTN_START => Btn(Button::Start),
nec::BTN_MODE => Btn(Button::Mode),
nec::BTN_LTHUMB => Btn(Button::LeftThumb),
nec::BTN_RTHUMB => Btn(Button::RightThumb),
nec::BTN_DPAD_UP => Btn(Button::DPadUp),
nec::BTN_DPAD_DOWN => Btn(Button::DPadDown),
nec::BTN_DPAD_LEFT => Btn(Button::DPadLeft),
nec::BTN_DPAD_RIGHT => Btn(Button::DPadRight),
nec::AXIS_LT => Btn(Button::LeftTrigger),
nec::AXIS_RT => Btn(Button::RightTrigger),
nec::AXIS_LT2 => Btn(Button::LeftTrigger2),
nec::AXIS_RT2 => Btn(Button::RightTrigger2),
nec::AXIS_LSTICKX => Axis(Ax::LeftStickX),
nec::AXIS_LSTICKY => Axis(Ax::LeftStickY),
nec::AXIS_LEFTZ => Axis(Ax::LeftZ),
nec::AXIS_RSTICKX => Axis(Ax::RightStickX),
nec::AXIS_RSTICKY => Axis(Ax::RightStickY),
nec::AXIS_RIGHTZ => Axis(Ax::RightZ),
nec::AXIS_DPADX => Axis(Ax::DPadX),
nec::AXIS_DPADY => Axis(Ax::DPadY)
];
let axes = [
nec::AXIS_DPADX,
nec::AXIS_DPADY,
nec::AXIS_LEFTZ,
nec::AXIS_LSTICKX,
nec::AXIS_LSTICKY,
nec::AXIS_RSTICKX,
nec::AXIS_RSTICKY,
nec::AXIS_LT,
nec::AXIS_LT2,
nec::AXIS_RT,
nec::AXIS_RT2,
nec::AXIS_RIGHTZ,
];
let btns = [
nec::BTN_SOUTH,
nec::BTN_NORTH,
nec::BTN_WEST,
nec::BTN_WEST,
nec::BTN_C,
nec::BTN_Z,
nec::BTN_LT,
nec::BTN_LT2,
nec::BTN_RT,
nec::BTN_RT2,
nec::BTN_SELECT,
nec::BTN_START,
nec::BTN_MODE,
nec::BTN_LTHUMB,
nec::BTN_RTHUMB,
nec::BTN_DPAD_DOWN,
nec::BTN_DPAD_LEFT,
nec::BTN_DPAD_RIGHT,
nec::BTN_DPAD_UP,
];
for axis in &axes {
if !gamepad.axes().contains(axis) {
mappings.remove(axis);
}
}
for btn in &btns {
if !gamepad.buttons().contains(btn) {
mappings.remove(btn);
}
}
Mapping {
mappings,
name: String::new(),
default: true,
hats_mapped: 0,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn from_data(
data: &MappingData,
buttons: &[EvCode],
axes: &[EvCode],
name: &str,
uuid: Uuid,
) -> Result<(Self, String), MappingError> {
use crate::constants::*;
if !Self::is_name_valid(name) {
return Err(MappingError::InvalidName);
}
let mut mappings = FnvHashMap::default();
let mut sdl_mappings = format!("{},{},", uuid.as_simple(), name);
{
let mut add_button = |ident, ev_code, mapped_btn| {
Self::add_button(
ident,
ev_code,
mapped_btn,
buttons,
&mut sdl_mappings,
&mut mappings,
)
};
for (button, &ev_code) in &data.buttons {
match button as u16 {
BTN_SOUTH => add_button("a", ev_code, Button::South)?,
BTN_EAST => add_button("b", ev_code, Button::East)?,
BTN_WEST => add_button("x", ev_code, Button::West)?,
BTN_NORTH => add_button("y", ev_code, Button::North)?,
BTN_LT => add_button("leftshoulder", ev_code, Button::LeftTrigger)?,
BTN_RT => add_button("rightshoulder", ev_code, Button::RightTrigger)?,
BTN_LT2 => add_button("lefttrigger", ev_code, Button::LeftTrigger2)?,
BTN_RT2 => add_button("righttrigger", ev_code, Button::RightTrigger2)?,
BTN_SELECT => add_button("back", ev_code, Button::Select)?,
BTN_START => add_button("start", ev_code, Button::Start)?,
BTN_MODE => add_button("guide", ev_code, Button::Mode)?,
BTN_LTHUMB => add_button("leftstick", ev_code, Button::LeftThumb)?,
BTN_RTHUMB => add_button("rightstick", ev_code, Button::RightThumb)?,
BTN_DPAD_UP => add_button("dpup", ev_code, Button::DPadUp)?,
BTN_DPAD_DOWN => add_button("dpdown", ev_code, Button::DPadDown)?,
BTN_DPAD_LEFT => add_button("dpleft", ev_code, Button::DPadLeft)?,
BTN_DPAD_RIGHT => add_button("dpright", ev_code, Button::DPadRight)?,
BTN_C => add_button("c", ev_code, Button::C)?,
BTN_Z => add_button("z", ev_code, Button::Z)?,
BTN_UNKNOWN => return Err(MappingError::UnknownElement),
_ => unreachable!(),
}
}
}
{
let mut add_axis = |ident, ev_code, mapped_axis| {
Self::add_axis(
ident,
ev_code,
mapped_axis,
axes,
&mut sdl_mappings,
&mut mappings,
)
};
for (axis, &ev_code) in &data.axes {
match axis as u16 {
AXIS_LSTICKX => add_axis("leftx", ev_code, Axis::LeftStickX)?,
AXIS_LSTICKY => add_axis("lefty", ev_code, Axis::LeftStickY)?,
AXIS_RSTICKX => add_axis("rightx", ev_code, Axis::RightStickX)?,
AXIS_RSTICKY => add_axis("righty", ev_code, Axis::RightStickY)?,
AXIS_LEFTZ => add_axis("leftz", ev_code, Axis::LeftZ)?,
AXIS_RIGHTZ => add_axis("rightz", ev_code, Axis::RightZ)?,
AXIS_UNKNOWN => return Err(MappingError::UnknownElement),
_ => unreachable!(),
}
}
}
let mapping = Mapping {
mappings,
name: name.to_owned(),
default: false,
hats_mapped: 0,
};
Ok((mapping, sdl_mappings))
}
pub fn parse_sdl_mapping(
line: &str,
buttons: &[EvCode],
axes: &[EvCode],
) -> Result<Self, ParseSdlMappingError> {
let mut mapping = Mapping::new();
let mut parser = Parser::new(line);
let mut uuid: Option<Uuid> = None;
while let Some(token) = parser.next_token() {
if let Err(ref e) = token {
if e.kind() == &ParserErrorKind::EmptyValue {
continue;
}
}
let token = token?;
match token {
Token::Platform(platform) => {
if platform != SDL_PLATFORM_NAME {
warn!("Mappings for different platform – {}", platform);
}
}
Token::Uuid(v) => uuid = Some(v),
Token::Name(name) => mapping.name = name.to_owned(),
Token::AxisMapping { from, to, .. } => {
let axis = axes.get(from as usize).cloned();
if let Some(axis) = axis {
mapping.mappings.insert(axis, to);
} else {
warn!(
"SDL-mapping {} {}: Unknown axis a{}",
uuid.unwrap(),
mapping.name,
from
)
}
}
Token::ButtonMapping { from, to, .. } => {
let btn = buttons.get(from as usize).cloned();
if let Some(btn) = btn {
mapping.mappings.insert(btn, to);
} else {
warn!(
"SDL-mapping {} {}: Unknown button b{}",
uuid.unwrap(),
mapping.name,
from
)
}
}
Token::HatMapping {
hat, direction, to, ..
} => {
if hat != 0 {
warn!(
"Hat mappings are only supported for dpads (requested to map hat \
{}.{} to {:?}",
hat, direction, to
);
} else {
let (from_axis, from_btn) = match direction {
1 => (nec::AXIS_DPADY, nec::BTN_DPAD_UP),
4 => (nec::AXIS_DPADY, nec::BTN_DPAD_DOWN),
2 => (nec::AXIS_DPADX, nec::BTN_DPAD_RIGHT),
8 => (nec::AXIS_DPADX, nec::BTN_DPAD_LEFT),
0 => continue, _ => return Err(ParseSdlMappingError::UnknownHatDirection),
};
if to.is_button() {
match to {
AxisOrBtn::Btn(Button::DPadLeft | Button::DPadRight) => {
mapping
.mappings
.insert(from_axis, AxisOrBtn::Axis(Axis::DPadX));
}
AxisOrBtn::Btn(Button::DPadUp | Button::DPadDown) => {
mapping
.mappings
.insert(from_axis, AxisOrBtn::Axis(Axis::DPadY));
}
_ => (),
}
mapping.mappings.insert(from_btn, to);
} else {
mapping.mappings.insert(from_axis, to);
}
mapping.hats_mapped |= direction as u8;
}
}
}
}
Ok(mapping)
}
fn add_button(
ident: &str,
ev_code: EvCode,
mapped_btn: Button,
buttons: &[EvCode],
sdl_mappings: &mut String,
mappings: &mut FnvHashMap<EvCode, AxisOrBtn>,
) -> Result<(), MappingError> {
let n_btn = buttons
.iter()
.position(|&x| x == ev_code)
.ok_or(MappingError::InvalidCode(ev::Code(ev_code)))?;
let _ = write!(sdl_mappings, "{}:b{},", ident, n_btn);
mappings.insert(ev_code, AxisOrBtn::Btn(mapped_btn));
Ok(())
}
fn add_axis(
ident: &str,
ev_code: EvCode,
mapped_axis: Axis,
axes: &[EvCode],
sdl_mappings: &mut String,
mappings: &mut FnvHashMap<EvCode, AxisOrBtn>,
) -> Result<(), MappingError> {
let n_axis = axes
.iter()
.position(|&x| x == ev_code)
.ok_or(MappingError::InvalidCode(ev::Code(ev_code)))?;
let _ = write!(sdl_mappings, "{}:a{},", ident, n_axis);
mappings.insert(ev_code, AxisOrBtn::Axis(mapped_axis));
Ok(())
}
fn is_name_valid(name: &str) -> bool {
!name.chars().any(|x| x == ',')
}
pub fn map(&self, code: &EvCode) -> Option<AxisOrBtn> {
self.mappings.get(code).cloned()
}
pub fn map_rev(&self, el: &AxisOrBtn) -> Option<EvCode> {
self.mappings.iter().find(|x| x.1 == el).map(|x| *x.0)
}
pub fn is_default(&self) -> bool {
self.default
}
pub fn hats_mapped(&self) -> u8 {
self.hats_mapped
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ParseSdlMappingError {
UnknownHatDirection,
ParseError(ParserError),
}
impl From<ParserError> for ParseSdlMappingError {
fn from(f: ParserError) -> Self {
ParseSdlMappingError::ParseError(f)
}
}
impl Error for ParseSdlMappingError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
if let ParseSdlMappingError::ParseError(ref err) = self {
Some(err)
} else {
None
}
}
}
impl Display for ParseSdlMappingError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
match self {
ParseSdlMappingError::UnknownHatDirection => {
fmt.write_str("hat direction wasn't 1, 2, 4 or 8")
}
ParseSdlMappingError::ParseError(_) => fmt.write_str("parsing error"),
}
}
}
#[derive(Debug)]
pub struct MappingDb {
mappings: HashMap<Uuid, String>,
}
impl MappingDb {
pub fn new() -> Self {
MappingDb {
mappings: HashMap::new(),
}
}
pub fn add_included_mappings(&mut self) {
self.insert(include_str!(concat!(
env!("OUT_DIR"),
PATH_SEPARATOR!(),
"gamecontrollerdb.txt"
)));
}
pub fn add_env_mappings(&mut self) {
if let Ok(mapping) = env::var("SDL_GAMECONTROLLERCONFIG") {
self.insert(&mapping);
}
}
pub fn insert(&mut self, s: &str) {
for mapping in s.lines() {
let pat = "platform:";
if let Some(offset) = mapping.find(pat).map(|o| o + pat.len()) {
let s = &mapping[offset..];
let end = s.find(',').unwrap_or(s.len());
if &s[..end] != SDL_PLATFORM_NAME {
continue;
}
}
mapping
.split(',')
.next()
.and_then(|s| Uuid::parse_str(s).ok())
.and_then(|uuid| self.mappings.insert(uuid, mapping.to_owned()));
}
}
pub fn get(&self, uuid: Uuid) -> Option<&str> {
self.mappings.get(&uuid).map(String::as_ref)
}
pub fn len(&self) -> usize {
self.mappings.len()
}
}
#[derive(Debug, Clone, Default)]
pub struct MappingData {
buttons: VecMap<EvCode>,
axes: VecMap<EvCode>,
}
impl MappingData {
pub fn new() -> Self {
MappingData {
buttons: VecMap::with_capacity(18),
axes: VecMap::with_capacity(11),
}
}
pub fn button(&self, idx: Button) -> Option<ev::Code> {
self.buttons.get(idx as usize).cloned().map(ev::Code)
}
pub fn axis(&self, idx: Axis) -> Option<ev::Code> {
self.axes.get(idx as usize).cloned().map(ev::Code)
}
pub fn insert_btn(&mut self, from: ev::Code, to: Button) -> Option<ev::Code> {
self.buttons.insert(to as usize, from.0).map(ev::Code)
}
pub fn insert_axis(&mut self, from: ev::Code, to: Axis) -> Option<ev::Code> {
self.axes.insert(to as usize, from.0).map(ev::Code)
}
pub fn remove_button(&mut self, idx: Button) -> Option<ev::Code> {
self.buttons.remove(idx as usize).map(ev::Code)
}
pub fn remove_axis(&mut self, idx: Axis) -> Option<ev::Code> {
self.axes.remove(idx as usize).map(ev::Code)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum MappingError {
InvalidCode(ev::Code),
InvalidName,
NotImplemented,
NotConnected,
DuplicatedEntry,
UnknownElement,
NotSdl2Compatible,
}
impl Error for MappingError {}
impl Display for MappingError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
let sbuf;
let s = match self {
MappingError::InvalidCode(code) => {
sbuf = format!("gamepad does not have element with {}", code);
sbuf.as_ref()
}
MappingError::InvalidName => "name can not contain comma",
MappingError::NotImplemented => {
"current platform does not implement setting custom mappings"
}
MappingError::NotConnected => "gamepad is not connected",
MappingError::DuplicatedEntry => {
"same gamepad element is referenced by axis and button"
}
MappingError::UnknownElement => "Button::Unknown and Axis::Unknown are not allowed",
MappingError::NotSdl2Compatible => "one of buttons or axes is not compatible with SDL2",
};
f.write_str(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ev::{Axis, Button};
use gilrs_core::native_ev_codes as nec;
use gilrs_core::EvCode;
use uuid::Uuid;
const TEST_STR: &str = "03000000260900008888000000010001,GameCube {WiseGroup USB \
box},a:b0,b:b2,y:b3,x:b1,start:b7,rightshoulder:b6,dpup:h0.1,dpleft:\
h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,\
lefttrigger:a4,righttrigger:a5,";
const BUTTONS: [EvCode; 15] = [
nec::BTN_SOUTH,
nec::BTN_EAST,
nec::BTN_C,
nec::BTN_NORTH,
nec::BTN_WEST,
nec::BTN_Z,
nec::BTN_LT,
nec::BTN_RT,
nec::BTN_LT2,
nec::BTN_RT2,
nec::BTN_SELECT,
nec::BTN_START,
nec::BTN_MODE,
nec::BTN_LTHUMB,
nec::BTN_RTHUMB,
];
const AXES: [EvCode; 12] = [
nec::AXIS_LSTICKX,
nec::AXIS_LSTICKY,
nec::AXIS_LEFTZ,
nec::AXIS_RSTICKX,
nec::AXIS_RSTICKY,
nec::AXIS_RIGHTZ,
nec::AXIS_DPADX,
nec::AXIS_DPADY,
nec::AXIS_RT,
nec::AXIS_LT,
nec::AXIS_RT2,
nec::AXIS_LT2,
];
#[test]
fn mapping() {
Mapping::parse_sdl_mapping(TEST_STR, &BUTTONS, &AXES).unwrap();
}
#[test]
fn from_data() {
let uuid = Uuid::nil();
let name = "Best Gamepad";
let buttons = BUTTONS.iter().cloned().map(ev::Code).collect::<Vec<_>>();
let axes = AXES.iter().cloned().map(ev::Code).collect::<Vec<_>>();
let mut data = MappingData::new();
data.insert_axis(axes[0], Axis::LeftStickX);
data.insert_axis(axes[1], Axis::LeftStickY);
data.insert_axis(axes[2], Axis::LeftZ);
data.insert_axis(axes[3], Axis::RightStickX);
data.insert_axis(axes[4], Axis::RightStickY);
data.insert_axis(axes[5], Axis::RightZ);
data.insert_btn(buttons[0], Button::South);
data.insert_btn(buttons[1], Button::East);
data.insert_btn(buttons[3], Button::North);
data.insert_btn(buttons[4], Button::West);
data.insert_btn(buttons[5], Button::Select);
data.insert_btn(buttons[6], Button::Start);
data.insert_btn(buttons[7], Button::DPadDown);
data.insert_btn(buttons[8], Button::DPadLeft);
data.insert_btn(buttons[9], Button::RightThumb);
let (mappings, sdl_mappings) =
Mapping::from_data(&data, &BUTTONS, &AXES, name, uuid).unwrap();
let sdl_mappings = Mapping::parse_sdl_mapping(&sdl_mappings, &BUTTONS, &AXES).unwrap();
assert_eq!(mappings, sdl_mappings);
let incorrect_mappings = Mapping::from_data(&data, &BUTTONS, &AXES, "Inval,id name", uuid);
assert_eq!(Err(MappingError::InvalidName), incorrect_mappings);
data.insert_btn(ev::Code(nec::BTN_DPAD_RIGHT), Button::DPadRight);
let incorrect_mappings = Mapping::from_data(&data, &BUTTONS, &AXES, name, uuid);
assert_eq!(
Err(MappingError::InvalidCode(ev::Code(nec::BTN_DPAD_RIGHT))),
incorrect_mappings
);
data.insert_btn(ev::Code(BUTTONS[3]), Button::Unknown);
let incorrect_mappings = Mapping::from_data(&data, &BUTTONS, &AXES, name, uuid);
assert_eq!(Err(MappingError::UnknownElement), incorrect_mappings);
}
#[test]
fn with_mappings() {
let mappings = format!(
"\nShould be ignored\nThis also should,be ignored\n\n{}",
TEST_STR
);
let mut db = MappingDb::new();
db.add_included_mappings();
db.insert(&mappings);
assert_eq!(
Some(TEST_STR),
db.get(Uuid::parse_str("03000000260900008888000000010001").unwrap())
);
}
}