308 lines
8.0 KiB
Rust
308 lines
8.0 KiB
Rust
pub mod joystick;
|
|
|
|
use std::{
|
|
collections::{HashMap, hash_map::Entry},
|
|
io::{Read, Write},
|
|
sync::{Arc, RwLock},
|
|
time::Duration,
|
|
};
|
|
|
|
use crossbeam_channel::{Receiver, bounded};
|
|
use embedded_graphics_core::{
|
|
pixelcolor::BinaryColor,
|
|
prelude::{Dimensions, DrawTarget, Point, Size},
|
|
primitives::Rectangle,
|
|
};
|
|
use nusb::{
|
|
Interface, MaybeFuture,
|
|
transfer::{ControlOut, ControlType, In, Interrupt, Out, Recipient},
|
|
};
|
|
|
|
use crate::joystick::Button;
|
|
|
|
pub const G13_LCD_COLUMNS: i32 = 160;
|
|
pub const G13_LCD_ROWS: i32 = 43;
|
|
pub const G13_LCD_BYTES_PER_ROW: i32 = G13_LCD_COLUMNS / 8;
|
|
pub const G13_LCD_BUF_SIZE: i32 = (G13_LCD_ROWS + 5) * G13_LCD_BYTES_PER_ROW;
|
|
|
|
const DEADZONE: f32 = 10.0;
|
|
const CENTER: f32 = 127.5;
|
|
const RANGE: f32 = 117.5;
|
|
|
|
const BUTTON_MAP: &[(Button, usize, u8)] = &[
|
|
// buf[6]
|
|
(Button::Action, 6, 0b00000001),
|
|
(Button::Screen1, 6, 0b00000010),
|
|
(Button::Screen2, 6, 0b00000100),
|
|
(Button::Screen3, 6, 0b00001000),
|
|
(Button::Screen4, 6, 0b00010000),
|
|
(Button::M1, 6, 0b00100000),
|
|
(Button::M2, 6, 0b01000000),
|
|
(Button::M3, 6, 0b10000000),
|
|
// buf[7]
|
|
(Button::Light, 7, 0b01000000),
|
|
(Button::MR, 7, 0b00000001),
|
|
(Button::Stick1, 7, 0b00000010),
|
|
(Button::Stick2, 7, 0b00000100),
|
|
(Button::Stick3, 7, 0b00001000),
|
|
// buf[3]
|
|
(Button::G1, 3, 0b00000001),
|
|
(Button::G2, 3, 0b00000010),
|
|
(Button::G3, 3, 0b00000100),
|
|
(Button::G4, 3, 0b00001000),
|
|
(Button::G5, 3, 0b00010000),
|
|
(Button::G6, 3, 0b00100000),
|
|
(Button::G7, 3, 0b01000000),
|
|
(Button::G8, 3, 0b10000000),
|
|
// buf[4]
|
|
(Button::G9, 4, 0b00000001),
|
|
(Button::G10, 4, 0b00000010),
|
|
(Button::G11, 4, 0b00000100),
|
|
(Button::G12, 4, 0b00001000),
|
|
(Button::G13, 4, 0b00010000),
|
|
(Button::G14, 4, 0b00100000),
|
|
(Button::G15, 4, 0b01000000),
|
|
(Button::G16, 4, 0b10000000),
|
|
// buf[5]
|
|
(Button::G17, 5, 0b00000001),
|
|
(Button::G18, 5, 0b00000010),
|
|
(Button::G19, 5, 0b00000100),
|
|
(Button::G20, 5, 0b00001000),
|
|
(Button::G21, 5, 0b00010000),
|
|
(Button::G22, 5, 0b00100000),
|
|
];
|
|
|
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct Vec2<T>
|
|
where
|
|
T: Copy,
|
|
{
|
|
pub x: T,
|
|
pub y: T,
|
|
}
|
|
|
|
impl<T> Vec2<T>
|
|
where
|
|
T: Copy,
|
|
{
|
|
pub fn new(x: T, y: T) -> Self {
|
|
Self { x, y }
|
|
}
|
|
}
|
|
|
|
pub struct G13 {
|
|
rx: Option<Receiver<G13Event>>,
|
|
interface: Interface,
|
|
img_buffer: Arc<RwLock<[u8; G13_LCD_BUF_SIZE as usize + 8]>>,
|
|
}
|
|
|
|
impl G13 {
|
|
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
|
|
let img_buffer = [0u8; G13_LCD_BUF_SIZE as usize + 8];
|
|
|
|
let Ok(mut devices) = nusb::list_devices().wait() else {
|
|
return Err("No devices found".into());
|
|
};
|
|
|
|
let Some(device) = devices.find(|d| d.vendor_id() == 0x046d && d.product_id() == 0xc21c)
|
|
else {
|
|
return Err("No device found".into());
|
|
};
|
|
|
|
let Ok(device) = device.open().wait() else {
|
|
return Err("Unable to open device".into());
|
|
};
|
|
|
|
let _ = device.detach_kernel_driver(0);
|
|
|
|
let interface = match device.claim_interface(0).wait() {
|
|
Ok(i) => i,
|
|
Err(e) => return Err(format!("Unable to claim interface: {:#?}", e).into()),
|
|
};
|
|
|
|
let input = G13Input::new(interface.clone());
|
|
let (tx, rx) = bounded(0);
|
|
|
|
let _tx = tx.clone();
|
|
std::thread::spawn(move || {
|
|
for event in input {
|
|
let Some(event) = event else {
|
|
continue;
|
|
};
|
|
if _tx.send(event).is_err() {
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
Ok(Self {
|
|
interface,
|
|
img_buffer: Arc::new(RwLock::new(img_buffer)),
|
|
rx: Some(rx),
|
|
})
|
|
}
|
|
|
|
pub fn events(&mut self) -> Receiver<G13Event> {
|
|
self.rx.take().expect("events() can only be called once")
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub enum G13Event {
|
|
Axis(f32, f32),
|
|
Button(Button, (bool, bool)), // (old, new)
|
|
}
|
|
|
|
pub struct G13Input {
|
|
interface: Interface,
|
|
buttons: HashMap<Button, bool>,
|
|
axis: Vec2<f32>,
|
|
}
|
|
|
|
impl G13Input {
|
|
pub fn new(interface: Interface) -> Self {
|
|
Self {
|
|
interface,
|
|
buttons: HashMap::new(),
|
|
axis: Default::default(),
|
|
}
|
|
}
|
|
|
|
fn handle_button(&mut self, button: Button, value: bool) -> Option<G13Event> {
|
|
if let Entry::Vacant(e) = self.buttons.entry(button) {
|
|
e.insert(value);
|
|
if value {
|
|
return Some(G13Event::Button(button, (!value, value)));
|
|
}
|
|
}
|
|
|
|
let prev = self.buttons[&button];
|
|
if self.buttons[&button] == value {
|
|
return None;
|
|
}
|
|
|
|
self.buttons.insert(button, value);
|
|
Some(G13Event::Button(button, (prev, value)))
|
|
}
|
|
}
|
|
|
|
impl Iterator for G13Input {
|
|
type Item = Option<G13Event>;
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
let mut reader = self
|
|
.interface
|
|
.endpoint::<Interrupt, In>(0x81)
|
|
.ok()?
|
|
.reader(8);
|
|
let mut buf = [0; 8];
|
|
reader.read_exact(&mut buf).ok()?;
|
|
|
|
for &(button, index, mask) in BUTTON_MAP {
|
|
if let Some(event) = self.handle_button(button, buf[index] & mask != 0) {
|
|
// self.calibrating = false;
|
|
return Some(Some(event));
|
|
}
|
|
}
|
|
|
|
let axis = Vec2::new(buf[1], buf[2]);
|
|
|
|
let mut input = Vec2::new(axis.x as f32 - CENTER, axis.y as f32 - CENTER);
|
|
|
|
input.x = apply_deadzone(input.x);
|
|
input.y = apply_deadzone(input.y);
|
|
|
|
if self.axis == input {
|
|
return Some(None);
|
|
}
|
|
|
|
self.axis = input;
|
|
|
|
Some(Some(G13Event::Axis(input.x / RANGE, input.y / RANGE)))
|
|
}
|
|
}
|
|
|
|
fn apply_deadzone(v: f32) -> f32 {
|
|
if v.abs() <= DEADZONE {
|
|
0.0
|
|
} else if v > 0.0 {
|
|
v - DEADZONE
|
|
} else {
|
|
v + DEADZONE
|
|
}
|
|
}
|
|
|
|
impl G13 {
|
|
pub fn set_lcd_color(&self, r: u8, g: u8, b: u8) -> Result<(), Box<dyn std::error::Error>> {
|
|
self.interface
|
|
.control_out(
|
|
ControlOut {
|
|
control_type: ControlType::Class,
|
|
recipient: Recipient::Interface,
|
|
request: 9,
|
|
value: 0x307,
|
|
index: 0,
|
|
data: &[5, r, g, b, 0],
|
|
},
|
|
Duration::from_secs(2),
|
|
)
|
|
.wait()?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn render(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
|
let img_buffer = self.img_buffer.read().expect("Poisoned");
|
|
|
|
let mut buffer = [0u8; G13_LCD_BUF_SIZE as usize + 32 + 8];
|
|
buffer[0] = 0x03;
|
|
buffer[32..G13_LCD_BUF_SIZE as usize + 32 + 8].copy_from_slice(&(*img_buffer));
|
|
|
|
let mut w = self.interface.endpoint::<Interrupt, Out>(0x02)?.writer(64);
|
|
w.write_all(&buffer)?;
|
|
w.flush()?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn size(&self) -> Size {
|
|
Size::new(G13_LCD_COLUMNS as u32, G13_LCD_ROWS as u32)
|
|
}
|
|
}
|
|
|
|
impl Dimensions for G13 {
|
|
fn bounding_box(&self) -> Rectangle {
|
|
Rectangle::new(
|
|
Point::new(0, 0),
|
|
Size::new(G13_LCD_COLUMNS as u32, G13_LCD_ROWS as u32),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl DrawTarget for G13 {
|
|
type Color = BinaryColor;
|
|
type Error = Box<dyn std::error::Error>;
|
|
|
|
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
|
|
where
|
|
I: IntoIterator<Item = embedded_graphics_core::Pixel<Self::Color>>,
|
|
{
|
|
let mut img_buffer = self.img_buffer.write().expect("Poisoned");
|
|
for p in pixels {
|
|
if p.0.x < 0 || p.0.x > G13_LCD_COLUMNS - 1 || p.0.y < 0 || p.0.y > G13_LCD_ROWS - 1 {
|
|
continue;
|
|
}
|
|
|
|
let offset = p.0.x + (p.0.y / 8) * (G13_LCD_BYTES_PER_ROW) * 8;
|
|
let mask = 1 << (p.0.y.rem_euclid(8));
|
|
|
|
if p.1.is_on() {
|
|
img_buffer[offset as usize] |= mask;
|
|
} else {
|
|
img_buffer[offset as usize] &= !mask;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|