Compare commits

..

No commits in common. "trunk" and "v0.1.1" have entirely different histories.

10 changed files with 75 additions and 393 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "chuniio-rs" name = "chuniio-rs"
version = "0.1.3" version = "0.1.0"
edition = "2021" edition = "2021"
[lib] [lib]
@ -17,8 +17,6 @@ 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,7 +23,6 @@ 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 laverita_v3 for backend in tasoller_v1 tasoller_v2
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

View File

@ -1,162 +0,0 @@
//! # 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,6 +1,4 @@
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;

View File

@ -1,129 +0,0 @@
//! # 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: IR1 //! - Bit 28: IR2
//! - Bit 29: IR2 //! - Bit 29: IR1
//! - Bit 30: IR3 //! - Bit 30: IR4
//! - Bit 31: IR4 //! - Bit 31: IR3
//! - Bit 32: IR5 //! - Bit 32: IR6
//! - Bit 33: IR6 //! - Bit 33: IR5
//! - 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,8 +58,12 @@ 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] >> 4 let beams = input[3] >> 5 & 1
| (input[4] & 3) << 4; | input[3] >> 3 & 2
| input[3] >> 5 & 4
| input[3] >> 3 & 8
| input[4] << 3 & 16
| input[4] << 5 & 32;
(opbtn, beams) (opbtn, beams)
} }
@ -131,14 +135,13 @@ 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, state_chunk) in output[output_start..output_start + 72] for buf_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] = state_chunk[1]; buf_chunk[0] = rgb[air_rgb + 1];
buf_chunk[1] = state_chunk[0]; buf_chunk[1] = rgb[air_rgb];
buf_chunk[2] = state_chunk[2]; buf_chunk[2] = rgb[air_rgb + 2];
} }
device.write_bulk(0x03, output, TIMEOUT)?; device.write_bulk(0x03, output, TIMEOUT)?;

View File

@ -18,7 +18,7 @@
//! ### OUT Bulk (0x03) //! ### OUT Bulk (0x03)
//! - Content length: 240 bytes //! - Content length: 240 bytes
//! - Bytes 0..3: magic bytes [0x42, 0x4C, 0x00] //! - Bytes 0..3: magic bytes [0x42, 0x4C, 0x00]
//! - Bytes 3..96: Slider LED (BGR order, right -> left) //! - Bytes 3..96: Slider LED (GRB order, right -> left)
//! - Bytes 96..168: Left tower LEDs (GRB) //! - Bytes 96..168: Left tower LEDs (GRB)
//! - Bytes 168..240: Right tower LEDs (GRB) //! - Bytes 168..240: Right tower LEDs (GRB)
@ -42,7 +42,6 @@ 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)
@ -83,9 +82,9 @@ pub fn set_slider_leds<T: UsbContext>(
rgb: &[u8], rgb: &[u8],
) -> Result<()> { ) -> Result<()> {
for (buf_chunk, state_chunk) in output[3..96].chunks_mut(3).take(31).zip(rgb.chunks(3)) { for (buf_chunk, state_chunk) in output[3..96].chunks_mut(3).take(31).zip(rgb.chunks(3)) {
buf_chunk[0] = state_chunk[2]; buf_chunk[0] = state_chunk[1];
buf_chunk[1] = state_chunk[1]; buf_chunk[1] = state_chunk[0];
buf_chunk[2] = state_chunk[0]; buf_chunk[2] = state_chunk[2];
} }
device.write_bulk(0x03, output, TIMEOUT)?; device.write_bulk(0x03, output, TIMEOUT)?;
@ -108,14 +107,13 @@ 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, state_chunk) in output[output_start..output_start + 72] for buf_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] = state_chunk[1]; buf_chunk[0] = rgb[air_rgb + 1];
buf_chunk[1] = state_chunk[0]; buf_chunk[1] = rgb[air_rgb];
buf_chunk[2] = state_chunk[2]; buf_chunk[2] = rgb[air_rgb + 2];
} }
device.write_bulk(0x03, output, TIMEOUT)?; device.write_bulk(0x03, output, TIMEOUT)?;

View File

