Compare commits

...

9 Commits

10 changed files with 385 additions and 67 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "chuniio-rs" name = "chuniio-rs"
version = "0.1.0" version = "0.1.3"
edition = "2021" edition = "2021"
[lib] [lib]
@ -17,6 +17,8 @@ panic = "abort"
chusan = [] chusan = []
tasoller_v1 = [] tasoller_v1 = []
tasoller_v2 = [] tasoller_v2 = []
laverita_v3 = []
redboard = []
[dependencies] [dependencies]
anyhow = "1.0.76" anyhow = "1.0.76"

View File

@ -23,6 +23,7 @@ coin=0x33
Currently supported backends are: Currently supported backends are:
- `tasoller_v1` - `tasoller_v1`
- `tasoller_v2` - `tasoller_v2`
- `laverita_v2`
```shell ```shell
CONTROLLER="tasoller_v1" # replace with your preferred controller backend CONTROLLER="tasoller_v1" # replace with your preferred controller backend

View File

@ -3,7 +3,7 @@
mkdir -p dist/chusan mkdir -p dist/chusan
mkdir -p dist/chuni mkdir -p dist/chuni
for backend in tasoller_v1 tasoller_v2 for backend in tasoller_v1 tasoller_v2 laverita_v3
do do
cargo build --target i686-pc-windows-msvc --release --features chusan,$backend cargo build --target i686-pc-windows-msvc --release --features chusan,$backend
cargo build --target x86_64-pc-windows-msvc --release --features chusan,$backend cargo build --target x86_64-pc-windows-msvc --release --features chusan,$backend

162
src/backends/laverita_v2.rs Normal file
View File

