Skip to main content

pfx_input/
pads.rs

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}