use crate::{
ev::{
state::{AxisData, ButtonData, GamepadState},
Axis, AxisOrBtn, Button, Code, Event, EventType,
},
ff::{
server::{self, FfMessage, Message},
Error as FfError,
},
mapping::{Mapping, MappingData, MappingDb},
utils, MappingError,
};
use gilrs_core::{
self, AxisInfo, Error as PlatformError, Event as RawEvent, EventType as RawEventType,
};
use uuid::Uuid;
use std::cmp::Ordering;
use std::{
collections::VecDeque,
error,
fmt::{self, Display},
sync::mpsc::{Receiver, Sender},
time::Duration,
};
pub use gilrs_core::PowerInfo;
#[cfg(feature = "serde-serialize")]
use serde::{Deserialize, Serialize};
const DEFAULT_DEADZONE: f32 = 0.1;
#[derive(Debug)]
pub struct Gilrs {
inner: gilrs_core::Gilrs,
next_id: usize,
tx: Sender<Message>,
rx: Receiver<FfMessage>,
counter: u64,
mappings: MappingDb,
default_filters: bool,
events: VecDeque<Event>,
axis_to_btn_pressed: f32,
axis_to_btn_released: f32,
pub(crate) update_state: bool,
pub(crate) gamepads_data: Vec<GamepadData>,
}
impl Gilrs {
#[allow(clippy::result_large_err)]
pub fn new() -> Result<Self, Error> {
GilrsBuilder::new().build()
}
pub fn next_event(&mut self) -> Option<Event> {
self.next_event_inner(false, None)
}
pub fn next_event_blocking(&mut self, timeout: Option<Duration>) -> Option<Event> {
self.next_event_inner(true, timeout)
}
fn next_event_inner(
&mut self,
is_blocking: bool,
blocking_timeout: Option<Duration>,
) -> Option<Event> {
use crate::ev::filter::{axis_dpad_to_button, deadzone, Filter, Jitter};
let ev = if self.default_filters {
let jitter_filter = Jitter::new();
loop {
let ev = self
.next_event_priv(is_blocking, blocking_timeout)
.filter_ev(&axis_dpad_to_button, self)
.filter_ev(&jitter_filter, self)
.filter_ev(&deadzone, self);
match ev {
Some(ev) if ev.is_dropped() => (),
_ => break ev,
}
}
} else {
self.next_event_priv(is_blocking, blocking_timeout)
};
if self.update_state {
if let Some(ref ev) = ev {
self.update(ev);
}
}
ev
}
fn next_event_priv(
&mut self,
is_blocking: bool,
blocking_timeout: Option<Duration>,
) -> Option<Event> {
if let Ok(msg) = self.rx.try_recv() {
return match msg {
FfMessage::EffectCompleted { event } => Some(event),
};
}
if let Some(ev) = self.events.pop_front() {
Some(ev)
} else {
let event = if is_blocking {
self.inner.next_event_blocking(blocking_timeout)
} else {
self.inner.next_event()
};
match event {
Some(RawEvent {
id,
event: event_type,
time,
..
}) => {
trace!("Original event: {:?}", event);
let id = GamepadId(id);
let event = match event_type {
RawEventType::ButtonPressed(nec) => {
let nec = Code(nec);
match self.gamepad(id).axis_or_btn_name(nec) {
Some(AxisOrBtn::Btn(b)) => {
self.events.push_back(Event {
id,
time,
event: EventType::ButtonChanged(b, 1.0, nec),
});
EventType::ButtonPressed(b, nec)
}
Some(AxisOrBtn::Axis(a)) => EventType::AxisChanged(a, 1.0, nec),
None => {
self.events.push_back(Event {
id,
time,
event: EventType::ButtonChanged(Button::Unknown, 1.0, nec),
});
EventType::ButtonPressed(Button::Unknown, nec)
}
}
}
RawEventType::ButtonReleased(nec) => {
let nec = Code(nec);
match self.gamepad(id).axis_or_btn_name(nec) {
Some(AxisOrBtn::Btn(b)) => {
self.events.push_back(Event {
id,
time,
event: EventType::ButtonChanged(b, 0.0, nec),
});
EventType::ButtonReleased(b, nec)
}
Some(AxisOrBtn::Axis(a)) => EventType::AxisChanged(a, 0.0, nec),
None => {
self.events.push_back(Event {
id,
time,
event: EventType::ButtonChanged(Button::Unknown, 0.0, nec),
});
EventType::ButtonReleased(Button::Unknown, nec)
}
}
}
RawEventType::AxisValueChanged(val, nec) => {
let axis_info = *self.gamepad(id).inner.axis_info(nec).unwrap();
let nec = Code(nec);
match self.gamepad(id).axis_or_btn_name(nec) {
Some(AxisOrBtn::Btn(b)) => {
let val = btn_value(&axis_info, val);
if val >= self.axis_to_btn_pressed
&& !self.gamepad(id).state().is_pressed(nec)
{
self.events.push_back(Event {
id,
time,
event: EventType::ButtonChanged(b, val, nec),
});
EventType::ButtonPressed(b, nec)
} else if val <= self.axis_to_btn_released
&& self.gamepad(id).state().is_pressed(nec)
{
self.events.push_back(Event {
id,
time,
event: EventType::ButtonChanged(b, val, nec),
});
EventType::ButtonReleased(b, nec)
} else {
EventType::ButtonChanged(b, val, nec)
}
}
Some(AxisOrBtn::Axis(a)) => {
EventType::AxisChanged(a, axis_value(&axis_info, val, a), nec)
}
None => EventType::AxisChanged(
Axis::Unknown,
axis_value(&axis_info, val, Axis::Unknown),
nec,
),
}
}
RawEventType::Connected => {
match id.0.cmp(&self.gamepads_data.len()) {
Ordering::Equal => {
self.gamepads_data.push(GamepadData::new(
id,
self.tx.clone(),
self.inner.gamepad(id.0).unwrap(),
&self.mappings,
));
}
Ordering::Less => {
self.gamepads_data[id.0] = GamepadData::new(
id,
self.tx.clone(),
self.inner.gamepad(id.0).unwrap(),
&self.mappings,
);
}
Ordering::Greater => {
error!(
"Platform implementation error: got Connected event with \
id {}, when expected id {}",
id.0,
self.gamepads_data.len()
);
}
}
EventType::Connected
}
RawEventType::Disconnected => {
let _ = self.tx.send(Message::Close { id: id.0 });
EventType::Disconnected
}
_ => {
unimplemented!()
}
};
Some(Event { id, event, time })
}
None => None,
}
}
}
pub fn update(&mut self, event: &Event) {
use crate::EventType::*;
let counter = self.counter;
let data = match self.gamepads_data.get_mut(event.id.0) {
Some(d) => d,
None => return,
};
match event.event {
ButtonPressed(_, nec) => {
data.state.set_btn_pressed(nec, true, counter, event.time);
}
ButtonReleased(_, nec) => {
data.state.set_btn_pressed(nec, false, counter, event.time);
}
ButtonRepeated(_, nec) => {
data.state.set_btn_repeating(nec, counter, event.time);
}
ButtonChanged(_, value, nec) => {
data.state.set_btn_value(nec, value, counter, event.time);
}
AxisChanged(_, value, nec) => {
data.state
.update_axis(nec, AxisData::new(value, counter, event.time));
}
Disconnected | Connected | Dropped | ForceFeedbackEffectCompleted => (),
}
}
pub fn inc(&mut self) {
if self.counter == 0x3FFF_FFFF_FFFF_FFFF {
self.counter = 0;
} else {
self.counter += 1;
}
}
pub fn counter(&self) -> u64 {
self.counter
}
pub fn reset_counter(&mut self) {
self.counter = 0;
}
fn finish_gamepads_creation(&mut self) {
let tx = self.tx.clone();
for id in 0..self.inner.last_gamepad_hint() {
let gamepad = self.inner.gamepad(id).unwrap();
self.gamepads_data.push(GamepadData::new(
GamepadId(id),
tx.clone(),
gamepad,
&self.mappings,
))
}
}
pub fn gamepad(&self, id: GamepadId) -> Gamepad<'_> {
Gamepad {
inner: self.inner.gamepad(id.0).unwrap(),
data: &self.gamepads_data[id.0],
}
}
pub fn connected_gamepad(&self, id: GamepadId) -> Option<Gamepad<'_>> {
if let Some(data) = self.gamepads_data.get(id.0) {
let inner = self.inner.gamepad(id.0)?;
if inner.is_connected() {
Some(Gamepad { inner, data })
} else {
None
}
} else {
None
}
}
pub fn gamepads(&self) -> ConnectedGamepadsIterator<'_> {
ConnectedGamepadsIterator(self, 0)
}
pub fn insert_event(&mut self, ev: Event) {
self.events.push_back(ev);
}
pub(crate) fn ff_sender(&self) -> &Sender<Message> {
&self.tx
}
pub fn set_mapping<'b, O: Into<Option<&'b str>>>(
&mut self,
gamepad_id: usize,
mapping: &MappingData,
name: O,
) -> Result<String, MappingError> {
if let Some(gamepad) = self.inner.gamepad(gamepad_id) {
if !gamepad.is_connected() {
return Err(MappingError::NotConnected);
}
let name = match name.into() {
Some(s) => s,
None => gamepad.name(),
};
let (mapping, s) = Mapping::from_data(
mapping,
gamepad.buttons(),
gamepad.axes(),
name,
Uuid::from_bytes(gamepad.uuid()),
)?;
let data = &mut self.gamepads_data[gamepad_id];
data.mapping = mapping;
Ok(s)
} else {
Err(MappingError::NotConnected)
}
}
pub fn set_mapping_strict<'b, O: Into<Option<&'b str>>>(
&mut self,
gamepad_id: usize,
mapping: &MappingData,
name: O,
) -> Result<String, MappingError> {
if mapping.button(Button::C).is_some()
|| mapping.button(Button::Z).is_some()
|| mapping.axis(Axis::LeftZ).is_some()
|| mapping.axis(Axis::RightZ).is_some()
{
Err(MappingError::NotSdl2Compatible)
} else {
self.set_mapping(gamepad_id, mapping, name)
}
}
pub(crate) fn next_ff_id(&mut self) -> usize {
let id = self.next_id;
self.next_id = match self.next_id.checked_add(1) {
Some(x) => x,
None => panic!("Failed to assign ID to new effect"),
};
id
}
}
pub struct GilrsBuilder {
mappings: MappingDb,
default_filters: bool,
axis_to_btn_pressed: f32,
axis_to_btn_released: f32,
update_state: bool,
env_mappings: bool,
included_mappings: bool,
force_feedback: bool,
}
impl GilrsBuilder {
pub fn new() -> Self {
GilrsBuilder {
mappings: MappingDb::new(),
default_filters: true,
axis_to_btn_pressed: 0.75,
axis_to_btn_released: 0.65,
update_state: true,
env_mappings: true,
included_mappings: true,
force_feedback: true,
}
}
pub fn with_default_filters(mut self, default_filters: bool) -> Self {
self.default_filters = default_filters;
self
}
pub fn with_force_feedback(mut self, enable_ff: bool) -> Self {
self.force_feedback = enable_ff;
self
}
pub fn add_mappings(mut self, mappings: &str) -> Self {
self.mappings.insert(mappings);
self
}
pub fn add_env_mappings(mut self, env_mappings: bool) -> Self {
self.env_mappings = env_mappings;
self
}
pub fn add_included_mappings(mut self, included_mappings: bool) -> Self {
self.included_mappings = included_mappings;
self
}
pub fn set_axis_to_btn(mut self, pressed: f32, released: f32) -> Self {
self.axis_to_btn_pressed = pressed;
self.axis_to_btn_released = released;
self
}
pub fn set_update_state(mut self, enabled: bool) -> Self {
self.update_state = enabled;
self
}
#[allow(clippy::result_large_err)]
pub fn build(mut self) -> Result<Gilrs, Error> {
if self.included_mappings {
self.mappings.add_included_mappings();
}
if self.env_mappings {
self.mappings.add_env_mappings();
}
debug!("Loaded {} mappings.", self.mappings.len());
if self.axis_to_btn_pressed <= self.axis_to_btn_released
|| self.axis_to_btn_pressed < 0.0
|| self.axis_to_btn_pressed > 1.0
|| self.axis_to_btn_released < 0.0
|| self.axis_to_btn_released > 1.0
{
return Err(Error::InvalidAxisToBtn);
}
let mut is_dummy = false;
let inner = match gilrs_core::Gilrs::new() {
Ok(g) => g,
Err(PlatformError::NotImplemented(g)) => {
is_dummy = true;
g
}
Err(PlatformError::Other(e)) => return Err(Error::Other(e)),
Err(_) => unimplemented!(),
};
let (tx, rx) = server::init(self.force_feedback);
let mut gilrs = Gilrs {
inner,
next_id: 0,
tx,
rx,
counter: 0,
mappings: self.mappings,
default_filters: self.default_filters,
events: VecDeque::new(),
axis_to_btn_pressed: self.axis_to_btn_pressed,
axis_to_btn_released: self.axis_to_btn_released,
update_state: self.update_state,
gamepads_data: Vec::new(),
};
gilrs.finish_gamepads_creation();
if is_dummy {
Err(Error::NotImplemented(gilrs))
} else {
Ok(gilrs)
}
}
}
impl Default for GilrsBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct ConnectedGamepadsIterator<'a>(&'a Gilrs, usize);
impl<'a> Iterator for ConnectedGamepadsIterator<'a> {
type Item = (GamepadId, Gamepad<'a>);
fn next(&mut self) -> Option<(GamepadId, Gamepad<'a>)> {
loop {
if self.1 == self.0.inner.last_gamepad_hint() {
return None;
}
if let Some(gp) = self.0.connected_gamepad(GamepadId(self.1)) {
let idx = self.1;
self.1 += 1;
return Some((GamepadId(idx), gp));
}
self.1 += 1;
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct Gamepad<'a> {
data: &'a GamepadData,
inner: &'a gilrs_core::Gamepad,
}
impl Gamepad<'_> {
pub fn name(&self) -> &str {
if let Some(map_name) = self.map_name() {
map_name
} else {
self.os_name()
}
}
pub fn map_name(&self) -> Option<&str> {
self.data.map_name()
}
pub fn os_name(&self) -> &str {
self.inner.name()
}
pub fn uuid(&self) -> [u8; 16] {
self.inner.uuid()
}
pub fn vendor_id(&self) -> Option<u16> {
self.inner.vendor_id()
}
pub fn product_id(&self) -> Option<u16> {
self.inner.product_id()
}
pub fn state(&self) -> &GamepadState {
&self.data.state
}
pub fn is_connected(&self) -> bool {
self.inner.is_connected()
}
pub fn is_pressed(&self, btn: Button) -> bool {
self.data.is_pressed(btn)
}
pub fn value(&self, axis: Axis) -> f32 {
self.data.value(axis)
}
pub fn button_data(&self, btn: Button) -> Option<&ButtonData> {
self.data.button_data(btn)
}
pub fn axis_data(&self, axis: Axis) -> Option<&AxisData> {
self.data.axis_data(axis)
}
pub fn power_info(&self) -> PowerInfo {
self.inner.power_info()
}
pub fn mapping_source(&self) -> MappingSource {
if self.data.mapping.is_default() {
MappingSource::Driver
} else {
MappingSource::SdlMappings
}
}
pub fn is_ff_supported(&self) -> bool {
self.inner.is_ff_supported()
}
pub fn set_listener_position<Vec3: Into<[f32; 3]>>(
&self,
position: Vec3,
) -> Result<(), FfError> {
if !self.is_connected() {
Err(FfError::Disconnected(self.id()))
} else if !self.is_ff_supported() {
Err(FfError::FfNotSupported(self.id()))
} else {
self.data.tx.send(Message::SetListenerPosition {
id: self.data.id.0,
position: position.into(),
})?;
Ok(())
}
}
pub fn axis_or_btn_name(&self, ec: Code) -> Option<AxisOrBtn> {
self.data.axis_or_btn_name(ec)
}
pub fn button_code(&self, btn: Button) -> Option<Code> {
self.data.button_code(btn)
}
pub fn axis_code(&self, axis: Axis) -> Option<Code> {
self.data.axis_code(axis)
}
pub fn deadzone(&self, axis: Code) -> Option<f32> {
self.inner.axis_info(axis.0).map(|i| {
let range = i.max as f32 - i.min as f32;
if range == 0.0 {
0.0
} else {
i.deadzone
.map(|d| d as f32 / range * 2.0)
.unwrap_or(DEFAULT_DEADZONE)
}
})
}
pub fn id(&self) -> GamepadId {
self.data.id
}
pub(crate) fn mapping(&self) -> &Mapping {
&self.data.mapping
}
}
#[cfg(target_os = "linux")]
pub use gilrs_core::LinuxGamepadExt;
#[cfg(target_os = "linux")]
use std::path::Path;
#[cfg(target_os = "linux")]
impl LinuxGamepadExt for Gamepad<'_> {
fn devpath(&self) -> &Path {
self.inner.devpath()
}
}
#[derive(Debug)]
pub(crate) struct GamepadData {
state: GamepadState,
mapping: Mapping,
tx: Sender<Message>,
id: GamepadId,
pub(crate) have_sent_nonzero_for_axis: [bool; 6],
}
impl GamepadData {
fn new(
id: GamepadId,
tx: Sender<Message>,
gamepad: &gilrs_core::Gamepad,
db: &MappingDb,
) -> Self {
let uuid = Uuid::from_bytes(gamepad.uuid());
let mapping = db
.get(uuid)
.map(
|s| match Mapping::parse_sdl_mapping(s, gamepad.buttons(), gamepad.axes()) {
Ok(result) => result,
Err(e) => {
warn!(
"Unable to parse SDL mapping for UUID {uuid}\n\t{e:?}\n\tDefault \
mapping will be used.",
);
Mapping::default(gamepad)
}
},
)
.unwrap_or_else(|| {
warn!("No mapping found for UUID {uuid}\n\tDefault mapping will be used.");
Mapping::default(gamepad)
});
if gamepad.is_ff_supported() && gamepad.is_connected() {
if let Some(device) = gamepad.ff_device() {
let _ = tx.send(Message::Open { id: id.0, device });
}
}
GamepadData {
state: GamepadState::new(),
mapping,
tx,
id,
have_sent_nonzero_for_axis: Default::default(),
}
}
pub fn map_name(&self) -> Option<&str> {
if self.mapping.is_default() {
None
} else {
Some(self.mapping.name())
}
}
pub fn is_pressed(&self, btn: Button) -> bool {
assert_ne!(btn, Button::Unknown);
self.button_code(btn)
.or_else(|| btn.to_nec())
.map(|nec| self.state.is_pressed(nec))
.unwrap_or(false)
}
pub fn value(&self, axis: Axis) -> f32 {
assert_ne!(axis, Axis::Unknown);
self.axis_code(axis)
.map(|nec| self.state.value(nec))
.unwrap_or(0.0)
}
pub fn button_data(&self, btn: Button) -> Option<&ButtonData> {
self.button_code(btn)
.and_then(|nec| self.state.button_data(nec))
}
pub fn axis_data(&self, axis: Axis) -> Option<&AxisData> {
self.axis_code(axis)
.and_then(|nec| self.state.axis_data(nec))
}
pub fn axis_or_btn_name(&self, ec: Code) -> Option<AxisOrBtn> {
self.mapping.map(&ec.0)
}
pub fn button_code(&self, btn: Button) -> Option<Code> {
self.mapping.map_rev(&AxisOrBtn::Btn(btn)).map(Code)
}
pub fn axis_code(&self, axis: Axis) -> Option<Code> {
self.mapping.map_rev(&AxisOrBtn::Axis(axis)).map(Code)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MappingSource {
SdlMappings,
Driver,
None,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct GamepadId(pub(crate) usize);
impl From<GamepadId> for usize {
fn from(x: GamepadId) -> usize {
x.0
}
}
impl Display for GamepadId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
fn axis_value(info: &AxisInfo, val: i32, axis: Axis) -> f32 {
let mut range = info.max as f32 - info.min as f32;
let mut val = val as f32 - info.min as f32;
if let Some(i_range) = info.max.checked_sub(info.min) {
if i_range % 2 == 1 {
range += 1.0;
val += 1.0;
}
}
val = val / range * 2.0 - 1.0;
if gilrs_core::IS_Y_AXIS_REVERSED
&& (axis == Axis::LeftStickY || axis == Axis::RightStickY || axis == Axis::DPadY)
&& val != 0.0
{
val = -val;
}
utils::clamp(val, -1.0, 1.0)
}
fn btn_value(info: &AxisInfo, val: i32) -> f32 {
let range = info.max as f32 - info.min as f32;
let mut val = val as f32 - info.min as f32;
val /= range;
utils::clamp(val, 0.0, 1.0)
}
#[non_exhaustive]
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Error {
NotImplemented(Gilrs),
InvalidAxisToBtn,
Other(Box<dyn error::Error + Send + Sync + 'static>),
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::NotImplemented(_) => f.write_str("Gilrs does not support current platform."),
Error::InvalidAxisToBtn => f.write_str(
"Either `pressed ≤ released` or one of values is outside [0.0, 1.0] range.",
),
Error::Other(ref e) => e.fmt(f),
}
}
}
impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
Error::Other(e) => Some(e.as_ref()),
_ => None,
}
}
}
const _: () = {
const fn assert_send<T: Send>() {}
#[cfg(not(target_arch = "wasm32"))]
assert_send::<Gilrs>();
};
#[cfg(test)]
mod tests {
use super::{axis_value, btn_value, Axis, AxisInfo};
#[test]
fn axis_value_documented_case() {
let info = AxisInfo {
min: 0,
max: 255,
deadzone: None,
};
let axis = Axis::LeftStickY;
assert_eq!(0., axis_value(&info, 127, axis));
}
#[test]
fn axis_value_overflow() {
let info = AxisInfo {
min: i32::MIN,
max: i32::MAX,
deadzone: None,
};
let axis = Axis::LeftStickY;
assert_eq!(0., axis_value(&info, -1, axis));
assert_eq!(0., axis_value(&info, 0, axis));
assert_eq!(0., axis_value(&info, 1, axis));
assert_eq!(1.0, axis_value(&info, i32::MIN, axis));
assert_eq!(-1.0, axis_value(&info, i32::MAX, axis));
}
#[test]
fn btn_value_overflow() {
let info = AxisInfo {
min: i32::MIN,
max: i32::MAX,
deadzone: None,
};
assert_eq!(0.5, btn_value(&info, -1));
assert_eq!(0.5, btn_value(&info, 0));
assert_eq!(0.5, btn_value(&info, 1));
assert_eq!(0.0, btn_value(&info, i32::MIN));
assert_eq!(1.0, btn_value(&info, i32::MAX));
}
}