sdl3/
joystick.rs

1use crate::sys;
2// use crate::sys::joystick::joystick::SDL_PowerState;
3
4use crate::clear_error;
5use crate::common::{validate_int, IntegerOrSdlError};
6use crate::get_error;
7use crate::guid::Guid;
8use crate::Error;
9use crate::JoystickSubsystem;
10use libc::{c_char, c_void};
11use std::ffi::CStr;
12use std::fmt;
13use sys::power::{SDL_PowerState, SDL_POWERSTATE_UNKNOWN};
14use sys::stdinc::SDL_free;
15
16pub type JoystickId = sys::joystick::SDL_JoystickID;
17
18impl JoystickSubsystem {
19    /// Get joystick instance IDs and names.
20    #[doc(alias = "SDL_GetJoysticks")]
21    pub fn joysticks(&self) -> Result<Vec<JoystickId>, Error> {
22        let mut num_joysticks: i32 = 0;
23        unsafe {
24            let joystick_ids = sys::joystick::SDL_GetJoysticks(&mut num_joysticks);
25            if joystick_ids.is_null() {
26                Err(get_error())
27            } else {
28                let mut instances = Vec::new();
29                for i in 0..num_joysticks {
30                    let id = *joystick_ids.offset(i as isize);
31                    instances.push(id);
32                }
33                SDL_free(joystick_ids as *mut c_void);
34                Ok(instances)
35            }
36        }
37    }
38
39    /// Attempt to open the joystick at index `joystick_index` and return it.
40    #[doc(alias = "SDL_OpenJoystick")]
41    pub fn open(&self, joystick_id: JoystickId) -> Result<Joystick, IntegerOrSdlError> {
42        use crate::common::IntegerOrSdlError::*;
43        let joystick = unsafe { sys::joystick::SDL_OpenJoystick(joystick_id) };
44
45        if joystick.is_null() {
46            Err(SdlError(get_error()))
47        } else {
48            Ok(Joystick {
49                subsystem: self.clone(),
50                raw: joystick,
51            })
52        }
53    }
54
55    /// If state is `true` joystick events are processed, otherwise
56    /// they're ignored.
57    #[doc(alias = "SDL_SetJoystickEventsEnabled")]
58    pub fn set_joystick_events_enabled(&self, state: bool) {
59        unsafe { sys::joystick::SDL_SetJoystickEventsEnabled(state) };
60    }
61
62    /// Return `true` if joystick events are processed.
63    #[doc(alias = "SDL_JoystickEventsEnabled")]
64    pub fn event_state(&self) -> bool {
65        unsafe { sys::joystick::SDL_JoystickEventsEnabled() }
66    }
67
68    /// Force joystick update when not using the event loop
69    #[inline]
70    #[doc(alias = "SDL_UpdateJoysticks")]
71    pub fn update(&self) {
72        unsafe { sys::joystick::SDL_UpdateJoysticks() };
73    }
74}
75
76// power level and percentage together
77pub struct PowerInfo {
78    pub state: PowerLevel,
79    pub percentage: i32,
80}
81
82impl fmt::Debug for PowerInfo {
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84        write!(
85            f,
86            "PowerInfo {{ state: {:?}, percentage: {} }}",
87            self.state, self.percentage
88        )
89    }
90}
91
92#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
93#[repr(i32)]
94pub enum PowerLevel {
95    Unknown = SDL_PowerState::UNKNOWN.0,
96    Error = SDL_PowerState::ERROR.0,
97    OnBattery = SDL_PowerState::ON_BATTERY.0,
98    NoBattery = SDL_PowerState::NO_BATTERY.0,
99    Charging = SDL_PowerState::CHARGING.0,
100    Charged = SDL_PowerState::CHARGED.0,
101}
102
103impl PowerLevel {
104    pub fn from_ll(raw: SDL_PowerState) -> PowerLevel {
105        match raw {
106            SDL_PowerState::UNKNOWN => PowerLevel::Unknown,
107            SDL_PowerState::ERROR => PowerLevel::Error,
108            SDL_PowerState::ON_BATTERY => PowerLevel::OnBattery,
109            SDL_PowerState::NO_BATTERY => PowerLevel::NoBattery,
110            SDL_PowerState::CHARGING => PowerLevel::Charging,
111            SDL_PowerState::CHARGED => PowerLevel::Charged,
112            _ => panic!("Unexpected power level"),
113        }
114    }
115
116    pub fn to_ll(self) -> SDL_PowerState {
117        match self {
118            PowerLevel::Unknown => SDL_PowerState::UNKNOWN,
119            PowerLevel::Error => SDL_PowerState::ERROR,
120            PowerLevel::OnBattery => SDL_PowerState::ON_BATTERY,
121            PowerLevel::NoBattery => SDL_PowerState::NO_BATTERY,
122            PowerLevel::Charging => SDL_PowerState::CHARGING,
123            PowerLevel::Charged => SDL_PowerState::CHARGED,
124        }
125    }
126}
127
128/// Wrapper around the `SDL_Joystick` object
129pub struct Joystick {
130    subsystem: JoystickSubsystem,
131    raw: *mut sys::joystick::SDL_Joystick,
132}
133
134impl Joystick {
135    #[inline]
136    pub const fn subsystem(&self) -> &JoystickSubsystem {
137        &self.subsystem
138    }
139
140    /// Return the name of the joystick or an empty string if no name
141    /// is found.
142    #[doc(alias = "SDL_GetJoystickName")]
143    pub fn name(&self) -> String {
144        let name = unsafe { sys::joystick::SDL_GetJoystickName(self.raw) };
145
146        c_str_to_string(name)
147    }
148
149    /// Return true if the joystick has been opened and currently
150    /// connected.
151    #[doc(alias = "SDL_JoystickConnected")]
152    pub fn connected(&self) -> bool {
153        unsafe { sys::joystick::SDL_JoystickConnected(self.raw) }
154    }
155
156    #[doc(alias = "SDL_GetJoystickID")]
157    pub fn id(&self) -> u32 {
158        let result = unsafe { sys::joystick::SDL_GetJoystickID(self.raw) };
159
160        if result == 0 {
161            // Should only fail if the joystick is NULL.
162            panic!("{}", get_error())
163        } else {
164            result as u32
165        }
166    }
167
168    /// Retrieve the joystick's GUID
169    #[doc(alias = "SDL_GetJoystickGUID")]
170    pub fn guid(&self) -> Guid {
171        let raw = unsafe { sys::joystick::SDL_GetJoystickGUID(self.raw) };
172
173        let guid = Guid { raw };
174
175        if guid.is_zero() {
176            // Should only fail if the joystick is NULL.
177            panic!("{}", get_error())
178        } else {
179            guid
180        }
181    }
182
183    /// Retrieve the battery level of this joystick
184    /// This method doesn't match the name of the SDL API since we have PowerLevel + percentage in
185    /// a PowerInfo struct.
186    #[doc(alias = "SDL_GetJoystickPowerLevel")]
187    pub fn power_info(&self) -> Result<PowerInfo, IntegerOrSdlError> {
188        use crate::common::IntegerOrSdlError::*;
189        clear_error();
190
191        let mut power_pct: core::ffi::c_int = 0;
192        let result = unsafe { sys::joystick::SDL_GetJoystickPowerInfo(self.raw, &mut power_pct) };
193
194        let state = PowerLevel::from_ll(result);
195
196        if result != SDL_POWERSTATE_UNKNOWN {
197            Ok(PowerInfo {
198                state,
199                percentage: power_pct,
200            })
201        } else {
202            let err = get_error();
203
204            if err.is_empty() {
205                Ok(PowerInfo {
206                    state,
207                    percentage: power_pct,
208                })
209            } else {
210                Err(SdlError(err))
211            }
212        }
213    }
214
215    /// Retrieve the number of axes for this joystick
216    #[doc(alias = "SDL_GetNumJoystickAxes")]
217    pub fn num_axes(&self) -> u32 {
218        let result = unsafe { sys::joystick::SDL_GetNumJoystickAxes(self.raw) };
219
220        if result < 0 {
221            // Should only fail if the joystick is NULL.
222            panic!("{}", get_error())
223        } else {
224            result as u32
225        }
226    }
227
228    /// Gets the position of the given `axis`.
229    ///
230    /// The function will fail if the joystick doesn't have the provided axis.
231    #[doc(alias = "SDL_GetJoystickAxis")]
232    pub fn axis(&self, axis: u32) -> Result<i16, IntegerOrSdlError> {
233        use crate::common::IntegerOrSdlError::*;
234        // This interface is a bit messed up: 0 is a valid position
235        // but can also mean that an error occured. As far as I can
236        // tell the only way to know if an error happened is to see if
237        // get_error() returns a non-empty string.
238        clear_error();
239
240        let axis = validate_int(axis, "axis")?;
241        let pos = unsafe { sys::joystick::SDL_GetJoystickAxis(self.raw, axis) };
242
243        if pos != 0 {
244            Ok(pos)
245        } else {
246            let err = get_error();
247
248            if err.is_empty() {
249                Ok(pos)
250            } else {
251                Err(SdlError(err))
252            }
253        }
254    }
255
256    /// Retrieve the number of buttons for this joystick
257    #[doc(alias = "SDL_GetNumJoystickButtons")]
258    pub fn num_buttons(&self) -> u32 {
259        let result = unsafe { sys::joystick::SDL_GetNumJoystickButtons(self.raw) };
260
261        if result < 0 {
262            // Should only fail if the joystick is NULL.
263            panic!("{}", get_error())
264        } else {
265            result as u32
266        }
267    }
268
269    /// Return `Ok(true)` if `button` is pressed.
270    ///
271    /// The function will fail if the joystick doesn't have the provided button.
272    #[doc(alias = "SDL_GetJoystickButton")]
273    pub fn button(&self, button: u32) -> Result<bool, IntegerOrSdlError> {
274        use crate::common::IntegerOrSdlError::*;
275        // Same deal as axis, 0 can mean both unpressed or
276        // error...
277        clear_error();
278
279        let button = validate_int(button, "button")?;
280        let pressed = unsafe { sys::joystick::SDL_GetJoystickButton(self.raw, button) };
281
282        match pressed {
283            true => Ok(true),
284            false => {
285                let err = get_error();
286
287                if err.is_empty() {
288                    // Button is not pressed
289                    Ok(false)
290                } else {
291                    Err(SdlError(err))
292                }
293            }
294        }
295    }
296
297    /// Retrieve the number of balls for this joystick
298    #[doc(alias = "SDL_GetNumJoystickHats")]
299    pub fn num_hats(&self) -> u32 {
300        let result = unsafe { sys::joystick::SDL_GetNumJoystickHats(self.raw) };
301
302        if result < 0 {
303            // Should only fail if the joystick is NULL.
304            panic!("{}", get_error())
305        } else {
306            result as u32
307        }
308    }
309
310    /// Return the position of `hat` for this joystick
311    #[doc(alias = "SDL_GetJoystickHat")]
312    pub fn hat(&self, hat: u32) -> Result<HatState, IntegerOrSdlError> {
313        use crate::common::IntegerOrSdlError::*;
314        // Guess what? This function as well uses 0 to report an error
315        // but 0 is also a valid value (HatState::Centered). So we
316        // have to use the same hack as `axis`...
317        clear_error();
318
319        let hat = validate_int(hat, "hat")?;
320        let result = unsafe { sys::joystick::SDL_GetJoystickHat(self.raw, hat) };
321
322        let state = HatState::from_raw(result as u8);
323
324        if result != 0 {
325            Ok(state)
326        } else {
327            let err = get_error();
328
329            if err.is_empty() {
330                Ok(state)
331            } else {
332                Err(SdlError(err))
333            }
334        }
335    }
336
337    /// Set the rumble motors to their specified intensities, if supported.
338    /// Automatically resets back to zero after `duration_ms` milliseconds have passed.
339    ///
340    /// # Notes
341    ///
342    /// The value range for the intensities is 0 to 0xFFFF.
343    ///
344    /// Do *not* use `std::u32::MAX` or similar for `duration_ms` if you want
345    /// the rumble effect to keep playing for a long time, as this results in
346    /// the effect ending immediately after starting due to an overflow.
347    /// Use some smaller, "huge enough" number instead.
348    ///
349    /// Returns false if the gamepad doesn't support rumble.
350    #[doc(alias = "SDL_RumbleJoystick")]
351    pub fn set_rumble(
352        &mut self,
353        low_frequency_rumble: u16,
354        high_frequency_rumble: u16,
355        duration_ms: u32,
356    ) -> bool {
357        unsafe {
358            sys::joystick::SDL_RumbleJoystick(
359                self.raw,
360                low_frequency_rumble,
361                high_frequency_rumble,
362                duration_ms,
363            )
364        }
365    }
366
367    /// Start a rumble effect in the joystick's triggers.
368    #[doc(alias = "SDL_RumbleJoystickTriggers")]
369    pub fn set_rumble_triggers(
370        &mut self,
371        left_rumble: u16,
372        right_rumble: u16,
373        duration_ms: u32,
374    ) -> Result<(), IntegerOrSdlError> {
375        let result = unsafe {
376            sys::joystick::SDL_RumbleJoystickTriggers(
377                self.raw,
378                left_rumble,
379                right_rumble,
380                duration_ms,
381            )
382        };
383
384        if !result {
385            Err(IntegerOrSdlError::SdlError(get_error()))
386        } else {
387            Ok(())
388        }
389    }
390
391    /// Query whether a joystick has an LED.
392    #[doc(alias = "SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN")]
393    pub unsafe fn has_led(&self) -> bool {
394        let props = unsafe { sys::joystick::SDL_GetJoystickProperties(self.raw) };
395        sys::properties::SDL_GetBooleanProperty(
396            props,
397            sys::joystick::SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN,
398            false,
399        )
400    }
401
402    /// Query whether a joystick has rumble support.
403    #[doc(alias = "SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN")]
404    pub unsafe fn has_rumble(&self) -> bool {
405        let props = unsafe { sys::joystick::SDL_GetJoystickProperties(self.raw) };
406        sys::properties::SDL_GetBooleanProperty(
407            props,
408            sys::joystick::SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN,
409            false,
410        )
411    }
412
413    /// Query whether a joystick has rumble support on triggers.
414    #[doc(alias = "SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN")]
415    pub unsafe fn has_rumble_triggers(&self) -> bool {
416        let props = unsafe { sys::joystick::SDL_GetJoystickProperties(self.raw) };
417        sys::properties::SDL_GetBooleanProperty(
418            props,
419            sys::joystick::SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN,
420            false,
421        )
422    }
423
424    /// Update a joystick's LED color.
425    #[doc(alias = "SDL_SetJoystickLED")]
426    pub fn set_led(&mut self, red: u8, green: u8, blue: u8) -> Result<(), IntegerOrSdlError> {
427        let result = unsafe { sys::joystick::SDL_SetJoystickLED(self.raw, red, green, blue) };
428
429        if !result {
430            Err(IntegerOrSdlError::SdlError(get_error()))
431        } else {
432            Ok(())
433        }
434    }
435
436    /// Send a joystick specific effect packet.
437    #[doc(alias = "SDL_SendJoystickEffect")]
438    pub fn send_effect(&mut self, data: &[u8]) -> Result<(), IntegerOrSdlError> {
439        let result = unsafe {
440            sys::joystick::SDL_SendJoystickEffect(
441                self.raw,
442                data.as_ptr() as *const libc::c_void,
443                data.len() as i32,
444            )
445        };
446
447        if !result {
448            Err(IntegerOrSdlError::SdlError(get_error()))
449        } else {
450            Ok(())
451        }
452    }
453}
454
455impl Drop for Joystick {
456    #[doc(alias = "SDL_CloseJoystick")]
457    fn drop(&mut self) {
458        if self.connected() {
459            unsafe { sys::joystick::SDL_CloseJoystick(self.raw) }
460        }
461    }
462}
463
464/// This is represented in SDL2 as a bitfield but obviously not all
465/// combinations make sense: 5 for instance would mean up and down at
466/// the same time... To simplify things I turn it into an enum which
467/// is how the SDL2 docs present it anyway (using macros).
468#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
469pub enum HatState {
470    Centered = 0,
471    Up = 0x01,
472    Right = 0x02,
473    Down = 0x04,
474    Left = 0x08,
475    RightUp = 0x02 | 0x01,
476    RightDown = 0x02 | 0x04,
477    LeftUp = 0x08 | 0x01,
478    LeftDown = 0x08 | 0x04,
479}
480
481impl HatState {
482    pub fn from_raw(raw: u8) -> HatState {
483        match raw {
484            0 => HatState::Centered,
485            1 => HatState::Up,
486            2 => HatState::Right,
487            4 => HatState::Down,
488            8 => HatState::Left,
489            3 => HatState::RightUp,
490            6 => HatState::RightDown,
491            9 => HatState::LeftUp,
492            12 => HatState::LeftDown,
493
494            // The Xinput driver on Windows can report hat states on certain hardware that don't
495            // make any sense from a gameplay perspective, and so aren't worth putting in the
496            // HatState enumeration.
497            _ => HatState::Centered,
498        }
499    }
500
501    pub fn to_raw(self) -> u8 {
502        match self {
503            HatState::Centered => 0,
504            HatState::Up => 1,
505            HatState::Right => 2,
506            HatState::Down => 4,
507            HatState::Left => 8,
508            HatState::RightUp => 3,
509            HatState::RightDown => 6,
510            HatState::LeftUp => 9,
511            HatState::LeftDown => 12,
512        }
513    }
514}
515
516#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
517#[repr(i32)]
518pub enum ConnectionState {
519    Invalid = sys::joystick::SDL_JoystickConnectionState::INVALID.0,
520    Unknown = sys::joystick::SDL_JoystickConnectionState::UNKNOWN.0,
521    Wired = sys::joystick::SDL_JoystickConnectionState::WIRED.0,
522    Wireless = sys::joystick::SDL_JoystickConnectionState::WIRELESS.0,
523}
524
525impl ConnectionState {
526    pub fn from_ll(bitflags: sys::joystick::SDL_JoystickConnectionState) -> ConnectionState {
527        match bitflags {
528            sys::joystick::SDL_JoystickConnectionState::UNKNOWN => ConnectionState::Unknown,
529            sys::joystick::SDL_JoystickConnectionState::WIRED => ConnectionState::Wired,
530            sys::joystick::SDL_JoystickConnectionState::WIRELESS => ConnectionState::Wireless,
531            _ => ConnectionState::Invalid,
532        }
533    }
534
535    pub fn to_ll(self) -> sys::joystick::SDL_JoystickConnectionState {
536        match self {
537            ConnectionState::Invalid => sys::joystick::SDL_JoystickConnectionState::INVALID,
538            ConnectionState::Unknown => sys::joystick::SDL_JoystickConnectionState::UNKNOWN,
539            ConnectionState::Wired => sys::joystick::SDL_JoystickConnectionState::WIRED,
540            ConnectionState::Wireless => sys::joystick::SDL_JoystickConnectionState::WIRELESS,
541        }
542    }
543}
544
545/// Convert C string `c_str` to a String. Return an empty string if
546/// `c_str` is NULL.
547fn c_str_to_string(c_str: *const c_char) -> String {
548    if c_str.is_null() {
549        String::new()
550    } else {
551        let bytes = unsafe { CStr::from_ptr(c_str as *const _).to_bytes() };
552
553        String::from_utf8_lossy(bytes).to_string()
554    }
555}