@ -34,10 +34,6 @@ 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;
} }
@ -47,15 +43,17 @@ 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; use std::thread::{self, JoinHandle};
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: RwLock<Option<DeviceHandle<GlobalContext>>> = RwLock::new(None); static DEVICE: OnceCell<DeviceHandle<GlobalContext>> = OnceCell::new();
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]);
} }
@ -71,7 +69,7 @@ cfg_if::cfg_if! {
use crate::configuration::Configuration; use crate::configuration::Configuration;
static COIN_COUNT: AtomicU16 = AtomicU16::new(0); static COIN_COUNT: AtomicU16 = AtomicU16::new(0);
static COIN_PRESSED: AtomicBool = AtomicBool::new(false); static mut COIN_PRESSED: bool = false;
lazy_static! { lazy_static! {
static ref CONFIGURATION: Configuration = { static ref CONFIGURATION: Configuration = {
@ -109,7 +107,6 @@ 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";
@ -134,9 +131,10 @@ 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)]
{ {
// We don't care if we can't find the device at this time, it'll if let Err(e) = device_init() {
// probably come eventually. error!("Could not initialize controller: {e:#?}");
thread::spawn(device_init); return E_FAIL;
}
} }
create_input_shared_memory() create_input_shared_memory()
@ -184,12 +182,12 @@ pub unsafe extern "C" fn chuni_io_jvs_read_coin_counter(total: *mut u16) {
if con_impl::is_coin_button_pressed(input_shmem.as_slice()) if con_impl::is_coin_button_pressed(input_shmem.as_slice())
|| GetAsyncKeyState(CONFIGURATION.coin_key as c_int) != 0 || GetAsyncKeyState(CONFIGURATION.coin_key as c_int) != 0
{ {
if !COIN_PRESSED.load(Ordering::Relaxed) { if !COIN_PRESSED {
COIN_PRESSED.store(true, Ordering::Relaxed); COIN_PRESSED = true;
COIN_COUNT.fetch_add(1, Ordering::Relaxed); COIN_COUNT.fetch_add(1, Ordering::Relaxed);
} }
} else { } else {
COIN_PRESSED.store(false, Ordering::Relaxed); COIN_PRESSED = false;
} }
*total = COIN_COUNT.load(Ordering::Relaxed); *total = COIN_COUNT.load(Ordering::Relaxed);
@ -209,9 +207,11 @@ pub unsafe extern "C" fn chuni_io_slider_init() -> HRESULT {
return S_OK; return S_OK;
} }
// We don't care if we can't find the device at this time, it'll if let Err(e) = device_init() {
// probably come eventually. error!("Could not initialize controller: {e:#?}");
thread::spawn(device_init); return E_FAIL;
}
create_input_shared_memory() create_input_shared_memory()
} }
@ -228,15 +228,14 @@ pub unsafe extern "C" fn chuni_io_slider_start(callback: *const c_void) {
return; return;
} }
if SLIDER_ACTIVE.load(Ordering::SeqCst) { if SLIDER_THREAD.get().is_some() {
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;
}; };
@ -250,6 +249,8 @@ 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]
@ -259,7 +260,13 @@ 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]
@ -273,9 +280,7 @@ pub unsafe extern "C" fn chuni_io_slider_set_leds(rgb: *const u8) {
return; return;
} }
let device_rg = DEVICE.read(); let Some(device) = DEVICE.get() else { return };
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();
@ -311,9 +316,8 @@ pub unsafe extern "C" fn chuni_io_led_set_colors(board: u8, rgb: *const u8) {
return; return;
} }
let device_rg = DEVICE.read(); let Some(device) = DEVICE.get() else { return };
let Some(device) = device_rg.as_ref().map(|z| z) else { return }; let rgb = std::slice::from_raw_parts(rgb, if board == 0 { 153 } else { 183 });
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) {
@ -327,38 +331,28 @@ 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 mut global_device = DEVICE.write();
*global_device = None;
}
info!("Waiting for device...");
loop {
let Some(mut device) = let Some(mut device) =
rusb::open_device_with_vid_pid(con_impl::DEVICE_VID, con_impl::DEVICE_PID) rusb::open_device_with_vid_pid(con_impl::DEVICE_VID, con_impl::DEVICE_PID)
else { else {
thread::sleep(DEVICE_POLLING_INTERVAL); return Err(anyhow!("Device not found."));
continue;
}; };
device.set_active_configuration(1)?; device.set_active_configuration(1)?;
device.claim_interface(0)?; device.claim_interface(0)?;
DEVICE
let mut global_device = DEVICE.write(); .set(device)
*global_device = Some(device); .map_err(|_| anyhow!("Could not store device handle"))?;
thread::spawn(input_thread_proc); thread::spawn(input_thread_proc);
return Ok(()) 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, false) let mut shmem = match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, true)
{ {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
@ -367,8 +361,7 @@ fn input_thread_proc() {
} }
}; };
let usb_in = unsafe { shmem.as_slice_mut() }; let usb_in = unsafe { shmem.as_slice_mut() };
let device_rg = DEVICE.read(); let device = DEVICE.get().unwrap();
let device = device_rg.as_ref().unwrap();
loop { loop {
let result = match con_impl::READ_TYPE { let result = match con_impl::READ_TYPE {
@ -377,31 +370,15 @@ fn input_thread_proc() {
}; };
if let Err(e) = result { if let Err(e) = result {
match e { error!("Failed to read data from controller: {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(mut s) => { Ok(s) => {
unsafe { unsafe { INPUT_SHMEM = Some(Rc::new(s)) };
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-rs", "chuniio-tasoller",
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
::log::LevelFilter::Debug ::log::LevelFilter::Debug
} else { } else {