use crate::sys;
use crate::clear_error;
use crate::common::{validate_int, IntegerOrSdlError};
use crate::get_error;
use crate::guid::Guid;
use crate::Error;
use crate::JoystickSubsystem;
use libc::{c_char, c_void};
use std::ffi::{CStr, CString};
use std::fmt;
use sys::power::{SDL_PowerState, SDL_POWERSTATE_UNKNOWN};
use sys::stdinc::SDL_free;
pub type JoystickId = sys::joystick::SDL_JoystickID;
impl JoystickSubsystem {
#[doc(alias = "SDL_GetJoysticks")]
pub fn joysticks(&self) -> Result<Vec<JoystickId>, Error> {
let mut num_joysticks: i32 = 0;
unsafe {
let joystick_ids = sys::joystick::SDL_GetJoysticks(&mut num_joysticks);
if joystick_ids.is_null() {
Err(get_error())
} else {
let mut instances = Vec::new();
for i in 0..num_joysticks {
let id = *joystick_ids.offset(i as isize);
instances.push(id);
}
SDL_free(joystick_ids as *mut c_void);
Ok(instances)
}
}
}
#[doc(alias = "SDL_OpenJoystick")]
pub fn open(&self, joystick_id: JoystickId) -> Result<Joystick, IntegerOrSdlError> {
use crate::common::IntegerOrSdlError::*;
let joystick = unsafe { sys::joystick::SDL_OpenJoystick(joystick_id) };
if joystick.is_null() {
Err(SdlError(get_error()))
} else {
Ok(Joystick {
subsystem: self.clone(),
raw: joystick,
})
}
}
#[doc(alias = "SDL_SetJoystickEventsEnabled")]
pub fn set_joystick_events_enabled(&self, state: bool) {
unsafe { sys::joystick::SDL_SetJoystickEventsEnabled(state) };
}
#[doc(alias = "SDL_JoystickEventsEnabled")]
pub fn event_state(&self) -> bool {
unsafe { sys::joystick::SDL_JoystickEventsEnabled() }
}
#[inline]
#[doc(alias = "SDL_UpdateJoysticks")]
pub fn update(&self) {
unsafe { sys::joystick::SDL_UpdateJoysticks() };
}
#[doc(alias = "SDL_IsJoystickVirtual")]
pub fn is_virtual(&self, joystick_id: JoystickId) -> bool {
unsafe { sys::joystick::SDL_IsJoystickVirtual(joystick_id) }
}
pub fn attach_virtual_joystick(
&self,
desc: VirtualJoystickDescription,
) -> Result<VirtualJoystickConnection, IntegerOrSdlError> {
let mut raw_desc = sys::joystick::SDL_VirtualJoystickDesc::new();
raw_desc.name = desc.name.as_ptr();
raw_desc.r#type = desc.joystick_type.to_ll().0 as u16;
raw_desc.naxes = desc.num_axes;
raw_desc.nbuttons = desc.num_buttons;
raw_desc.nhats = desc.num_hats;
raw_desc.axis_mask = desc.axis_mask;
raw_desc.button_mask = desc.button_mask;
let joystick_id = unsafe { sys::joystick::SDL_AttachVirtualJoystick(&raw_desc) };
if joystick_id.0 == 0 {
Err(IntegerOrSdlError::SdlError(get_error()))
} else {
let joystick = self.open(joystick_id)?;
Ok(VirtualJoystickConnection { inner: joystick })
}
}
}
pub struct PowerInfo {
pub state: PowerLevel,
pub percentage: i32,
}
impl fmt::Debug for PowerInfo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"PowerInfo {{ state: {:?}, percentage: {} }}",
self.state, self.percentage
)
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
#[repr(i32)]
pub enum PowerLevel {
Unknown = SDL_PowerState::UNKNOWN.0,
Error = SDL_PowerState::ERROR.0,
OnBattery = SDL_PowerState::ON_BATTERY.0,
NoBattery = SDL_PowerState::NO_BATTERY.0,
Charging = SDL_PowerState::CHARGING.0,
Charged = SDL_PowerState::CHARGED.0,
}
impl PowerLevel {
pub fn from_ll(raw: SDL_PowerState) -> PowerLevel {
match raw {
SDL_PowerState::UNKNOWN => PowerLevel::Unknown,
SDL_PowerState::ERROR => PowerLevel::Error,
SDL_PowerState::ON_BATTERY => PowerLevel::OnBattery,
SDL_PowerState::NO_BATTERY => PowerLevel::NoBattery,
SDL_PowerState::CHARGING => PowerLevel::Charging,
SDL_PowerState::CHARGED => PowerLevel::Charged,
_ => panic!("Unexpected power level"),
}
}
pub fn to_ll(self) -> SDL_PowerState {
match self {
PowerLevel::Unknown => SDL_PowerState::UNKNOWN,
PowerLevel::Error => SDL_PowerState::ERROR,
PowerLevel::OnBattery => SDL_PowerState::ON_BATTERY,
PowerLevel::NoBattery => SDL_PowerState::NO_BATTERY,
PowerLevel::Charging => SDL_PowerState::CHARGING,
PowerLevel::Charged => SDL_PowerState::CHARGED,
}
}
}
pub struct Joystick {
subsystem: JoystickSubsystem,
raw: *mut sys::joystick::SDL_Joystick,
}
impl Joystick {
#[inline]
pub const fn subsystem(&self) -> &JoystickSubsystem {
&self.subsystem
}
#[doc(alias = "SDL_GetJoystickName")]
pub fn name(&self) -> String {
let name = unsafe { sys::joystick::SDL_GetJoystickName(self.raw) };
c_str_to_string(name)
}
#[doc(alias = "SDL_JoystickConnected")]
pub fn connected(&self) -> bool {
unsafe { sys::joystick::SDL_JoystickConnected(self.raw) }
}
#[doc(alias = "SDL_GetJoystickID")]
pub fn id(&self) -> u32 {
let result = unsafe { sys::joystick::SDL_GetJoystickID(self.raw) };
if result == 0 {
panic!("{}", get_error())
} else {
u32::from(result)
}
}
#[doc(alias = "SDL_GetJoystickGUID")]
pub fn guid(&self) -> Guid {
let raw = unsafe { sys::joystick::SDL_GetJoystickGUID(self.raw) };
let guid = Guid { raw };
if guid.is_zero() {
panic!("{}", get_error())
} else {
guid
}
}
#[doc(alias = "SDL_GetJoystickPowerLevel")]
pub fn power_info(&self) -> Result<PowerInfo, IntegerOrSdlError> {
use crate::common::IntegerOrSdlError::*;
clear_error();
let mut power_pct: core::ffi::c_int = 0;
let result = unsafe { sys::joystick::SDL_GetJoystickPowerInfo(self.raw, &mut power_pct) };
let state = PowerLevel::from_ll(result);
if result != SDL_POWERSTATE_UNKNOWN {
Ok(PowerInfo {
state,
percentage: power_pct,
})
} else {
let err = get_error();
if err.is_empty() {
Ok(PowerInfo {
state,
percentage: power_pct,
})
} else {
Err(SdlError(err))
}
}
}
#[doc(alias = "SDL_GetNumJoystickAxes")]
pub fn num_axes(&self) -> u32 {
let result = unsafe { sys::joystick::SDL_GetNumJoystickAxes(self.raw) };
if result < 0 {
panic!("{}", get_error())
} else {
result as u32
}
}
#[doc(alias = "SDL_GetJoystickAxis")]
pub fn axis(&self, axis: u32) -> Result<i16, IntegerOrSdlError> {
use crate::common::IntegerOrSdlError::*;
clear_error();
let axis = validate_int(axis, "axis")?;
let pos = unsafe { sys::joystick::SDL_GetJoystickAxis(self.raw, axis) };
if pos != 0 {
Ok(pos)
} else {
let err = get_error();
if err.is_empty() {
Ok(pos)
} else {
Err(SdlError(err))
}
}
}
#[doc(alias = "SDL_GetNumJoystickButtons")]
pub fn num_buttons(&self) -> u32 {
let result = unsafe { sys::joystick::SDL_GetNumJoystickButtons(self.raw) };
if result < 0 {
panic!("{}", get_error())
} else {
result as u32
}
}
#[doc(alias = "SDL_GetJoystickButton")]
pub fn button(&self, button: u32) -> Result<bool, IntegerOrSdlError> {
use crate::common::IntegerOrSdlError::*;
clear_error();
let button = validate_int(button, "button")?;
let pressed = unsafe { sys::joystick::SDL_GetJoystickButton(self.raw, button) };
match pressed {
true => Ok(true),
false => {
let err = get_error();
if err.is_empty() {
Ok(false)
} else {
Err(SdlError(err))
}
}
}
}
#[doc(alias = "SDL_GetNumJoystickHats")]
pub fn num_hats(&self) -> u32 {
let result = unsafe { sys::joystick::SDL_GetNumJoystickHats(self.raw) };
if result < 0 {
panic!("{}", get_error())
} else {
result as u32
}
}
#[doc(alias = "SDL_GetJoystickHat")]
pub fn hat(&self, hat: u32) -> Result<HatState, IntegerOrSdlError> {
use crate::common::IntegerOrSdlError::*;
clear_error();
let hat = validate_int(hat, "hat")?;
let result = unsafe { sys::joystick::SDL_GetJoystickHat(self.raw, hat) };
let state = HatState::from_raw(result as u8);
if result != 0 {
Ok(state)
} else {
let err = get_error();
if err.is_empty() {
Ok(state)
} else {
Err(SdlError(err))
}
}
}
#[doc(alias = "SDL_RumbleJoystick")]
pub fn set_rumble(
&mut self,
low_frequency_rumble: u16,
high_frequency_rumble: u16,
duration_ms: u32,
) -> bool {
unsafe {
sys::joystick::SDL_RumbleJoystick(
self.raw,
low_frequency_rumble,
high_frequency_rumble,
duration_ms,
)
}
}
#[doc(alias = "SDL_RumbleJoystickTriggers")]
pub fn set_rumble_triggers(
&mut self,
left_rumble: u16,
right_rumble: u16,
duration_ms: u32,
) -> Result<(), IntegerOrSdlError> {
let result = unsafe {
sys::joystick::SDL_RumbleJoystickTriggers(
self.raw,
left_rumble,
right_rumble,
duration_ms,
)
};
if !result {
Err(IntegerOrSdlError::SdlError(get_error()))
} else {
Ok(())
}
}
#[doc(alias = "SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN")]
pub unsafe fn has_led(&self) -> bool {
let props = unsafe { sys::joystick::SDL_GetJoystickProperties(self.raw) };
sys::properties::SDL_GetBooleanProperty(
props,
sys::joystick::SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN,
false,
)
}
#[doc(alias = "SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN")]
pub unsafe fn has_rumble(&self) -> bool {
let props = unsafe { sys::joystick::SDL_GetJoystickProperties(self.raw) };
sys::properties::SDL_GetBooleanProperty(
props,
sys::joystick::SDL_PROP_JOYSTICK_CAP_RUMBLE_BOOLEAN,
false,
)
}
#[doc(alias = "SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN")]
pub unsafe fn has_rumble_triggers(&self) -> bool {
let props = unsafe { sys::joystick::SDL_GetJoystickProperties(self.raw) };
sys::properties::SDL_GetBooleanProperty(
props,
sys::joystick::SDL_PROP_JOYSTICK_CAP_TRIGGER_RUMBLE_BOOLEAN,
false,
)
}
#[doc(alias = "SDL_SetJoystickLED")]
pub fn set_led(&mut self, red: u8, green: u8, blue: u8) -> Result<(), IntegerOrSdlError> {
let result = unsafe { sys::joystick::SDL_SetJoystickLED(self.raw, red, green, blue) };
if !result {
Err(IntegerOrSdlError::SdlError(get_error()))
} else {
Ok(())
}
}
#[doc(alias = "SDL_SendJoystickEffect")]
pub fn send_effect(&mut self, data: &[u8]) -> Result<(), IntegerOrSdlError> {
let result = unsafe {
sys::joystick::SDL_SendJoystickEffect(
self.raw,
data.as_ptr() as *const libc::c_void,
data.len() as i32,
)
};
if !result {
Err(IntegerOrSdlError::SdlError(get_error()))
} else {
Ok(())
}
}
#[doc(alias = "SDL_IsJoystickVirtual")]
pub fn is_virtual(&self) -> bool {
let id = sys::joystick::SDL_JoystickID(self.id());
unsafe { sys::joystick::SDL_IsJoystickVirtual(id) }
}
#[doc(alias = "SDL_SetJoystickVirtualAxis")]
pub fn set_virtual_axis(&self, axis: u32, state: i16) -> Result<(), IntegerOrSdlError> {
let axis = validate_int(axis, "axis")?;
if unsafe { sys::joystick::SDL_SetJoystickVirtualAxis(self.raw, axis, state) } {
Ok(())
} else {
Err(IntegerOrSdlError::SdlError(get_error()))
}
}
#[doc(alias = "SDL_SetJoystickVirtualButton")]
pub fn set_virtual_button(&self, button: u32, state: bool) -> Result<(), IntegerOrSdlError> {
let button = validate_int(button, "button")?;
if unsafe { sys::joystick::SDL_SetJoystickVirtualButton(self.raw, button, state) } {
Ok(())
} else {
Err(IntegerOrSdlError::SdlError(get_error()))
}
}
#[doc(alias = "SDL_SetJoystickVirtualHat")]
pub fn set_virtual_hat(&self, hat: u32, state: HatState) -> Result<(), IntegerOrSdlError> {
let hat = validate_int(hat, "hat")?;
if unsafe { sys::joystick::SDL_SetJoystickVirtualHat(self.raw, hat, state.to_raw()) } {
Ok(())
} else {
Err(IntegerOrSdlError::SdlError(get_error()))
}
}
}
impl Drop for Joystick {
#[doc(alias = "SDL_CloseJoystick")]
fn drop(&mut self) {
if self.connected() {
unsafe { sys::joystick::SDL_CloseJoystick(self.raw) }
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum HatState {
Centered = 0,
Up = 0x01,
Right = 0x02,
Down = 0x04,
Left = 0x08,
RightUp = 0x02 | 0x01,
RightDown = 0x02 | 0x04,
LeftUp = 0x08 | 0x01,
LeftDown = 0x08 | 0x04,
}
impl HatState {
pub fn from_raw(raw: u8) -> HatState {
match raw {
0 => HatState::Centered,
1 => HatState::Up,
2 => HatState::Right,
4 => HatState::Down,
8 => HatState::Left,
3 => HatState::RightUp,
6 => HatState::RightDown,
9 => HatState::LeftUp,
12 => HatState::LeftDown,
_ => HatState::Centered,
}
}
pub fn to_raw(self) -> u8 {
match self {
HatState::Centered => 0,
HatState::Up => 1,
HatState::Right => 2,
HatState::Down => 4,
HatState::Left => 8,
HatState::RightUp => 3,
HatState::RightDown => 6,
HatState::LeftUp => 9,
HatState::LeftDown => 12,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(i32)]
pub enum ConnectionState {
Invalid = sys::joystick::SDL_JoystickConnectionState::INVALID.0,
Unknown = sys::joystick::SDL_JoystickConnectionState::UNKNOWN.0,
Wired = sys::joystick::SDL_JoystickConnectionState::WIRED.0,
Wireless = sys::joystick::SDL_JoystickConnectionState::WIRELESS.0,
}
impl ConnectionState {
pub fn from_ll(bitflags: sys::joystick::SDL_JoystickConnectionState) -> ConnectionState {
match bitflags {
sys::joystick::SDL_JoystickConnectionState::UNKNOWN => ConnectionState::Unknown,
sys::joystick::SDL_JoystickConnectionState::WIRED => ConnectionState::Wired,
sys::joystick::SDL_JoystickConnectionState::WIRELESS => ConnectionState::Wireless,
_ => ConnectionState::Invalid,
}
}
pub fn to_ll(self) -> sys::joystick::SDL_JoystickConnectionState {
match self {
ConnectionState::Invalid => sys::joystick::SDL_JoystickConnectionState::INVALID,
ConnectionState::Unknown => sys::joystick::SDL_JoystickConnectionState::UNKNOWN,
ConnectionState::Wired => sys::joystick::SDL_JoystickConnectionState::WIRED,
ConnectionState::Wireless => sys::joystick::SDL_JoystickConnectionState::WIRELESS,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(i32)]
pub enum JoystickType {
Unknown = sys::joystick::SDL_JoystickType::UNKNOWN.0,
Gamepad = sys::joystick::SDL_JoystickType::GAMEPAD.0,
Wheel = sys::joystick::SDL_JoystickType::WHEEL.0,
ArcadeStick = sys::joystick::SDL_JoystickType::ARCADE_STICK.0,
FlightStick = sys::joystick::SDL_JoystickType::FLIGHT_STICK.0,
DancePad = sys::joystick::SDL_JoystickType::DANCE_PAD.0,
Guitar = sys::joystick::SDL_JoystickType::GUITAR.0,
DrumKit = sys::joystick::SDL_JoystickType::DRUM_KIT.0,
ArcadePad = sys::joystick::SDL_JoystickType::ARCADE_PAD.0,
Throttle = sys::joystick::SDL_JoystickType::THROTTLE.0,
}
impl JoystickType {
pub fn from_ll(kind: sys::joystick::SDL_JoystickType) -> JoystickType {
match kind {
sys::joystick::SDL_JoystickType::GAMEPAD => JoystickType::Gamepad,
sys::joystick::SDL_JoystickType::WHEEL => JoystickType::Wheel,
sys::joystick::SDL_JoystickType::ARCADE_STICK => JoystickType::ArcadeStick,
sys::joystick::SDL_JoystickType::FLIGHT_STICK => JoystickType::FlightStick,
sys::joystick::SDL_JoystickType::DANCE_PAD => JoystickType::DancePad,
sys::joystick::SDL_JoystickType::GUITAR => JoystickType::Guitar,
sys::joystick::SDL_JoystickType::DRUM_KIT => JoystickType::DrumKit,
sys::joystick::SDL_JoystickType::ARCADE_PAD => JoystickType::ArcadePad,
sys::joystick::SDL_JoystickType::THROTTLE => JoystickType::Throttle,
_ => JoystickType::Unknown,
}
}
pub fn to_ll(self) -> sys::joystick::SDL_JoystickType {
match self {
JoystickType::Unknown => sys::joystick::SDL_JoystickType::UNKNOWN,
JoystickType::Gamepad => sys::joystick::SDL_JoystickType::GAMEPAD,
JoystickType::Wheel => sys::joystick::SDL_JoystickType::WHEEL,
JoystickType::ArcadeStick => sys::joystick::SDL_JoystickType::ARCADE_STICK,
JoystickType::FlightStick => sys::joystick::SDL_JoystickType::FLIGHT_STICK,
JoystickType::DancePad => sys::joystick::SDL_JoystickType::DANCE_PAD,
JoystickType::Guitar => sys::joystick::SDL_JoystickType::GUITAR,
JoystickType::DrumKit => sys::joystick::SDL_JoystickType::DRUM_KIT,
JoystickType::ArcadePad => sys::joystick::SDL_JoystickType::ARCADE_PAD,
JoystickType::Throttle => sys::joystick::SDL_JoystickType::THROTTLE,
}
}
}
fn c_str_to_string(c_str: *const c_char) -> String {
if c_str.is_null() {
String::new()
} else {
let bytes = unsafe { CStr::from_ptr(c_str as *const _).to_bytes() };
String::from_utf8_lossy(bytes).to_string()
}
}
fn string_to_c_str(string: &str) -> CString {
let end = string.bytes().position(|b| b == 0).unwrap_or(string.len());
CString::new(&string.as_bytes()[..end]).expect("interior NULs stripped above")
}
pub struct VirtualJoystickConnection {
inner: Joystick,
}
impl VirtualJoystickConnection {
pub fn id(&self) -> JoystickId {
sys::joystick::SDL_JoystickID(self.inner.id())
}
}
impl Drop for VirtualJoystickConnection {
#[doc(alias = "SDL_DetachVirtualJoystick")]
fn drop(&mut self) {
let id = sys::joystick::SDL_JoystickID(self.inner.id());
unsafe { sys::joystick::SDL_DetachVirtualJoystick(id) };
}
}
pub struct VirtualJoystickDescription {
name: CString,
joystick_type: JoystickType,
num_axes: u16,
num_buttons: u16,
num_hats: u16,
axis_mask: u32,
button_mask: u32,
}
impl Default for VirtualJoystickDescription {
fn default() -> Self {
Self::new()
}
}
impl VirtualJoystickDescription {
pub fn new() -> Self {
Self {
name: string_to_c_str("Unnamed Virtual Joystick"),
joystick_type: JoystickType::Unknown,
num_axes: 0,
num_buttons: 0,
num_hats: 0,
axis_mask: 0,
button_mask: 0,
}
}
pub fn name(self, name: &str) -> Self {
let mut desc = self;
desc.name = string_to_c_str(name);
desc
}
pub fn joystick_type(self, joystick_type: JoystickType) -> Self {
let mut desc = self;
desc.joystick_type = joystick_type;
desc
}
pub fn num_hats(self, num_hats: u16) -> Self {
let mut desc = self;
desc.num_hats = num_hats;
desc
}
pub fn with_axis(self, axis: crate::gamepad::Axis) -> Self {
let mut desc = self;
let axis_code = axis.to_ll().0 as u16;
if axis_code >= desc.num_axes {
desc.num_axes = axis_code + 1;
}
desc.axis_mask |= 1 << axis_code;
desc
}
pub fn with_button(self, button: crate::gamepad::Button) -> Self {
let mut desc = self;
let button_code = button.to_ll().0 as u16;
if button_code >= desc.num_buttons {
desc.num_buttons = button_code + 1;
}
desc.button_mask |= 1 << button_code;
desc
}
pub fn with_axes<T>(self, axes: T) -> Self
where
T: IntoIterator<Item = crate::gamepad::Axis>,
{
let mut desc = self;
for axis in axes {
desc = desc.with_axis(axis);
}
desc
}
pub fn with_buttons<T>(self, buttons: T) -> Self
where
T: IntoIterator<Item = crate::gamepad::Button>,
{
let mut desc = self;
for button in buttons {
desc = desc.with_button(button);
}
desc
}
}