1use std::collections::BTreeMap;
2
3use gilrs::ff::{BaseEffect, BaseEffectType, EffectBuilder, Replay, Ticks};
4use gilrs::{EventType, GamepadId};
5
6use crate::backend::Backend;
7use crate::device::{Axis, Button, Motors, PadId, PadInfo};
8use crate::event::InputEvent;
9use crate::precedence::SteamPrecedence;
10use crate::rumble::MotorCommand;
11
12pub struct Gilrs {
13 gilrs: gilrs::Gilrs,
14 ids: BTreeMap<PadId, GamepadId>,
15 effects: BTreeMap<PadId, gilrs::ff::Effect>,
16 announced: bool,
17 steam: SteamPrecedence,
18 pending: Vec<InputEvent>,
19}
20
21impl Gilrs {
22 pub fn new() -> Result<Self, Box<gilrs::Error>> {
23 Ok(Self {
24 gilrs: gilrs::Gilrs::new().map_err(Box::new)?,
25 ids: BTreeMap::new(),
26 effects: BTreeMap::new(),
27 announced: false,
28 steam: SteamPrecedence::new(),
29 pending: Vec::new(),
30 })
31 }
32
33 pub fn with_steam(mut self, steam: SteamPrecedence) -> Self {
34 self.steam = steam;
35 self
36 }
37
38 pub fn steam(&self) -> &SteamPrecedence {
39 &self.steam
40 }
41
42 fn connected(&mut self, id: GamepadId) -> InputEvent {
43 let pad = PadId(usize::from(id) as u32);
44 self.ids.insert(pad, id);
45 let gamepad = self.gilrs.gamepad(id);
46 let motors = if gamepad.is_ff_supported() {
47 Motors::Two
48 } else {
49 Motors::None
50 };
51 InputEvent::PadConnected {
52 pad,
53 info: PadInfo::from_ids(
54 gamepad.name(),
55 gamepad.vendor_id(),
56 gamepad.product_id(),
57 motors,
58 ),
59 }
60 }
61}
62
63impl Backend for Gilrs {
64 fn poll(&mut self, out: &mut Vec<InputEvent>) {
65 out.append(&mut self.pending);
66 let mut raw = Vec::new();
67 self.read(&mut raw);
68 self.steam.filter(raw, out);
69 }
70
71 fn set_motors(&mut self, command: &MotorCommand) -> bool {
72 if self.steam.is_hidden(command.pad) {
73 return false;
74 }
75 self.play(command)
76 }
77
78 fn steam_input(&mut self, active: bool) {
79 self.steam.set_active(active, &mut self.pending);
80 let hidden: Vec<PadId> = self.steam.hidden().collect();
81 for pad in hidden {
82 if let Some(effect) = self.effects.remove(&pad) {
83 let _ = effect.stop();
84 }
85 }
86 }
87}
88
89impl Gilrs {
90 fn read(&mut self, events: &mut Vec<InputEvent>) {
91 if !self.announced {
92 self.announced = true;
93 let ids: Vec<GamepadId> = self.gilrs.gamepads().map(|(id, _)| id).collect();
94 for id in ids {
95 let event = self.connected(id);
96 events.push(event);
97 }
98 }
99 while let Some(event) = self.gilrs.next_event() {
100 let pad = PadId(usize::from(event.id) as u32);
101 match event.event {
102 EventType::Connected => {
103 let connected = self.connected(event.id);
104 events.push(connected);
105 }
106 EventType::Disconnected => {
107 self.ids.remove(&pad);
108 self.effects.remove(&pad);
109 events.push(InputEvent::PadDisconnected { pad });
110 }
111 EventType::ButtonChanged(button, value, _) => {
112 if let Some(button) = button_of(button) {
113 events.push(InputEvent::PadButton { pad, button, value });
114 }
115 }
116 EventType::AxisChanged(axis, value, _) => match axis {
117 gilrs::Axis::DPadX => {
118 events.push(InputEvent::PadButton {
119 pad,
120 button: Button::DPadLeft,
121 value: (-value).max(0.0),
122 });
123 events.push(InputEvent::PadButton {
124 pad,
125 button: Button::DPadRight,
126 value: value.max(0.0),
127 });
128 }
129 gilrs::Axis::DPadY => {
130 events.push(InputEvent::PadButton {
131 pad,
132 button: Button::DPadDown,
133 value: (-value).max(0.0),
134 });
135 events.push(InputEvent::PadButton {
136 pad,
137 button: Button::DPadUp,
138 value: value.max(0.0),
139 });
140 }
141 _ => {
142 if let Some(axis) = axis_of(axis) {
143 events.push(InputEvent::PadAxis { pad, axis, value });
144 }
145 }
146 },
147 _ => {}
148 }
149 }
150 }
151
152 fn play(&mut self, command: &MotorCommand) -> bool {
153 if let Some(effect) = self.effects.remove(&command.pad) {
154 let _ = effect.stop();
155 }
156 if command.low == 0 && command.high == 0 {
157 return true;
158 }
159 let Some(id) = self.ids.get(&command.pad).copied() else {
160 return false;
161 };
162 let scheduling = Replay {
163 play_for: Ticks::from_ms(command.hold_ms.max(1)),
164 ..Replay::default()
165 };
166 let effect = EffectBuilder::new()
167 .add_effect(BaseEffect {
168 kind: BaseEffectType::Strong {
169 magnitude: command.low,
170 },
171 scheduling,
172 envelope: Default::default(),
173 })
174 .add_effect(BaseEffect {
175 kind: BaseEffectType::Weak {
176 magnitude: command.high,
177 },
178 scheduling,
179 envelope: Default::default(),
180 })
181 .gamepads(&[id])
182 .repeat(gilrs::ff::Repeat::For(Ticks::from_ms(
183 command.hold_ms.max(1),
184 )))
185 .finish(&mut self.gilrs);
186 match effect {
187 Ok(effect) => {
188 let played = effect.play().is_ok();
189 self.effects.insert(command.pad, effect);
190 played
191 }
192 Err(_) => false,
193 }
194 }
195}
196
197pub fn button_of(button: gilrs::Button) -> Option<Button> {
198 Some(match button {
199 gilrs::Button::South => Button::South,
200 gilrs::Button::East => Button::East,
201 gilrs::Button::West => Button::West,
202 gilrs::Button::North => Button::North,
203 gilrs::Button::LeftTrigger => Button::LeftBumper,
204 gilrs::Button::RightTrigger => Button::RightBumper,
205 gilrs::Button::LeftTrigger2 => Button::LeftTrigger,
206 gilrs::Button::RightTrigger2 => Button::RightTrigger,
207 gilrs::Button::LeftThumb => Button::LeftStick,
208 gilrs::Button::RightThumb => Button::RightStick,
209 gilrs::Button::DPadUp => Button::DPadUp,
210 gilrs::Button::DPadDown => Button::DPadDown,
211 gilrs::Button::DPadLeft => Button::DPadLeft,
212 gilrs::Button::DPadRight => Button::DPadRight,
213 gilrs::Button::Start => Button::Menu,
214 gilrs::Button::Select => Button::View,
215 gilrs::Button::Mode => Button::Guide,
216 gilrs::Button::C | gilrs::Button::Z | gilrs::Button::Unknown => return None,
217 })
218}
219
220pub fn axis_of(axis: gilrs::Axis) -> Option<Axis> {
221 Some(match axis {
222 gilrs::Axis::LeftStickX => Axis::LeftX,
223 gilrs::Axis::LeftStickY => Axis::LeftY,
224 gilrs::Axis::RightStickX => Axis::RightX,
225 gilrs::Axis::RightStickY => Axis::RightY,
226 _ => return None,
227 })
228}