@ -0,0 +1,162 @@
//! # Yuancon Laverita v2
//! (does anyone still use this?)
//!
//! Notes: I'm getting conflicting information on the protocol.
//! CrazyRedMachine is claiming that there is [a leading byte][1], while 4yn's
//! [slidershim][2] as well as their earlier [Yuancon HID test script][3] is
//! claiming the opposite. This implementation is based on CrazyRedMachine,
//! since the chuniio open-sourced by ZhouSensor himself seems to agree.
//!
//!
//! ```c
//! struct outputdata_gaosan
//! {
//! unsigned char Address;
//! unsigned char Touch[62];
//! }OutputDataGaosan;
//! ```
//!
//! [1]: https://github.com/CrazyRedMachine/yuancon_laverita_v2-docs/blob/main/pcb_protocol/hid_reports.txt
//! [2]: https://github.com/4yn/slidershim/blob/main/src-slider_io/src/device/hid.rs#L174-L207
//! [3]: https://gist.github.com/4yn/53a6b6b681e40fd5658bf5b7f9fe9c15
//!
//! USB device: 1973:2001
//!
//! USB interface: 0
//!
//! ## Protocol information
//!
//! ### IN Interrupt (0x81)
//! - Content length: 35 bytes
//! - Byte 0: ReportID 0x00
//! - Byte 1: Air sensors, from lowest to highest bit {2, 1, 4, 3, 6, 5}
//! - Byte 2:
//! - Bit 0: Test
//! - Bit 1: Service
//! - Bit 2: Coin
//! - Bytes 3..35: Slider pressure (bottom -> top, then left -> right)
//!
//! ### OUT Interrupt (0x02)
//! - Content length: 63 bytes
//! - Byte 0: ReportID 0x00
//! - Byte 1..63: Slider LED data (right -> left)
//! - Each LED is controlled by 2 bytes: GGGG GRRR (second byte) | RRRB BBBB (first byte)
#[cfg(any(chuni, chusanapp))]
use anyhow::Result;
#[cfg(any(chuni, chusanapp))]
use rusb::{DeviceHandle, UsbContext};
use super::ReadType;
pub const DEVICE_VID: u16 = 0x1973;
pub const DEVICE_PID: u16 = 0x2001;
pub const READ_TYPE: ReadType = ReadType::Interrupt;
pub const READ_ENDPOINT: u8 = 0x81;
pub const INPUT_MEMORY_SIZE: usize = 35;
pub const OUTPUT_MEMORY_SIZE: usize = 63;
/// Poll JVS input.
///
/// The return value is a tuple:
/// - The first value returns the cabinet test/service state, where bit 0 is Test
/// and bit 1 is Service.
/// - The second value returns the IR beams that are currently broken, where bit 0
/// is the lowest IR beam and bit 5 is the highest IR beam, for a total of 6 beams.
///
/// Both bit masks are active-high.
#[cfg(any(chuni, amdaemon))]
#[inline(always)]
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
let mixed_beams = input[1] & 0x3F;
let beams = ((mixed_beams & 0xAA) >> 1) | ((mixed_beams & 0x55) << 1);
(input[2] & 3, beams)
}
/// Checks the current state of the coin button (if there is one).
#[cfg(any(chuni, amdaemon))]
#[inline(always)]
pub fn is_coin_button_pressed(input: &[u8]) -> bool {
(input[2] & 4) != 0
}
/// Reads slider pressure information from USB input.
///
/// There are a total of 32 regions on the touch slider. Each region can return
/// an 8-bit pressure value. The operator menu allows the operator to adjust the
/// pressure level at which a region is considered to be pressed; the factory
/// default value for this setting is 20.
///
/// You should return an array of 32 unsigned 8-bit integers, starting from top right
/// and going from top to bottom, right to left.
///
/// ```ignore
/// ^^ Towards screen ^^
/// ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
/// 30 | 28 | 26 | 24 | 22 | 20 | 18 | 16 | 14 | 12 | 10 | 8 | 6 | 4 | 2 | 0 |
/// ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
/// 31 | 29 | 27 | 25 | 23 | 21 | 19 | 17 | 15 | 13 | 11 | 9 | 7 | 5 | 3 | 1 |
/// ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
/// ```
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
let mut pressure = [0u8; 32];
for i in 0..32 {
pressure[i] = input[3 + 31 - i];
}
pressure
}
/// Do some one-time initialization to the USB out buffer
/// (e.g. set magic bytes).
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn init_output_buffer(output: &mut [u8]) {
output[0] = 0;
}
/// Update the RGB lighting on the slider. A slice `rgb` is provided, alternating
/// between 16 touch pad pixels and 15 divider pixels, going from right to left.
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn set_slider_leds<T: UsbContext>(
device: &DeviceHandle<T>,
output: &mut [u8],
rgb: &[u8],
) -> Result<()> {
use crate::TIMEOUT;
for (buf_chunk, state_chunk) in output[1..].chunks_mut(2).take(31).zip(rgb.chunks(3)) {
buf_chunk[0] = (state_chunk[0] << 3 & 0xE0) | (state_chunk[2] >> 3);
buf_chunk[1] = (state_chunk[1] & 0xF8) | (state_chunk[0] >> 5);
}
device.write_interrupt(0x02, output, TIMEOUT)?;
Ok(())
}
/// Update the RGB LEDs.
///
/// Board 0 corresponds to the left LEDs, with 5 * 10 * 3 RGB values for the
/// billboard, followed by 3 RGB values for the air tower.
///
/// Board 1 corresponds to the right LEDs, with 6 * 10 * 3 RGB values for the
/// billboard, followed by 3 RGB values for the air tower.
///
/// Note that billboard strips have alternating direction (bottom to top, top
/// to bottom...)
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn set_led_colors<T: UsbContext>(
_device: &DeviceHandle<T>,
_output: &mut [u8],
_board: u8,
_rgb: &[u8],
) -> Result<()> {
Ok(())
}

View File

@ -1,4 +1,6 @@
pub mod dummy; pub mod dummy;
pub mod laverita_v2;
pub mod redboard;
pub mod tasoller_v1; pub mod tasoller_v1;
pub mod tasoller_v2; pub mod tasoller_v2;

