1
0
mirror of https://github.com/4yn/slidershim.git synced 2025-01-21 12:23:39 +01:00

yuancon io

This commit is contained in:
4yn 2022-01-28 20:17:34 +08:00
parent 511a6a605d
commit 7e842971e7
14 changed files with 736 additions and 25 deletions

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"editor.formatOnSave": true
}

122
src-tauri/Cargo.lock generated
View File

@ -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"

View File

@ -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
View 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
View File

@ -0,0 +1,7 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
#![feature(div_duration)]
pub mod slider_io;

View File

@ -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,

View 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"),
},
}
}
}

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

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

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

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

View 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,
}
}
}

View File

@ -0,0 +1,6 @@
pub mod config;
pub mod controller_state;
pub mod device;
pub mod hid;
pub mod led;
pub mod manager;

View File

@ -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}