1use std::cell::RefCell;
6use std::collections::HashMap;
7
8use gilrs::ff::{BaseEffect, BaseEffectType, Effect, EffectBuilder, Repeat, Replay, Ticks};
9use gilrs::{EventType, Gilrs};
10use log::{debug, warn};
11use servo::{
12 GamepadDelegate, GamepadEvent, GamepadHapticEffectRequest, GamepadHapticEffectRequestType,
13 GamepadHapticEffectType, GamepadIndex, GamepadInputBounds, GamepadSupportedHapticEffects,
14 GamepadUpdateType, InputEvent, WebView,
15};
16
17pub struct HapticEffect {
18 pub effect: Effect,
19 pub request: GamepadHapticEffectRequest,
20}
21
22pub(crate) struct ServoshellGamepadDelegate {
23 handle: RefCell<Gilrs>,
24 haptic_effects: RefCell<HashMap<usize, HapticEffect>>,
25}
26
27impl ServoshellGamepadDelegate {
28 pub(crate) fn maybe_new() -> Option<Self> {
29 let handle = match Gilrs::new() {
30 Ok(handle) => handle,
31 Err(error) => {
32 warn!("Error creating gamepad input connection ({error})");
33 return None;
34 },
35 };
36 Some(Self {
37 handle: RefCell::new(handle),
38 haptic_effects: RefCell::new(Default::default()),
39 })
40 }
41
42 pub(crate) fn handle_gamepad_events(&self, active_webview: WebView) {
44 let mut handle = self.handle.borrow_mut();
45 while let Some(event) = handle.next_event() {
46 let gamepad = handle.gamepad(event.id);
47 let name = gamepad.name();
48 let index = GamepadIndex(event.id.into());
49 let mut gamepad_event: Option<GamepadEvent> = None;
50 match event.event {
51 EventType::ButtonPressed(button, _) => {
52 let mapped_index = Self::map_gamepad_button(button);
53 if !matches!(mapped_index, 6 | 7 | 17) {
55 let update_type = GamepadUpdateType::Button(mapped_index, 1.0);
56 gamepad_event = Some(GamepadEvent::Updated(index, update_type));
57 }
58 },
59 EventType::ButtonReleased(button, _) => {
60 let mapped_index = Self::map_gamepad_button(button);
61 if !matches!(mapped_index, 6 | 7 | 17) {
63 let update_type = GamepadUpdateType::Button(mapped_index, 0.0);
64 gamepad_event = Some(GamepadEvent::Updated(index, update_type));
65 }
66 },
67 EventType::ButtonChanged(button, value, _) => {
68 let mapped_index = Self::map_gamepad_button(button);
69 if matches!(mapped_index, 6 | 7) {
71 let update_type = GamepadUpdateType::Button(mapped_index, value as f64);
72 gamepad_event = Some(GamepadEvent::Updated(index, update_type));
73 }
74 },
75 EventType::AxisChanged(axis, value, _) => {
76 let mapped_axis: usize = match axis {
79 gilrs::Axis::LeftStickX => 0,
80 gilrs::Axis::LeftStickY => 1,
81 gilrs::Axis::RightStickX => 2,
82 gilrs::Axis::RightStickY => 3,
83 _ => 4, };
85 if mapped_axis < 4 {
86 let axis_value = match mapped_axis {
89 0 | 2 => value,
90 1 | 3 => -value,
91 _ => 0., };
93 let update_type = GamepadUpdateType::Axis(mapped_axis, axis_value as f64);
94 gamepad_event = Some(GamepadEvent::Updated(index, update_type));
95 }
96 },
97 EventType::Connected => {
98 let name = String::from(name);
99 let bounds = GamepadInputBounds {
100 axis_bounds: (-1.0, 1.0),
101 button_bounds: (0.0, 1.0),
102 };
103 let supported_haptic_effects = GamepadSupportedHapticEffects {
105 supports_dual_rumble: true,
106 supports_trigger_rumble: false,
107 };
108 gamepad_event = Some(GamepadEvent::Connected(
109 index,
110 name,
111 bounds,
112 supported_haptic_effects,
113 ));
114 },
115 EventType::Disconnected => {
116 gamepad_event = Some(GamepadEvent::Disconnected(index));
117 },
118 EventType::ForceFeedbackEffectCompleted => {
119 if let Some(haptic_effect) =
120 self.haptic_effects.borrow_mut().remove(&event.id.into())
121 {
122 haptic_effect.request.succeeded();
123 } else {
124 warn!("Failed to find haptic effect for id {}", event.id);
125 }
126 },
127 _ => {},
128 }
129
130 if let Some(event) = gamepad_event {
131 active_webview.notify_input_event(InputEvent::Gamepad(event));
132 }
133 }
134 }
135
136 fn map_gamepad_button(button: gilrs::Button) -> usize {
139 match button {
140 gilrs::Button::South => 0,
141 gilrs::Button::East => 1,
142 gilrs::Button::West => 2,
143 gilrs::Button::North => 3,
144 gilrs::Button::LeftTrigger => 4,
145 gilrs::Button::RightTrigger => 5,
146 gilrs::Button::LeftTrigger2 => 6,
147 gilrs::Button::RightTrigger2 => 7,
148 gilrs::Button::Select => 8,
149 gilrs::Button::Start => 9,
150 gilrs::Button::LeftThumb => 10,
151 gilrs::Button::RightThumb => 11,
152 gilrs::Button::DPadUp => 12,
153 gilrs::Button::DPadDown => 13,
154 gilrs::Button::DPadLeft => 14,
155 gilrs::Button::DPadRight => 15,
156 gilrs::Button::Mode => 16,
157 _ => 17, }
159 }
160
161 fn play_haptic_effect(
162 &self,
163 effect_type: &GamepadHapticEffectType,
164 request: GamepadHapticEffectRequest,
165 ) {
166 let index = request.gamepad_index();
167 let GamepadHapticEffectType::DualRumble(params) = effect_type;
168
169 let mut handle = self.handle.borrow_mut();
170 let Some(connected_gamepad) = handle
171 .gamepads()
172 .find(|gamepad| usize::from(gamepad.0) == index)
173 else {
174 debug!("Couldn't find connected gamepad to play haptic effect on");
175 request.failed();
176 return;
177 };
178
179 let start_delay = Ticks::from_ms(params.start_delay as u32);
180 let duration = Ticks::from_ms(params.duration as u32);
181 let strong_magnitude = (params.strong_magnitude * u16::MAX as f64).round() as u16;
182 let weak_magnitude = (params.weak_magnitude * u16::MAX as f64).round() as u16;
183
184 let scheduling = Replay {
185 after: start_delay,
186 play_for: duration,
187 with_delay: Ticks::from_ms(0),
188 };
189 let effect = EffectBuilder::new()
190 .add_effect(BaseEffect {
191 kind: BaseEffectType::Strong {
192 magnitude: strong_magnitude,
193 },
194 scheduling,
195 envelope: Default::default(),
196 })
197 .add_effect(BaseEffect {
198 kind: BaseEffectType::Weak {
199 magnitude: weak_magnitude,
200 },
201 scheduling,
202 envelope: Default::default(),
203 })
204 .repeat(Repeat::For(start_delay + duration))
205 .add_gamepad(&connected_gamepad.1)
206 .finish(&mut handle)
207 .expect(
208 "Failed to create haptic effect, ensure connected gamepad supports force feedback.",
209 );
210
211 let mut haptic_effects = self.haptic_effects.borrow_mut();
212 haptic_effects.insert(index, HapticEffect { effect, request });
213 haptic_effects[&index]
214 .effect
215 .play()
216 .expect("Failed to play haptic effect.");
217 }
218
219 fn stop_haptic_effect(&self, request: GamepadHapticEffectRequest) {
220 let index = request.gamepad_index();
221
222 let mut haptic_effects = self.haptic_effects.borrow_mut();
223 let Some(haptic_effect) = haptic_effects.get(&index) else {
224 request.failed();
225 return;
226 };
227
228 let stopped_successfully = match haptic_effect.effect.stop() {
229 Ok(()) => true,
230 Err(e) => {
231 debug!("Failed to stop haptic effect: {:?}", e);
232 false
233 },
234 };
235 haptic_effects.remove(&index);
236
237 if stopped_successfully {
238 request.succeeded();
239 } else {
240 request.failed();
241 }
242 }
243}
244
245impl GamepadDelegate for ServoshellGamepadDelegate {
246 fn handle_haptic_effect_request(&self, request: GamepadHapticEffectRequest) {
247 match request.request_type() {
248 GamepadHapticEffectRequestType::Play(effect_type) => {
249 self.play_haptic_effect(&effect_type.clone(), request);
250 },
251 GamepadHapticEffectRequestType::Stop => {
252 self.stop_haptic_effect(request);
253 },
254 }
255 }
256}