Skip to main content

gcrecomp_runtime/input/backends/
sdl2.rs

1// SDL2 backend for cross-platform controller support
2use crate::input::backends::{Backend, ControllerInfo, ControllerType, HatState, RawInput};
3use anyhow::Result;
4use sdl2::GameControllerSubsystem;
5use std::collections::HashMap;
6
7pub struct SDL2Backend {
8    sdl_context: sdl2::Sdl,
9    controller_subsystem: GameControllerSubsystem,
10    controllers: HashMap<usize, sdl2::controller::GameController>,
11    next_id: usize,
12}
13
14impl SDL2Backend {
15    pub fn new() -> Result<Self> {
16        let sdl_context = sdl2::init()
17            .map_err(|e| anyhow::anyhow!("Failed to initialize SDL2: {}", e))?;
18
19        let controller_subsystem = sdl_context
20            .game_controller()
21            .map_err(|e| anyhow::anyhow!("Failed to initialize SDL2 game controller subsystem: {}", e))?;
22
23        Ok(Self {
24            sdl_context,
25            controller_subsystem,
26            controllers: HashMap::new(),
27            next_id: 0,
28        })
29    }
30}
31
32impl Backend for SDL2Backend {
33    fn update(&mut self) -> Result<()> {
34        // SDL2 handles events automatically
35        Ok(())
36    }
37
38    fn enumerate_controllers(&mut self) -> Result<Vec<ControllerInfo>> {
39        let mut controllers = Vec::new();
40        let num_joysticks = self
41            .controller_subsystem
42            .num_joysticks()
43            .map_err(|e| anyhow::anyhow!("Failed to get joystick count: {}", e))?;
44
45        for i in 0..num_joysticks {
46            if self.controller_subsystem.is_game_controller(i) {
47                if let Ok(name) = self.controller_subsystem.name_for_index(i) {
48                    let controller_type = detect_controller_type(&name);
49                    let id = i as usize;
50
51                    // Try to open controller to add to our map
52                    if let Ok(controller) = self.controller_subsystem.open(i) {
53                        self.controllers.insert(id, controller);
54                    }
55
56                    controllers.push(ControllerInfo {
57                        id,
58                        name: name.to_string(),
59                        controller_type,
60                        button_count: 16, // SDL2 standard
61                        axis_count: 6,    // 2 sticks + 2 triggers
62                    });
63                }
64            }
65        }
66
67        Ok(controllers)
68    }
69
70    fn get_input(&self, controller_id: usize) -> Result<RawInput> {
71        if let Some(controller) = self.controllers.get(&controller_id) {
72            let mut buttons = Vec::new();
73            let mut axes = Vec::new();
74            let mut triggers = Vec::new();
75
76            // Read buttons - SDL2 button enum
77            use sdl2::controller::Button;
78            buttons.push(controller.button(Button::A));
79            buttons.push(controller.button(Button::B));
80            buttons.push(controller.button(Button::X));
81            buttons.push(controller.button(Button::Y));
82            buttons.push(controller.button(Button::Back));
83            buttons.push(controller.button(Button::Guide));
84            buttons.push(controller.button(Button::Start));
85            buttons.push(controller.button(Button::LeftStick));
86            buttons.push(controller.button(Button::RightStick));
87            buttons.push(controller.button(Button::LeftShoulder));
88            buttons.push(controller.button(Button::RightShoulder));
89            buttons.push(controller.button(Button::DPadUp));
90            buttons.push(controller.button(Button::DPadDown));
91            buttons.push(controller.button(Button::DPadLeft));
92            buttons.push(controller.button(Button::DPadRight));
93            buttons.push(false); // Extra button slot
94
95            // Read axes
96            use sdl2::controller::Axis;
97            axes.push(controller.axis(Axis::LeftX) as f32 / 32768.0);
98            axes.push(controller.axis(Axis::LeftY) as f32 / 32768.0);
99            axes.push(controller.axis(Axis::RightX) as f32 / 32768.0);
100            axes.push(controller.axis(Axis::RightY) as f32 / 32768.0);
101
102            // Read triggers
103            triggers.push(controller.axis(Axis::TriggerLeft) as f32 / 32768.0);
104            triggers.push(controller.axis(Axis::TriggerRight) as f32 / 32768.0);
105
106            Ok(RawInput {
107                buttons,
108                axes,
109                triggers,
110                hat: None,
111            })
112        } else {
113            anyhow::bail!("Controller not found: {}", controller_id);
114        }
115    }
116}
117
118fn detect_controller_type(name: &str) -> ControllerType {
119    let name_lower = name.to_lowercase();
120    if name_lower.contains("xbox") || name_lower.contains("xinput") {
121        ControllerType::Xbox
122    } else if name_lower.contains("playstation")
123        || name_lower.contains("dualshock")
124        || name_lower.contains("dualsense")
125    {
126        ControllerType::PlayStation
127    } else if name_lower.contains("switch") || name_lower.contains("pro controller") {
128        ControllerType::SwitchPro
129    } else {
130        ControllerType::Generic
131    }
132}