use super::NativeManager;
#[repr(C)]
struct TimeVal {
tv_sec: isize,
tv_usec: isize,
}
#[repr(C)]
struct Event {
ev_time: TimeVal,
ev_type: i16,
ev_code: i16,
ev_value: i32,
}
/// A button on a controller.
///
/// Example controller:
///
/// <img src="https://jeronaldaron.github.io/stick/res/controller.png" width="292">
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum Btn {
/// D-PAD LEFT / LEFT ARROW KEY / SCROLL UP "Previous Item"
Left = 0u8,
/// D-PAD RIGHT / RIGHT ARROW KEY / SCROLL DOWN "Next Item"
Right = 1,
/// D-PAD UP / UP ARROW KEY / R KEY "Reload/Tinker"
Up = 2,
/// D-PAD DOWN / DOWN ARROW KEY / X KEY "Put Away"
Down = 3,
/// ONE OF: Y OR X BUTTON / LEFT CLICK "Action/Attack/Execute/Use Item"
X = 4,
/// A BUTTON / ENTER KEY / RIGHT CLICK "Talk/Inspect/Ok/Accept"
A = 5,
/// ONE OF: Y OR X BUTTON / SPACE KEY "Jump/Upward"
Y = 6,
/// B BUTTON / SHIFT KEY "Speed Things Up/Cancel"
B = 7,
/// L THROTTLE BTN / CTRL KEY "Crouch/Sneak"
L = 8,
/// R THROTTLE BTN / ALT KEY "Slingshot/Bow & Arrow"
R = 9,
/// L BTN / W BTN / BACKSPACE KEY "Throw/Send/Wave"
W = 10,
/// R BTN / Z BTN / Z KEY "Alternative Action/Kick"
Z = 11,
/// BACK / SELECT / QUIT / ESCAPE KEY / EXIT "Menu / Quit / Finish"
F = 12,
/// START / E KEY / MENU / FIND "Inventory/Pockets/Find"
E = 13,
/// JOY1 PUSH / C KEY "Toggle Crouch/Sneak"
D = 14,
/// JOY2 PUSH / F KEY "Camera/Binoculars"
C = 15,
}
impl From<Btn> for u8 {
fn from(b: Btn) -> Self {
b as u8
}
}
/// The state of a joystick, gamepad or controller device.
#[derive(Debug, Copy, Clone, Default)]
#[repr(C)]
pub struct Device {
// Joystick 1 (XY).
joy: (i8, i8),
// L & R Throttles.
lrt: (u8, u8),
// Joystick 2 (Z-rotation,W-tilt)
cam: (i8, i8),
// Panning stick
pan: i16,
// 64 #'d Buttons (Left=Even,Right=Odd).
btn: u64,
// 128 bits so far.
// Native handle to the device (fd or index).
native_handle: u32,
// Hardware ID for this device.
hardware_id: u32,
abs_min: i32,
abs_max: i32,
// 256 bits total
}
impl std::fmt::Display for Device {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
let joy: (f32, f32) = (
(self.joy.0 as f32) / (std::i8::MAX as f32),
(self.joy.1 as f32) / (std::i8::MAX as f32),
);
let cam: (f32, f32) = (
(self.cam.0 as f32) / (std::i8::MAX as f32),
(self.cam.1 as f32) / (std::i8::MAX as f32),
);
let lrt: (f32, f32) = (
(self.lrt.0 as f32) / (std::u8::MAX as f32),
(self.lrt.1 as f32) / (std::u8::MAX as f32),
);
let pan: f32 = (self.pan as f32) / (std::i16::MAX as f32);
let b_btn: char = if self.btn(Btn::B) == Some(true) {
'▣'
} else {
'□'
};
let a_btn: char = if self.btn(Btn::A) == Some(true) {
'▣'
} else {
'□'
};
let y_btn: char = if self.btn(Btn::Y) == Some(true) {
'▣'
} else {
'□'
};
let x_btn: char = if self.btn(Btn::X) == Some(true) {
'▣'
} else {
'□'
};
let dl: char = if self.btn(Btn::Left) == Some(true) {
'▣'
} else {
'□'
};
let dr: char = if self.btn(Btn::Right) == Some(true) {
'▣'
} else {
'□'
};
let du: char = if self.btn(Btn::Up) == Some(true) {
'▣'
} else {
'□'
};
let dd: char = if self.btn(Btn::Down) == Some(true) {
'▣'
} else {
'□'
};
let w_btn: char = if self.btn(Btn::W) == Some(true) {
'▣'
} else {
'□'
};
let z_btn: char = if self.btn(Btn::Z) == Some(true) {
'▣'
} else {
'□'
};
let l_btn: char = if self.btn(Btn::L) == Some(true) {
'▣'
} else {
'□'
};
let r_btn: char = if self.btn(Btn::R) == Some(true) {
'▣'
} else {
'□'
};
let d_btn: char = if self.btn(Btn::D) == Some(true) {
'▣'
} else {
'□'
};
let c_btn: char = if self.btn(Btn::C) == Some(true) {
'▣'
} else {
'□'
};
let f_btn: char = if self.btn(Btn::F) == Some(true) {
'▣'
} else {
'□'
};
let e_btn: char = if self.btn(Btn::E) == Some(true) {
'▣'
} else {
'□'
};
write!(
f,
"j({:.2},{:.2}) p({:.2}) c({:.2},{:.2}) T({:.2},{:.2}) b{} a{} y{} x{} ←{} →{} \
↑{} ↓{} l{} r{} w{} z{} f{} e{} d{} c{}",
joy.0,
joy.1,
pan,
cam.0,
cam.1,
lrt.0,
lrt.1,
b_btn,
a_btn,
y_btn,
x_btn,
dl,
dr,
du,
dd,
l_btn,
r_btn,
w_btn,
z_btn,
f_btn,
e_btn,
d_btn,
c_btn,
)
}
}
impl Device {
/// Get main joystick state from the device if a main joystick exists, otherwise return `None`.
pub fn joy(&self) -> Option<(f32, f32)> {
Some((
(self.joy.0 as f32) / (std::i8::MAX as f32),
(self.joy.1 as f32) / (std::i8::MAX as f32),
))
}
/// Get X & Y from camera stick if it exists, otherwise return `None`.
pub fn cam(&mut self) -> Option<(f32, f32)> {
#[allow(clippy::single_match)]
match self.hardware_id {
// Flight controller
0x_07B5_0316 => return None,
_ => {}
}
if self.cam.0 == -128 || self.cam.1 == -128 || self.pan == -128 {
return None;
}
let rtn = (
(self.cam.0 as f32) / (std::i8::MAX as f32),
(self.cam.1 as f32) / (std::i8::MAX as f32),
);
self.cam = (-128, -128);
self.pan = std::i16::MIN;
Some(rtn)
}
/// Get X & Y facing direction from camera stick. This is like `cam()` for x, but `pitch()` for
/// y.
pub fn dir(&mut self) -> Option<(f32, f32)> {
#[allow(clippy::single_match)]
match self.hardware_id {
// Flight controller
0x_07B5_0316 => return None,
_ => {}
}
if self.cam.0 == -128 || self.cam.1 == -128 || self.pan == std::i16::MIN {
return None;
}
let rtn = (
(self.cam.0 as f32) / (std::i8::MAX as f32),
(self.pan as f32) / (std::i16::MAX as f32),
);
self.cam = (-128, -128);
self.pan = std::i16::MIN;
Some(rtn)
}
/// Get the pitch throttle value. Returns `None` if the pitch throttle doesn't exist.
pub fn pitch(&mut self) -> Option<f32> {
if self.cam.0 == -128 || self.cam.1 == -128 || self.pan == std::i16::MIN {
return None;
}
let rtn = (self.pan as f32) / (std::i16::MAX as f32);
self.pan = std::i16::MIN;
Some(rtn)
}
/// Return `Some(true)` if a button is pressed, `Some(false)` if not, and `None` if the button
/// doesn't exist.
pub fn btn<B: Into<u8>>(&self, b: B) -> Option<bool> {
Some(self.btn & (1 << (b.into())) != 0)
}
/// Swap 2 buttons in the mapping.
/// # Panics
/// Panics if the controller doesn't support either button a or button b.
pub fn mod_swap_btn<B: Into<u8> + Copy + Clone>(&mut self, a: B, b: B) {
let new_b = self.btn(a).unwrap();
let new_a = self.btn(b).unwrap();
if new_a {
self.btn |= 1 << a.into();
} else {
self.btn &= !(1 << a.into());
}
if new_b {
self.btn |= 1 << b.into();
} else {
self.btn &= !(1 << b.into());
}
}
/// Swap X & Y on joy stick
pub fn mod_swap_joy(&mut self) {
std::mem::swap(&mut self.joy.0, &mut self.joy.1)
}
/// Copy l value to pitch.
pub fn mod_l2pitch(&mut self) {
let l = self.lrt.0 as i8;
self.pan = ((l as i32 * std::i16::MAX as i32) / 127) as i16;
}
/// If trigger is all of the way down, activate button.
pub fn mod_t2lr(&mut self) {
if self.lrt.0 == 255 {
self.btn |= 1 << Btn::L as u8;
} else {
self.btn &= !(1 << Btn::L as u8);
}
if self.lrt.1 == 255 {
self.btn |= 1 << Btn::R as u8;
} else {
self.btn &= !(1 << Btn::R as u8);
}
}
/// mod_expand( axis for old controllers like GameCube.
pub fn mod_expand(&mut self) {
self.joy.0 = self
.joy
.0
.saturating_add((3 * self.joy.0 as i16 / 4) as i8)
.max(-127);
self.joy.1 = self
.joy
.1
.saturating_add((3 * self.joy.1 as i16 / 4) as i8)
.max(-127);
self.cam.0 = self
.cam
.0
.saturating_add((3 * self.cam.0 as i16 / 4) as i8)
.max(-127);
self.cam.1 = self
.cam
.1
.saturating_add((3 * self.cam.1 as i16 / 4) as i8)
.max(-127);
let lrt0 = (self.lrt.0 as i8).overflowing_add(-127).0;
self.lrt.0 = ((lrt0.saturating_add((3 * lrt0 as i16 / 4) as i8).max(-127)) as u8)
.overflowing_add(127)
.0;
let lrt1 = (self.lrt.1 as i8).overflowing_add(-127).0;
self.lrt.1 = ((lrt1.saturating_add((3 * lrt1 as i16 / 4) as i8).max(-127)) as u8)
.overflowing_add(127)
.0;
}
}
/*/// A Controller's layout.
pub struct Layout {
// A joystick.
joystick: bool,
// Can joystick be pushed as an extra button?
joystick_button: bool,
// An extra joystick.
alt_joystick: bool,
// Can extra joystick be pushed as an extra button?
alt_joystick_button: bool,
// A direction pad.
dir_pad: bool,
// A back button.
back: bool,
// A start button.
start: bool,
// A select button.
select: bool,
// A menu button.
menu: bool,
// An accept button (a).
accept: bool,
// A cancel button (b).
cancel: bool,
// A jump button (x or y).
jump: bool,
// An action button (x or y).
action: bool,
// A number of numbered buttons.
numbered: u16,
// Left throttle (resets position when released).
l_throttle: bool,
// Right throttle (resets position when released).
r_throttle: bool,
// Left Button
l: bool,
// Right Button.
r: bool,
// Left Button & Throttle (L on a GameCube controller)
ll_throttle: bool,
// Right Button & Throttle (R on a GameCube controller)
rr_throttle: bool,
// Trigger button.
trigger: bool,
// A throttle that stays stationary while user isn't touching it.
stationary_throttle: bool,
}*/
/*impl Layout {
pub fn new() -> Layout {
Layout {
}
}
}*/
/// An interface to all joystick, gamepad and controller devices.
pub struct Port {
manager: NativeManager,
controllers: Vec<Device>,
}
impl Port {
/// Create a new interface to all joystick, gamepad and controller devices currently plugged in
/// to this computer.
pub fn new() -> Port {
let manager = NativeManager::new();
let controllers = vec![];
Port {
manager,
controllers,
}
}
/// Get the number of devices currently plugged in, and update number if needed.
pub fn update(&mut self) -> u16 {
for mut controller in &mut self.controllers {
let (fd, is_out, ne) = self.manager.get_fd(controller.native_handle as usize);
if ne {
continue;
}
if is_out {
self.manager.disconnect(fd);
continue;
}
while joystick_poll_event(fd, &mut controller) {}
controller.pan = controller.pan.saturating_add(controller.cam.1 as i16 * 8);
}
let (device_count, added) = self.manager.search();
if added != ::std::usize::MAX {
// FOR TESTING
// println!("s{:08X}", self.manager.get_id(added).0);
let (min, max, _) = self.manager.get_abs(added);
self.controllers.resize_with(device_count, Default::default);
self.controllers[added] = Device {
native_handle: added as u32,
hardware_id: self.manager.get_id(added).0,
abs_min: min,
abs_max: max,
joy: (0, 0),
cam: (0, 0),
lrt: (0, 0),
pan: 0,
btn: 0,
};
}
self.controllers.len() as u16
}
/// Get the state of a device
pub fn get(&self, stick: u16) -> Device {
let mut rtn = self.controllers[stick as usize];
// Apply mods
match rtn.hardware_id {
// XBOX MODS
0x_0E6F_0501 => {
rtn.mod_swap_btn(Btn::A, Btn::B);
rtn.mod_t2lr();
}
// PS3 MODS
0x_054C_0268 => {
rtn.mod_swap_btn(Btn::X, Btn::Y);
rtn.mod_t2lr();
}
// THRUSTMASTER MODS
0x_07B5_0316 => rtn.mod_l2pitch(),
// GAMECUBE MODS
0x_0079_1844 => rtn.mod_expand(),
_ => {}
}
rtn
}
/// Swap two devices in the interface by their indexes.
/// # Panics
/// If either `a` or `b` are out of bounds.
/// # Note
/// This is useful for if in a game, you want P1 and P2 to swap which controller they are
/// assigned to. You can do this with:
/// ```norun
/// // Assuming P1 is at index 0, and P2 is at index 1,
/// devices.swap(0, 1);
/// ```
pub fn swap(&mut self, a: u16, b: u16) {
self.controllers.swap(a as usize, b as usize);
}
/// Get the name of a device by index.
#[allow(unused)]
pub fn name(&self, a: u16) -> String {
// TODO
"Unknown".to_string()
}
}
fn joystick_poll_event(fd: i32, device: &mut Device) -> bool {
extern "C" {
fn read(fd: i32, buf: *mut Event, count: usize) -> isize;
}
let mut js = unsafe { std::mem::uninitialized() };
let bytes = unsafe { read(fd, &mut js, std::mem::size_of::<Event>()) };
if bytes != (std::mem::size_of::<Event>() as isize) {
return false;
}
fn edit<B: Into<u8>>(is: bool, device: &mut Device, b: B) {
if is {
device.btn |= 1 << b.into()
} else {
device.btn &= !(1 << b.into())
}
}
match js.ev_type {
// button press / release (key)
0x01 => {
// println!("EV CODE {}", js.ev_code - 0x120);
let is = js.ev_value == 1;
match js.ev_code - 0x120 {
// ABXY
0 | 19 => edit(is, device, Btn::X),
1 | 17 => edit(is, device, Btn::A),
2 | 16 => edit(is, device, Btn::B),
3 | 20 => edit(is, device, Btn::Y),
// LT/RT
4 | 24 => edit(is, device, Btn::L),
5 | 25 => edit(is, device, Btn::R),
// LB/RB
6 | 22 => edit(is, device, Btn::W), // 6 is a guess.
7 | 23 => edit(is, device, Btn::Z),
// Select/Start
8 | 26 => edit(is, device, Btn::F), // 8 is a guess.
9 | 27 => edit(is, device, Btn::E),
// ?
10 => println!("Button 10 is Unknown"),
// D-PAD
12 | 256 => edit(is, device, Btn::Up),
13 | 259 => edit(is, device, Btn::Right),
14 | 257 => edit(is, device, Btn::Down),
15 | 258 => edit(is, device, Btn::Left),
// 16-17 already matched
18 => println!("Button 18 is Unknown"),
// 19-20 already matched
21 => println!("Button 21 is Unknown"),
// 22-27 already matched
28 => println!("Button 28 is Unknown"),
29 => edit(is, device, Btn::D),
30 => edit(is, device, Btn::C),
a => println!("Button {} is Unknown", a),
}
}
// axis move (abs)
0x03 => {
let value = transform(device.abs_min, device.abs_max, js.ev_value);
// if value != 0 {
// println!("{} {}", js.ev_code, value);
// }
// For some reason this is different on the GameCube controller, so fix it.
let (cam_x, cam_y, lrt_l, lrt_r) = match device.hardware_id {
0x_0079_1844 => (5, 2, 3, 4),
_ => (3, 4, 2, 5),
};
match js.ev_code {
0 => device.joy.0 = value,
1 => device.joy.1 = value,
16 => {
if js.ev_value < 0 {
edit(true, device, Btn::Left);
edit(false, device, Btn::Right);
} else if js.ev_value > 0 {
edit(false, device, Btn::Left);
edit(true, device, Btn::Right);
} else {
edit(false, device, Btn::Left);
edit(false, device, Btn::Right);
}
}
17 => {
if js.ev_value < 0 {
edit(true, device, Btn::Up);
edit(false, device, Btn::Down);
} else if js.ev_value > 0 {
edit(false, device, Btn::Up);
edit(true, device, Btn::Down);
} else {
edit(false, device, Btn::Up);
edit(false, device, Btn::Down);
}
}
40 => {} // IGNORE: Duplicate axis.
a => {
if a == cam_x {
device.cam.0 = value;
} else if a == cam_y {
device.cam.1 = value;
} else if a == lrt_l {
js.ev_value = js.ev_value.max(-127);
device.lrt.0 = js.ev_value as u8;
// edit(js.ev_value > 250, device, Btn::Crouch);
} else if a == lrt_r {
js.ev_value = js.ev_value.max(-127);
device.lrt.1 = js.ev_value as u8;
// edit(js.ev_value > 250, device, Btn::Aiming);
}
} // println!("Unknown Axis: {}", a),
}
}
// ignore
_ => {}
}
true
}
fn deadzone(min: i32, max: i32, val: i32) -> (i32, i32) {
let range = max - min;
let halfr = range >> 1;
let deadz = halfr >> 2; // 1/8th = deadzone.
let midpt = min + halfr;
// Center the range.
let value = val - midpt; // -halfr to halfr
// Take deadzone into account.
let value = if value < deadz {
if value > -deadz {
0
} else {
value + deadz
}
} else {
value - deadz
};
(value, (range >> 1) - deadz)
}
fn transform(min: i32, max: i32, val: i32) -> i8 {
let (value, full) = deadzone(min, max, val);
// Modify integer range from (-(full) thru (full)) to -127 to 127
((value * 127) / full).max(-127).min(127) as i8
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transform_test() {
let a = deadzone(-100, 100, 100);
assert_eq!(a.0, a.1);
assert_eq!(75, a.1);
let b = deadzone(-100, 100, -100);
assert_eq!(b.0, -b.1);
assert_eq!(75, b.1);
let c = deadzone(-100, 100, 0);
assert_eq!(c.0, 0);
assert_eq!(75, b.1);
assert_eq!(transform(-100, 100, 100), 127);
assert_eq!(transform(-100, 100, -100), -127);
assert_eq!(transform(-100, 100, 0), 0);
assert_eq!(transform(-128, 127, 127), 127);
assert_eq!(transform(-128, 127, 0), 0);
assert_eq!(transform(-128, 127, -128), -127);
}
}