129
src/backends/redboard.rs Normal file
View File

@ -0,0 +1,129 @@
//! # RedBoard
//! USB device: 0F0D:0092
//! USB interface: 0
//!
//! ## Protocol information
//! - Content length: 9 bytes (72 bits)
//! - Byte 1: Report ID
//! - Byte 2:
//! - 0x01: Service
//! - 0x02: Start (?)
//! - 0x10: Test
//! - Byte 3:
//! - 0x01: IR2
//! - 0x02: IR3
//! - 0x04: IR4
//! - 0x08: IR1
//! - 0x10: IR5
//! - 0x20: IR6
//! - Byte 4: HAT switch, whatever this is
//! - Byte
//!
#[cfg(any(chuni, chusanapp))]
use anyhow::Result;
#[cfg(any(chuni, chusanapp))]
use rusb::{DeviceHandle, UsbContext};
use super::ReadType;
pub const DEVICE_VID: u16 = 0x0F0D;
pub const DEVICE_PID: u16 = 0x0092;
pub const READ_TYPE: ReadType = ReadType::Interrupt;
pub const READ_ENDPOINT: u8 = 0x01;
pub const INPUT_MEMORY_SIZE: usize = 9;
pub const OUTPUT_MEMORY_SIZE: usize = 98;
#[repr(C, packed)]
struct ControllerReport {
pub report_id: u8,
pub buttons: u16,
pub _hat: u8,
pub axis: u32,
pub vendor_spec: u8,
}
/// Poll JVS input.
///
/// The return value is a tuple:
/// - The first value returns the cabinet test/service state, where bit 0 is Test
/// and bit 1 is Service.
/// - The second value returns the IR beams that are currently broken, where bit 0
/// is the lowest IR beam and bit 5 is the highest IR beam, for a total of 6 beams.
///
/// Both bit masks are active-high.
#[cfg(any(chuni, amdaemon))]
#[inline(always)]
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
let buttons = (input[1] as u16) << 8 | (input[2] as u16);
(0, 0)
}
/// Checks the current state of the coin button (if there is one).
#[cfg(any(chuni, amdaemon))]
#[inline(always)]
pub fn is_coin_button_pressed(_input: &[u8]) -> bool {
false
}
/// Reads slider pressure information from USB input.
///
/// There are a total of 32 regions on the touch slider. Each region can return
/// an 8-bit pressure value. The operator menu allows the operator to adjust the
/// pressure level at which a region is considered to be pressed; the factory
/// default value for this setting is 20.
///
/// You should return an array of 32 unsigned 8-bit integers, starting from top right
/// and going from top to bottom, right to left.
///
/// ```ignore
/// ^^ Towards screen ^^
/// ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
/// 30 | 28 | 26 | 24 | 22 | 20 | 18 | 16 | 14 | 12 | 10 | 8 | 6 | 4 | 2 | 0 |
/// ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
/// 31 | 29 | 27 | 25 | 23 | 21 | 19 | 17 | 15 | 13 | 11 | 9 | 7 | 5 | 3 | 1 |
/// ----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
/// ```
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn read_pressure_data(_input: &[u8]) -> [u8; 32] {
[0u8; 32]
}
/// Do some one-time initialization to the USB out buffer
/// (e.g. set magic bytes).
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn init_output_buffer(_output: &mut [u8]) {}
/// Update the RGB lighting on the slider. A slice `rgb` is provided, alternating
/// between 16 touch pad pixels and 15 divider pixels, going from right to left.
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn set_slider_leds<T: UsbContext>(
_device: &DeviceHandle<T>,
_output: &mut [u8],
_rgb: &[u8],
) -> Result<()> {
Ok(())
}
/// Update the RGB LEDs.
///
/// Board 0 corresponds to the left LEDs, with 5 * 10 * 3 RGB values for the
/// billboard, followed by 3 RGB values for the air tower.
///
/// Board 1 corresponds to the right LEDs, with 6 * 10 * 3 RGB values for the
/// billboard, followed by 3 RGB values for the air tower.
///
/// Note that billboard strips have alternating direction (bottom to top, top
/// to bottom...)
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn set_led_colors<T: UsbContext>(
_device: &DeviceHandle<T>,
_output: &mut [u8],
_board: u8,
_rgb: &[u8],
) -> Result<()> {
Ok(())
}

