mirror of
https://github.com/4yn/slidershim.git
synced 2025-01-21 12:23:39 +01:00
yuancon io
This commit is contained in:
parent
511a6a605d
commit
7e842971e7
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"editor.formatOnSave": true
|
||||
}
|
122
src-tauri/Cargo.lock
generated
122
src-tauri/Cargo.lock
generated
@ -39,13 +39,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94a45b455c14666b85fc40a019e8ab9eb75e3a124e05494f5397122bc9eb06e0"
|
||||
|
||||
[[package]]
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -800,6 +799,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "find-crate"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2"
|
||||
dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.22"
|
||||
@ -1466,6 +1474,18 @@ version = "0.2.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0005d08a8f7b65fb8073cb697aa0b12b631ed251ce73d862ce50eeb52ce3b50"
|
||||
|
||||
[[package]]
|
||||
name = "libusb1-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8772b7e8d4d988e19684aec5a3f5e470ecaf5c705cf0303da3973508e873027"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.5"
|
||||
@ -1868,6 +1888,30 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "palette"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9735f7e1e51a3f740bacd5dc2724b61a7806f23597a8736e679f38ee3435d18"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"num-traits",
|
||||
"palette_derive",
|
||||
"phf 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "palette_derive"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7799c3053ea8a6d8a1193c7ba42f534e7863cf52e378a7f90406f4a645d33bad"
|
||||
dependencies = [
|
||||
"find-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pango"
|
||||
version = "0.14.8"
|
||||
@ -1956,6 +2000,17 @@ dependencies = [
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2ac8b67553a7ca9457ce0e526948cad581819238f4a9d1ea74545851fa24f37"
|
||||
dependencies = [
|
||||
"phf_macros 0.9.0",
|
||||
"phf_shared 0.9.0",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.10.1"
|
||||
@ -1987,6 +2042,16 @@ dependencies = [
|
||||
"rand 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d43f3220d96e0080cc9ea234978ccd80d904eafb17be31bb0f76daaea6493082"
|
||||
dependencies = [
|
||||
"phf_shared 0.9.0",
|
||||
"rand 0.8.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.10.0"
|
||||
@ -2011,6 +2076,20 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b706f5936eb50ed880ae3009395b43ed19db5bff2ebd459c95e7bf013a89ab86"
|
||||
dependencies = [
|
||||
"phf_generator 0.9.1",
|
||||
"phf_shared 0.9.0",
|
||||
"proc-macro-hack",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.10.0"
|
||||
@ -2034,6 +2113,15 @@ dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a68318426de33640f02be62b4ae8eb1261be2efbc337b60c54d845bf4484e0d9"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.10.0"
|
||||
@ -2406,6 +2494,16 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusb"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83b454219aa5007af92a042ec13b2035325318a21d3c6be18bf592f841430794"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"libusb1-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-argon2"
|
||||
version = "0.8.3"
|
||||
@ -2641,6 +2739,18 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
||||
|
||||
[[package]]
|
||||
name = "slidershim"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"palette",
|
||||
"rusb",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
"tauri-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.8.0"
|
||||
|
@ -1,11 +1,11 @@
|
||||
[package]
|
||||
name = "app"
|
||||
name = "slidershim"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
description = "slidershim"
|
||||
authors = ["4yn"]
|
||||
license = ""
|
||||
repository = ""
|
||||
default-run = "app"
|
||||
default-run = "slidershim"
|
||||
edition = "2018"
|
||||
build = "src/build.rs"
|
||||
|
||||
@ -18,6 +18,8 @@ tauri-build = { version = "1.0.0-beta.4" }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.0.0-beta.8", features = ["api-all", "system-tray"] }
|
||||
rusb = "0.9.0"
|
||||
palette = "0.6.0"
|
||||
|
||||
[features]
|
||||
default = [ "custom-protocol" ]
|
||||
|
20
src-tauri/src/bin/test.rs
Normal file
20
src-tauri/src/bin/test.rs
Normal file
@ -0,0 +1,20 @@
|
||||
extern crate slidershim;
|
||||
|
||||
use std::io;
|
||||
|
||||
use slidershim::slider_io::{config::Config, manager::Manager};
|
||||
|
||||
fn main() {
|
||||
let config = Config::from_str(
|
||||
r#"{
|
||||
"deviceMode": "yuancon",
|
||||
"outputMode": "none",
|
||||
"ledMode": "reactive-8"
|
||||
}"#,
|
||||
);
|
||||
|
||||
let manager = Manager::new(config);
|
||||
|
||||
let mut input = String::new();
|
||||
let string = io::stdin().read_line(&mut input).unwrap();
|
||||
}
|
7
src-tauri/src/lib.rs
Normal file
7
src-tauri/src/lib.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
#![feature(div_duration)]
|
||||
|
||||
pub mod slider_io;
|
@ -2,6 +2,9 @@
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
#![feature(div_duration)]
|
||||
|
||||
mod slider_io;
|
||||
|
||||
use tauri::{
|
||||
AppHandle, CustomMenuItem, Event, Manager, Runtime, SystemTray, SystemTrayEvent, SystemTrayMenu,
|
||||
|
109
src-tauri/src/slider_io/config.rs
Normal file
109
src-tauri/src/slider_io/config.rs
Normal file
@ -0,0 +1,109 @@
|
||||
use serde_json::Value;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DeviceMode {
|
||||
None,
|
||||
Tasoller { version: i64 },
|
||||
Yuancon,
|
||||
Brokenithm { ground_only: bool },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum KeyboardLayout {
|
||||
Tasoller,
|
||||
Yuancon,
|
||||
Deemo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OutputMode {
|
||||
None,
|
||||
Keyboard {
|
||||
layout: KeyboardLayout,
|
||||
sensitivity: u8,
|
||||
},
|
||||
Websocket {
|
||||
url: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ReactiveLayout {
|
||||
Four,
|
||||
Eight,
|
||||
Sixteen,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LedMode {
|
||||
None,
|
||||
Reactive { layout: ReactiveLayout },
|
||||
Attract,
|
||||
Test,
|
||||
Websocket { url: String },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub raw: String,
|
||||
pub device_mode: DeviceMode,
|
||||
pub output_mode: OutputMode,
|
||||
pub led_mode: LedMode,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn from_str(s: &str) -> Config {
|
||||
let v: Value = serde_json::from_str(s).unwrap();
|
||||
|
||||
Config {
|
||||
raw: s.to_string(),
|
||||
device_mode: match v["deviceMode"].as_str().unwrap() {
|
||||
"none" => DeviceMode::None,
|
||||
"tasoller-one" => DeviceMode::Tasoller { version: 1 },
|
||||
"tasoller-two" => DeviceMode::Tasoller { version: 2 },
|
||||
"yuancon" => DeviceMode::Yuancon,
|
||||
"brokenithm" => DeviceMode::Brokenithm { ground_only: false },
|
||||
"brokenithm-ground" => DeviceMode::Brokenithm { ground_only: true },
|
||||
_ => panic!("Invalid device mode"),
|
||||
},
|
||||
output_mode: match v["outputMode"].as_str().unwrap() {
|
||||
"none" => OutputMode::None,
|
||||
"kb-32-tasoller" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::Tasoller,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64().unwrap()).unwrap(),
|
||||
},
|
||||
"kb-32-yuancon" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::Yuancon,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64().unwrap()).unwrap(),
|
||||
},
|
||||
"kb-6-deemo" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::Deemo,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64().unwrap()).unwrap(),
|
||||
},
|
||||
"websocket" => OutputMode::Websocket {
|
||||
url: v["outputWebsocketUrl"].to_string(),
|
||||
},
|
||||
_ => panic!("Invalid output mode"),
|
||||
},
|
||||
led_mode: match v["ledMode"].as_str().unwrap() {
|
||||
"none" => LedMode::None,
|
||||
"reactive-4" => LedMode::Reactive {
|
||||
layout: ReactiveLayout::Four,
|
||||
},
|
||||
"reactive-8" => LedMode::Reactive {
|
||||
layout: ReactiveLayout::Eight,
|
||||
},
|
||||
"reactive-16" => LedMode::Reactive {
|
||||
layout: ReactiveLayout::Sixteen,
|
||||
},
|
||||
"attract" => LedMode::Attract,
|
||||
"test" => LedMode::Test,
|
||||
"websocket" => LedMode::Websocket {
|
||||
url: v["ledWebsocketUrl"].to_string(),
|
||||
},
|
||||
_ => panic!("Invalid led mode"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
58
src-tauri/src/slider_io/controller_state.rs
Normal file
58
src-tauri/src/slider_io/controller_state.rs
Normal file
@ -0,0 +1,58 @@
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
pub struct ControllerState {
|
||||
pub ground_state: [u8; 32],
|
||||
pub air_state: [u8; 6],
|
||||
pub extra_state: [u8; 3],
|
||||
}
|
||||
|
||||
impl ControllerState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ground_state: [0; 32],
|
||||
air_state: [0; 6],
|
||||
extra_state: [0; 3],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LedState {
|
||||
pub led_state: [u8; 3 * 31],
|
||||
pub dirty: bool,
|
||||
pub start: Instant,
|
||||
}
|
||||
|
||||
impl LedState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
led_state: [0; 3 * 31],
|
||||
dirty: false,
|
||||
start: Instant::now(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FullState {
|
||||
pub controller_state: Arc<Mutex<ControllerState>>,
|
||||
pub led_state: Arc<Mutex<LedState>>,
|
||||
}
|
||||
|
||||
impl FullState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
controller_state: Arc::new(Mutex::new(ControllerState::new())),
|
||||
led_state: Arc::new(Mutex::new(LedState::new())),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_controller(&self) -> Arc<Mutex<ControllerState>> {
|
||||
Arc::clone(&self.controller_state)
|
||||
}
|
||||
|
||||
pub fn clone_led(&self) -> Arc<Mutex<LedState>> {
|
||||
Arc::clone(&self.led_state)
|
||||
}
|
||||
}
|
88
src-tauri/src/slider_io/device.rs
Normal file
88
src-tauri/src/slider_io/device.rs
Normal file
@ -0,0 +1,88 @@
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
|
||||
use crate::slider_io::{config::DeviceMode, controller_state::FullState, hid};
|
||||
|
||||
pub struct DeviceThread {
|
||||
thread: Option<JoinHandle<()>>,
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl DeviceThread {
|
||||
pub fn new(state: &FullState, mode: DeviceMode) -> Self {
|
||||
let controller_state = state.clone_controller();
|
||||
let led_state = state.clone_led();
|
||||
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let stop_signal_clone = Arc::clone(&stop_signal);
|
||||
Self {
|
||||
thread: Some(match mode {
|
||||
DeviceMode::None => thread::spawn(|| {}),
|
||||
DeviceMode::Tasoller { .. } => thread::spawn(|| {}),
|
||||
DeviceMode::Yuancon => thread::spawn(move || {
|
||||
hid::poll_controller(
|
||||
0x1973,
|
||||
0x2001,
|
||||
move |buf| {
|
||||
if (buf.len != 34) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut controller_state_handle = controller_state.lock().unwrap();
|
||||
controller_state_handle
|
||||
.ground_state
|
||||
.clone_from_slice(&buf.data[2..34]);
|
||||
for i in 0..6 {
|
||||
controller_state_handle.air_state[i ^ 1] =
|
||||
if buf.data[0] & (1 << i) == 0 { 1 } else { 0 };
|
||||
}
|
||||
for i in 0..3 {
|
||||
controller_state_handle.extra_state[i] =
|
||||
if buf.data[1] & (1 << i) == 0 { 1 } else { 0 };
|
||||
}
|
||||
|
||||
// println!("{:?}", controller_state_handle.ground_state);
|
||||
},
|
||||
move |buf| {
|
||||
let mut led_state_handle = led_state.lock().unwrap();
|
||||
if led_state_handle.dirty {
|
||||
buf.len = 31 * 2;
|
||||
buf
|
||||
.data
|
||||
.chunks_mut(2)
|
||||
.take(31)
|
||||
.zip(led_state_handle.led_state.chunks(3).rev())
|
||||
.for_each(|(buf_chunk, state_chunk)| {
|
||||
buf_chunk[0] = (state_chunk[0] << 3 & 0xe0) | (state_chunk[2] >> 3);
|
||||
buf_chunk[1] = (state_chunk[1] & 0xf8) | (state_chunk[0] >> 5);
|
||||
});
|
||||
led_state_handle.dirty = false;
|
||||
}
|
||||
},
|
||||
&stop_signal_clone,
|
||||
)
|
||||
// .unwrap_or_else(|err| {
|
||||
// println!("Device thread: {:?}", err);
|
||||
// });
|
||||
.unwrap();
|
||||
}),
|
||||
DeviceMode::Brokenithm { .. } => thread::spawn(|| {}),
|
||||
}),
|
||||
stop_signal: stop_signal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DeviceThread {
|
||||
fn drop(&mut self) {
|
||||
self.stop_signal.swap(true, Ordering::SeqCst);
|
||||
if self.thread.is_some() {
|
||||
self.thread.take().unwrap().join().ok();
|
||||
}
|
||||
}
|
||||
}
|
124
src-tauri/src/slider_io/hid.rs
Normal file
124
src-tauri/src/slider_io/hid.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use std::{
|
||||
error,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use rusb::{self, UsbContext};
|
||||
|
||||
const timeout: Duration = Duration::from_millis(20);
|
||||
|
||||
pub struct Buffer {
|
||||
pub data: [u8; 128],
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn new() -> Self {
|
||||
Buffer {
|
||||
data: [0; 128],
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn slice(&self) -> &[u8] {
|
||||
&self.data[0..self.len]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn poll_controller(
|
||||
vid: u16,
|
||||
pid: u16,
|
||||
read_callback: impl Fn(&Buffer) -> (),
|
||||
write_callback: impl Fn(&mut Buffer) -> (),
|
||||
// write_buf: Arc<Mutex<Buffer>>,
|
||||
stop: &AtomicBool,
|
||||
) -> Result<(), Box<dyn error::Error>> {
|
||||
// println!("Getting context");
|
||||
// let mut context = rusb::Context::new().unwrap();
|
||||
// println!("Getting devices");
|
||||
// let devices: Vec<rusb::Device<rusb::Context>> = context
|
||||
// .devices()
|
||||
// .unwrap()
|
||||
// .iter()
|
||||
// .filter(|d| {
|
||||
// d.device_descriptor()
|
||||
// .map(|d| d.vendor_id() == vid && d.product_id() == pid)
|
||||
// .unwrap_or(false)
|
||||
// })
|
||||
// .collect();
|
||||
// println!("Found {:?}", devices);
|
||||
// let mut handle = devices[0].open().unwrap();
|
||||
|
||||
let mut handle = rusb::open_device_with_vid_pid(vid, pid).unwrap();
|
||||
println!("Found device {:?}", handle);
|
||||
// .ok_or("Cannot find usb device".to_string())?;
|
||||
|
||||
// let device = handle.device();
|
||||
if handle.kernel_driver_active(0).unwrap_or(false) {
|
||||
println!("Disabling kernel driver");
|
||||
handle.detach_kernel_driver(0)?;
|
||||
}
|
||||
|
||||
println!("Kernel driver OK");
|
||||
handle.set_active_configuration(1)?;
|
||||
println!("Configuration OK");
|
||||
handle.claim_interface(0)?;
|
||||
println!("Interface OK");
|
||||
|
||||
let mut in_buf = Buffer::new();
|
||||
let mut led_buf = Buffer::new();
|
||||
loop {
|
||||
{
|
||||
// Read loop
|
||||
|
||||
let res = handle
|
||||
.read_interrupt(0x81, &mut in_buf.data, timeout)
|
||||
.unwrap_or(0);
|
||||
|
||||
in_buf.len = res;
|
||||
|
||||
// println!("Read {:?}", res);
|
||||
// println!("Data {:?}", in_buf.data);
|
||||
|
||||
if in_buf.len != 0 {
|
||||
read_callback(&in_buf);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Write loop
|
||||
write_callback(&mut led_buf);
|
||||
if led_buf.len != 0 {
|
||||
let res = handle
|
||||
.write_interrupt(0x02, led_buf.slice(), timeout)
|
||||
.unwrap_or(0);
|
||||
|
||||
// println!(
|
||||
// "Sent {:?} {:?} {:?}",
|
||||
// led_buf.len,
|
||||
// res,
|
||||
// led_buf.slice().len()
|
||||
// );
|
||||
if res == led_buf.len + 1 {
|
||||
led_buf.len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if stop.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("HID thread stopped");
|
||||
|
||||
handle.release_interface(0)?;
|
||||
|
||||
Ok(())
|
||||
}
|
153
src-tauri/src/slider_io/led.rs
Normal file
153
src-tauri/src/slider_io/led.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use std::{
|
||||
borrow::BorrowMut,
|
||||
ops::{Deref, DerefMut},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread::{self, JoinHandle},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use palette::{FromColor, Hsv, Srgb};
|
||||
|
||||
use crate::slider_io::{
|
||||
config::{LedMode, ReactiveLayout},
|
||||
controller_state::{ControllerState, FullState, LedState},
|
||||
};
|
||||
|
||||
fn update_reactive(
|
||||
controller_state: &ControllerState,
|
||||
led_state: &mut LedState,
|
||||
reactive_layout: &ReactiveLayout,
|
||||
sensitivity: u8,
|
||||
) {
|
||||
let splits = match reactive_layout {
|
||||
ReactiveLayout::Four => 4,
|
||||
ReactiveLayout::Eight => 8,
|
||||
ReactiveLayout::Sixteen => 16,
|
||||
};
|
||||
let buttons_per_split = 32 / splits;
|
||||
|
||||
let banks: Vec<bool> = controller_state
|
||||
.ground_state
|
||||
.chunks(buttons_per_split)
|
||||
.map(|x| x.iter().max().unwrap() > &sensitivity)
|
||||
.collect();
|
||||
|
||||
// (0..splits)
|
||||
// .map(|i| {
|
||||
// controller_state.ground_state[i * buttons_per_split..(i + 1) * buttons_per_split]
|
||||
// .iter()
|
||||
// .max()
|
||||
// .unwrap()
|
||||
// > &sensitivity
|
||||
// })
|
||||
// .collect();
|
||||
|
||||
led_state
|
||||
.led_state
|
||||
.chunks_mut(3)
|
||||
.enumerate()
|
||||
.for_each(|(idx, chunk)| match (idx + 1) % buttons_per_split {
|
||||
0 => {
|
||||
chunk[0] = 255;
|
||||
chunk[1] = 0;
|
||||
chunk[2] = 255;
|
||||
}
|
||||
_ => match banks[idx / buttons_per_split] {
|
||||
true => {
|
||||
chunk[0] = 255;
|
||||
chunk[1] = 0;
|
||||
chunk[2] = 255;
|
||||
}
|
||||
false => {
|
||||
chunk[0] = 255;
|
||||
chunk[1] = 255;
|
||||
chunk[2] = 0;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// println!("{:?}", controller_state.ground_state);
|
||||
// println!("{:?}", banks);
|
||||
// println!("{:?}", led_state.led_state);
|
||||
|
||||
led_state.dirty = true;
|
||||
}
|
||||
|
||||
fn update_attract(led_state: &mut LedState) {
|
||||
let now = Instant::now();
|
||||
let theta = (now - led_state.start).div_duration_f64(Duration::from_secs(4)) % 1.0;
|
||||
|
||||
led_state
|
||||
.led_state
|
||||
.chunks_mut(3)
|
||||
.enumerate()
|
||||
.for_each(|(idx, chunk)| {
|
||||
let slice_theta = (&theta + (idx as f64) / 31.0) % 1.0;
|
||||
let color = Srgb::from_color(Hsv::new(slice_theta * 360.0, 1.0, 1.0)).into_format::<u8>();
|
||||
chunk[0] = color.red;
|
||||
chunk[1] = color.green;
|
||||
chunk[2] = color.blue;
|
||||
});
|
||||
|
||||
// println!("{} {:?}", theta, led_state.led_state);
|
||||
|
||||
led_state.dirty = true;
|
||||
}
|
||||
|
||||
pub struct LedThread {
|
||||
thread: Option<JoinHandle<()>>,
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl LedThread {
|
||||
pub fn new(state: &FullState, mode: LedMode) -> Self {
|
||||
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let stop_signal_clone = Arc::clone(&stop_signal);
|
||||
let controller_state = state.clone_controller();
|
||||
let led_state = state.clone_led();
|
||||
Self {
|
||||
thread: Some(thread::spawn(move || loop {
|
||||
// println!("Led thread: {:?}", mode);
|
||||
match &mode {
|
||||
LedMode::Reactive { layout } => {
|
||||
let controller_state_handle = controller_state.lock().unwrap();
|
||||
let mut led_state_handle = led_state.lock().unwrap();
|
||||
update_reactive(
|
||||
controller_state_handle.deref(),
|
||||
led_state_handle.deref_mut(),
|
||||
layout,
|
||||
20,
|
||||
)
|
||||
}
|
||||
LedMode::Attract => {
|
||||
let mut led_state_handle = led_state.lock().unwrap();
|
||||
update_attract(led_state_handle.deref_mut());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
{
|
||||
if stop_signal_clone.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
thread::sleep(Duration::from_millis(33))
|
||||
})),
|
||||
stop_signal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LedThread {
|
||||
fn drop(&mut self) {
|
||||
self.stop_signal.swap(true, Ordering::SeqCst);
|
||||
if self.thread.is_some() {
|
||||
self.thread.take().unwrap().join().ok();
|
||||
}
|
||||
}
|
||||
}
|
27
src-tauri/src/slider_io/manager.rs
Normal file
27
src-tauri/src/slider_io/manager.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use crate::slider_io::{
|
||||
config::Config, controller_state::FullState, device::DeviceThread, led::LedThread,
|
||||
};
|
||||
|
||||
pub struct Manager {
|
||||
state: FullState,
|
||||
config: Config,
|
||||
device_thread: DeviceThread,
|
||||
led_thread: LedThread,
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn new(config: Config) -> Self {
|
||||
let state = FullState::new();
|
||||
let device_thread = DeviceThread::new(&state, config.device_mode.clone());
|
||||
let led_thread = LedThread::new(&state, config.led_mode.clone());
|
||||
|
||||
println!("Starting manager with config: {:?}", config);
|
||||
|
||||
Self {
|
||||
state,
|
||||
config,
|
||||
device_thread,
|
||||
led_thread,
|
||||
}
|
||||
}
|
||||
}
|
6
src-tauri/src/slider_io/mod.rs
Normal file
6
src-tauri/src/slider_io/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
pub mod config;
|
||||
pub mod controller_state;
|
||||
pub mod device;
|
||||
pub mod hid;
|
||||
pub mod led;
|
||||
pub mod manager;
|
@ -6,21 +6,21 @@
|
||||
|
||||
let deviceMode = "none";
|
||||
let outputMode = "none";
|
||||
let lightingMode = "none";
|
||||
let ledMode = "none";
|
||||
|
||||
let keyboardSensitivity = 20;
|
||||
let websocketOutputUrl = "http://localhost:3000";
|
||||
let websocketLedUrl = "http://localhost:3001";
|
||||
let outputWebsocketUrl = "http://localhost:3000";
|
||||
let ledWebsocketUrl = "http://localhost:3001";
|
||||
|
||||
onMount(async () => {
|
||||
await listen("showConfig", (event) => {
|
||||
const payload: any = event.payload;
|
||||
deviceMode = payload.deviceMode;
|
||||
outputMode = payload.outputMode;
|
||||
lightingMode = payload.lightingMode;
|
||||
ledMode = payload.ledMode;
|
||||
keyboardSensitivity = payload.keyboardSensitivity;
|
||||
websocketOutputUrl = payload.websocketOutputUrl;
|
||||
websocketLedUrl = payload.websocketLedUrl;
|
||||
outputWebsocketUrl = payload.outputWebsocketUrl;
|
||||
ledWebsocketUrl = payload.ledWebsocketUrl;
|
||||
});
|
||||
});
|
||||
|
||||
@ -29,10 +29,10 @@
|
||||
await emit("setConfig", JSON.stringify({
|
||||
deviceMode,
|
||||
outputMode,
|
||||
lightingMode,
|
||||
ledMode,
|
||||
keyboardSensitivity,
|
||||
websocketOutputUrl,
|
||||
websocketLedUrl
|
||||
outputWebsocketUrl,
|
||||
ledWebsocketUrl
|
||||
}));
|
||||
console.log("Done");
|
||||
}
|
||||
@ -49,8 +49,8 @@
|
||||
<main class="main">
|
||||
<div class="row">
|
||||
<div class="header">
|
||||
slidershim
|
||||
<!-- slidershim by @4yn -->
|
||||
slidershim
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
@ -61,7 +61,8 @@
|
||||
<div class="input">
|
||||
<select bind:value={deviceMode}>
|
||||
<option value="none">None</option>
|
||||
<option value="tasoller">GAMO2 Tasoller, HID Firmware</option>
|
||||
<option value="tasoller-one">GAMO2 Tasoller, 1.0 HID Firmware</option>
|
||||
<option value="tasoller-two">GAMO2 Tasoller, 2.0 HID Firmware</option>
|
||||
<option value="yuancon">Yuancon Laverita, HID Firmware</option>
|
||||
<option value="brokenithm">Brokenithm</option>
|
||||
<option value="brokenithm-ground">Brokenithm, Ground only</option>
|
||||
@ -111,14 +112,14 @@
|
||||
<div class="row">
|
||||
<div class="label">Output URL</div>
|
||||
<div class="input">
|
||||
<input placeholder="URL" bind:value={websocketOutputUrl} />
|
||||
<input placeholder="URL" bind:value={outputWebsocketUrl} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="row">
|
||||
<div class="label">LED Mode</div>
|
||||
<div class="input">
|
||||
<select bind:value={lightingMode}>
|
||||
<select bind:value={ledMode}>
|
||||
<option value="none">None</option>
|
||||
<option value="reactive-4">Reactive, 4-Zone</option>
|
||||
<option value="reactive-8">Reactive, 8-Zone</option>
|
||||
@ -129,11 +130,11 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{#if lightingMode === "websocket"}
|
||||
{#if ledMode === "websocket"}
|
||||
<div class="row">
|
||||
<div class="label">LED URL</div>
|
||||
<div class="input">
|
||||
<input placeholder="URL" bind:value={websocketLedUrl} />
|
||||
<input placeholder="URL" bind:value={ledWebsocketUrl} />
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
Loading…
x
Reference in New Issue
Block a user