Files
g13/driver/src/lib.rs
2026-02-14 03:03:26 +01:00

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(())
}
}