View File

@ -19,12 +19,12 @@
//! - Bits 0..26: ??? //! - Bits 0..26: ???
//! - Bit 26: FN2 (1 = pressed) //! - Bit 26: FN2 (1 = pressed)
//! - Bit 27: FN1 (1 = pressed) //! - Bit 27: FN1 (1 = pressed)
//! - Bit 28: IR2 //! - Bit 28: IR1
//! - Bit 29: IR1 //! - Bit 29: IR2
//! - Bit 30: IR4 //! - Bit 30: IR3
//! - Bit 31: IR3 //! - Bit 31: IR4
//! - Bit 32: IR6 //! - Bit 32: IR5
//! - Bit 33: IR5 //! - Bit 33: IR6
//! - Bits 34..66: touch sensor 1-32 (1 = held, top -> bottom then left -> right) //! - Bits 34..66: touch sensor 1-32 (1 = held, top -> bottom then left -> right)
//! - Bits 66..88: ??? //! - Bits 66..88: ???
//! //!
@ -58,12 +58,8 @@ pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
let opbtn = input[3] >> 3 & 1 | input[3] >> 1 & 2; let opbtn = input[3] >> 3 & 1 | input[3] >> 1 & 2;
// IR1-6 in order // IR1-6 in order
let beams = input[3] >> 5 & 1 let beams = input[3] >> 4
| input[3] >> 3 & 2 | (input[4] & 3) << 4;
| input[3] >> 5 & 4
| input[3] >> 3 & 8
| input[4] << 3 & 16
| input[4] << 5 & 32;
(opbtn, beams) (opbtn, beams)
} }
@ -135,13 +131,14 @@ pub fn set_led_colors<T: UsbContext>(
let air_rgb = if board == 0 { 0x96 } else { 0xB4 }; let air_rgb = if board == 0 { 0x96 } else { 0xB4 };
let output_start = if board == 0 { 96 } else { 168 }; let output_start = if board == 0 { 96 } else { 168 };
for buf_chunk in output[output_start..output_start + 72] for (buf_chunk, state_chunk) in output[output_start..output_start + 72]
.chunks_mut(3) .chunks_mut(3)
.take(24) .take(24)
.zip(rgb[air_rgb..air_rgb + 9].chunks(3).cycle().take(24))
{ {
buf_chunk[0] = rgb[air_rgb + 1]; buf_chunk[0] = state_chunk[1];
buf_chunk[1] = rgb[air_rgb]; buf_chunk[1] = state_chunk[0];
buf_chunk[2] = rgb[air_rgb + 2]; buf_chunk[2] = state_chunk[2];
} }
device.write_bulk(0x03, output, TIMEOUT)?; device.write_bulk(0x03, output, TIMEOUT)?;

View File

@ -42,6 +42,7 @@ pub const OUTPUT_MEMORY_SIZE: usize = 240;
#[inline(always)] #[inline(always)]
pub fn jvs_poll(input: &[u8]) -> (u8, u8) { pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
let opbtn = input[3] >> 6; let opbtn = input[3] >> 6;
let opbtn = (opbtn & 1) << 1 | (opbtn & 2) >> 1;
let beams = input[3] & 63; // mask for last 6 bits let beams = input[3] & 63; // mask for last 6 bits
(opbtn, beams) (opbtn, beams)
@ -106,14 +107,15 @@ pub fn set_led_colors<T: UsbContext>(
let air_rgb = if board == 0 { 0x96 } else { 0xB4 }; let air_rgb = if board == 0 { 0x96 } else { 0xB4 };
let output_start = if board == 0 { 96 } else { 168 }; let output_start = if board == 0 { 96 } else { 168 };
for buf_chunk in output[output_start..output_start + 72] for (buf_chunk, state_chunk) in output[output_start..output_start + 72]
.chunks_mut(3) .chunks_mut(3)
.take(24) .take(24)
.zip(rgb[air_rgb..air_rgb + 9].chunks(3).cycle().take(24))
{ {
buf_chunk[0] = rgb[air_rgb + 1]; buf_chunk[0] = state_chunk[1];
buf_chunk[1] = rgb[air_rgb]; buf_chunk[1] = state_chunk[0];
buf_chunk[2] = rgb[air_rgb + 2]; buf_chunk[2] = state_chunk[2];
} }
device.write_bulk(0x03, output, TIMEOUT)?; device.write_bulk(0x03, output, TIMEOUT)?;

View File

@ -34,6 +34,10 @@ cfg_if::cfg_if! {
use crate::backends::tasoller_v1 as con_impl; use crate::backends::tasoller_v1 as con_impl;
} else if #[cfg(feature = "tasoller_v2")] { } else if #[cfg(feature = "tasoller_v2")] {
use crate::backends::tasoller_v2 as con_impl; use crate::backends::tasoller_v2 as con_impl;
} else if #[cfg(feature = "laverita_v2")] {
use crate::backends::laverita_v2 as con_impl;
} else if #[cfg(feature = "redboard")] {
use crate::backends::redboard as con_impl;
} else { } else {
use crate::backends::dummy as con_impl; use crate::backends::dummy as con_impl;
} }
@ -43,17 +47,15 @@ cfg_if::cfg_if! {
if #[cfg(any(chuni, chusanapp))] { if #[cfg(any(chuni, chusanapp))] {
type SliderCallbackFn = unsafe extern "C" fn(data: *const u8); type SliderCallbackFn = unsafe extern "C" fn(data: *const u8);
use std::thread::{self, JoinHandle}; use std::thread;
use ::log::info; use ::log::info;
use once_cell::sync::OnceCell;
use rusb::{DeviceHandle, GlobalContext}; use rusb::{DeviceHandle, GlobalContext};
use parking_lot::RwLock; use parking_lot::RwLock;
use crate::backends::ReadType; use crate::backends::ReadType;
static DEVICE: OnceCell<DeviceHandle<GlobalContext>> = OnceCell::new(); static DEVICE: RwLock<Option<DeviceHandle<GlobalContext>>> = RwLock::new(None);
static mut SLIDER_THREAD: OnceCell<JoinHandle<()>> = OnceCell::new();
static SLIDER_ACTIVE: AtomicBool = AtomicBool::new(false); static SLIDER_ACTIVE: AtomicBool = AtomicBool::new(false);
static SLIDER_OUTPUT: RwLock<[u8; con_impl::OUTPUT_MEMORY_SIZE]> = RwLock::new([0u8; con_impl::OUTPUT_MEMORY_SIZE]); static SLIDER_OUTPUT: RwLock<[u8; con_impl::OUTPUT_MEMORY_SIZE]> = RwLock::new([0u8; con_impl::OUTPUT_MEMORY_SIZE]);
} }
@ -107,6 +109,7 @@ cfg_if::cfg_if! {
} }
pub static TIMEOUT: Duration = Duration::from_millis(20); pub static TIMEOUT: Duration = Duration::from_millis(20);
pub static DEVICE_POLLING_INTERVAL: Duration = Duration::from_millis(100);
static mut INPUT_SHMEM: Option<Rc<Shmem>> = None; static mut INPUT_SHMEM: Option<Rc<Shmem>> = None;
const INPUT_SHMEM_OS_ID: &str = "fcfe5b1100568d65af167d81acbed71d"; const INPUT_SHMEM_OS_ID: &str = "fcfe5b1100568d65af167d81acbed71d";
@ -131,10 +134,9 @@ pub extern "C" fn chuni_io_get_api_version() -> u16 {
pub extern "C" fn chuni_io_jvs_init() -> HRESULT { pub extern "C" fn chuni_io_jvs_init() -> HRESULT {
#[cfg(chuni)] #[cfg(chuni)]
{ {
if let Err(e) = device_init() { // We don't care if we can't find the device at this time, it'll
error!("Could not initialize controller: {e:#?}"); // probably come eventually.
return E_FAIL; thread::spawn(device_init);
}
} }
create_input_shared_memory() create_input_shared_memory()
@ -207,11 +209,9 @@ pub unsafe extern "C" fn chuni_io_slider_init() -> HRESULT {
return S_OK; return S_OK;
} }
if let Err(e) = device_init() { // We don't care if we can't find the device at this time, it'll
error!("Could not initialize controller: {e:#?}"); // probably come eventually.
return E_FAIL; thread::spawn(device_init);
}
create_input_shared_memory() create_input_shared_memory()
} }
@ -228,14 +228,15 @@ pub unsafe extern "C" fn chuni_io_slider_start(callback: *const c_void) {
return; return;
} }
if SLIDER_THREAD.get().is_some() { if SLIDER_ACTIVE.load(Ordering::SeqCst) {
return; return;
} }
SLIDER_ACTIVE.store(true, Ordering::SeqCst); SLIDER_ACTIVE.store(true, Ordering::SeqCst);
let callback = std::mem::transmute::<_, SliderCallbackFn>(callback); let callback = std::mem::transmute::<_, SliderCallbackFn>(callback);
let thread = thread::spawn(move || {
thread::spawn(move || {
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else { let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
return; return;
}; };
@ -249,8 +250,6 @@ pub unsafe extern "C" fn chuni_io_slider_start(callback: *const c_void) {
thread::sleep(Duration::from_nanos(1_000_000)); thread::sleep(Duration::from_nanos(1_000_000));
} }
}); });
SLIDER_THREAD.set(thread).unwrap();
} }
#[no_mangle] #[no_mangle]
@ -260,13 +259,7 @@ pub extern "C" fn chuni_io_slider_start(_callback: *const c_void) {}
#[no_mangle] #[no_mangle]
#[cfg(any(chuni, chusanapp))] #[cfg(any(chuni, chusanapp))]
pub extern "C" fn chuni_io_slider_stop() { pub extern "C" fn chuni_io_slider_stop() {
let Some(thread) = (unsafe { SLIDER_THREAD.take() }) else {
return;
};
SLIDER_ACTIVE.store(false, Ordering::SeqCst); SLIDER_ACTIVE.store(false, Ordering::SeqCst);
thread.join().expect("Couldn't join slider input thread");
} }
#[no_mangle] #[no_mangle]
@ -280,7 +273,9 @@ pub unsafe extern "C" fn chuni_io_slider_set_leds(rgb: *const u8) {
return; return;
} }
let Some(device) = DEVICE.get() else { return }; let device_rg = DEVICE.read();
let Some(device) = device_rg.as_ref().map(|z| z) else { return };
let rgb = std::slice::from_raw_parts(rgb, 93); let rgb = std::slice::from_raw_parts(rgb, 93);
let mut output = SLIDER_OUTPUT.write(); let mut output = SLIDER_OUTPUT.write();
@ -316,8 +311,9 @@ pub unsafe extern "C" fn chuni_io_led_set_colors(board: u8, rgb: *const u8) {
return; return;
} }
let Some(device) = DEVICE.get() else { return }; let device_rg = DEVICE.read();
let rgb = std::slice::from_raw_parts(rgb, if board == 0 { 153 } else { 183 }); let Some(device) = device_rg.as_ref().map(|z| z) else { return };
let rgb = std::slice::from_raw_parts(rgb, 256);
let mut output = SLIDER_OUTPUT.write(); let mut output = SLIDER_OUTPUT.write();
if let Err(e) = con_impl::set_led_colors(device, output.as_mut_slice(), board, rgb) { if let Err(e) = con_impl::set_led_colors(device, output.as_mut_slice(), board, rgb) {
@ -331,28 +327,38 @@ pub extern "C" fn chuni_io_led_set_colors(_rgb: *const u8) {}
#[cfg(any(chuni, chusanapp))] #[cfg(any(chuni, chusanapp))]
fn device_init() -> Result<()> { fn device_init() -> Result<()> {
let Some(mut device) = {
rusb::open_device_with_vid_pid(con_impl::DEVICE_VID, con_impl::DEVICE_PID) let mut global_device = DEVICE.write();
else { *global_device = None;
return Err(anyhow!("Device not found.")); }
};
device.set_active_configuration(1)?; info!("Waiting for device...");
device.claim_interface(0)?;
DEVICE
.set(device)
.map_err(|_| anyhow!("Could not store device handle"))?;
thread::spawn(input_thread_proc); loop {
let Some(mut device) =
rusb::open_device_with_vid_pid(con_impl::DEVICE_VID, con_impl::DEVICE_PID)
else {
thread::sleep(DEVICE_POLLING_INTERVAL);
continue;
};
Ok(()) device.set_active_configuration(1)?;
device.claim_interface(0)?;
let mut global_device = DEVICE.write();
*global_device = Some(device);
thread::spawn(input_thread_proc);
return Ok(())
}
} }
#[cfg(any(chuni, chusanapp))] #[cfg(any(chuni, chusanapp))]
fn input_thread_proc() { fn input_thread_proc() {
info!("Input thread started"); info!("Input thread started");
let mut shmem = match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, true) let mut shmem = match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false)
{ {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
@ -361,7 +367,8 @@ fn input_thread_proc() {
} }
}; };
let usb_in = unsafe { shmem.as_slice_mut() }; let usb_in = unsafe { shmem.as_slice_mut() };
let device = DEVICE.get().unwrap(); let device_rg = DEVICE.read();
let device = device_rg.as_ref().unwrap();
loop { loop {
let result = match con_impl::READ_TYPE { let result = match con_impl::READ_TYPE {
@ -370,15 +377,31 @@ fn input_thread_proc() {
}; };
if let Err(e) = result { if let Err(e) = result {
error!("Failed to read data from controller: {e:#?}"); match e {
rusb::Error::NoDevice | rusb::Error::Io => {
error!("Controller disconnected.");
usb_in.iter_mut().for_each(|m| *m = 0);
// Spawn a thread polling for a connection again
thread::spawn(device_init);
return;
}
_ => error!("Could not read data from controller: {e:#?}"),
}
} }
} }
} }
fn create_input_shared_memory() -> HRESULT { fn create_input_shared_memory() -> HRESULT {
match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false) { match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false) {
Ok(s) => { Ok(mut s) => {
unsafe { INPUT_SHMEM = Some(Rc::new(s)) }; unsafe {
s.as_slice_mut().iter_mut().for_each(|m| *m = 0);
INPUT_SHMEM = Some(Rc::new(s))
};
S_OK S_OK
} }
Err(e) => { Err(e) => {

View File

@ -23,7 +23,7 @@ pub fn init_logger() {
env_logger::builder() env_logger::builder()
.filter_level(::log::LevelFilter::Error) .filter_level(::log::LevelFilter::Error)
.filter_module( .filter_module(
"chuniio-tasoller", "chuniio-rs",
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
::log::LevelFilter::Debug ::log::LevelFilter::Debug
} else { } else {