restructuring
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
#![feature(div_duration)]
|
||||
#![feature(more_qualified_paths)]
|
||||
|
||||
mod slider_io;
|
||||
use slider_io;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
extern crate slidershim;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use std::{future::Future, io, time::Duration};
|
||||
|
||||
use tokio::{select, time::sleep};
|
||||
|
||||
// use slidershim::slider_io::worker::{AsyncJob, AsyncWorker};
|
||||
|
||||
// struct CounterJob;
|
||||
|
||||
// #[async_trait]
|
||||
// impl AsyncJob for CounterJob {
|
||||
// async fn run<F: Future<Output = ()> + Send>(self, stop_signal: F) {
|
||||
// let job_a = async {
|
||||
// println!("Start job A");
|
||||
// let mut x = 0;
|
||||
// loop {
|
||||
// x += 1;
|
||||
// println!("{}", x);
|
||||
// sleep(Duration::from_millis(100)).await;
|
||||
// }
|
||||
// };
|
||||
// let job_b = async move {
|
||||
// println!("Start job B");
|
||||
// stop_signal.await;
|
||||
// println!("Stop signal hit at job B");
|
||||
// };
|
||||
|
||||
// select! {
|
||||
// _ = job_a => {},
|
||||
// _ = job_b => {},
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fn main() {
|
||||
env_logger::Builder::new()
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.init();
|
||||
|
||||
// let worker = AsyncWorker::new("counter", CounterJob);
|
||||
let mut input = String::new();
|
||||
let string = io::stdin().read_line(&mut input).unwrap();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
extern crate slidershim;
|
||||
|
||||
use std::{io, time::Duration};
|
||||
|
||||
use tokio::time::sleep;
|
||||
|
||||
// use slidershim::slider_io::{
|
||||
// brokenithm::BrokenithmJob, controller_state::FullState, worker::AsyncWorker,
|
||||
// };
|
||||
|
||||
fn main() {
|
||||
env_logger::Builder::new()
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.init();
|
||||
|
||||
// let worker = AsyncWorker::new("brokenithm", BrokenithmJob::new(FullState::new()));
|
||||
let mut input = String::new();
|
||||
let string = io::stdin().read_line(&mut input).unwrap();
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// extern crate slidershim;
|
||||
|
||||
use serialport::available_ports;
|
||||
use std::io;
|
||||
|
||||
fn main() {
|
||||
let res = available_ports();
|
||||
println!("{:?}", res);
|
||||
let mut input = String::new();
|
||||
let string = io::stdin().read_line(&mut input).unwrap();
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
extern crate slidershim;
|
||||
|
||||
use std::io;
|
||||
|
||||
// use slidershim::slider_io::{Config, Context};
|
||||
|
||||
fn main() {
|
||||
env_logger::Builder::new()
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.init();
|
||||
|
||||
// voltex?
|
||||
// let config = Config::from_str(
|
||||
// r#"{
|
||||
// "deviceMode": "yuancon",
|
||||
// "outputMode": "gamepad-voltex",
|
||||
// "keyboardSensitivity": 50,
|
||||
// "ledMode": "reactive-voltex",
|
||||
// "ledSensitivity": 50
|
||||
// }"#,
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// serial?
|
||||
// let config = Config::from_str(
|
||||
// r#"{
|
||||
// "deviceMode": "yuancon",
|
||||
// "outputMode": "kb-32-tasoller",
|
||||
// "keyboardSensitivity": 50,
|
||||
// "ledMode": "serial",
|
||||
// "ledSerialPort": "COM5"
|
||||
// }"#,
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// basic
|
||||
// let config = Config::from_str(
|
||||
// r#"{
|
||||
// "deviceMode": "yuancon",
|
||||
// "outputMode": "kb-32-tasoller",
|
||||
// "keyboardSensitivity": 50,fdwdfp1
|
||||
// "ledMode": "reactive-8",
|
||||
// "ledSensitivity": 50
|
||||
// }"#,
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// tasoller/
|
||||
// let config = Config::from_str(
|
||||
// r#"{
|
||||
// "deviceMode": "tasoller-two",
|
||||
// "outputMode": "kb-32-tasoller",
|
||||
// "keyboardSensitivity": 50,
|
||||
// "ledMode": "reactive-8",
|
||||
// "ledSensitivity": 50
|
||||
// }"#,
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// let manager = Context::new(config);
|
||||
|
||||
let mut input = String::new();
|
||||
let string = io::stdin().read_line(&mut input).unwrap();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
extern crate slidershim;
|
||||
|
||||
use std::io;
|
||||
|
||||
// use slidershim::slider_io::worker::{Job, Worker};
|
||||
|
||||
// struct TestJob {
|
||||
// data: i64,
|
||||
// }
|
||||
|
||||
// impl Job for TestJob {
|
||||
// fn setup(&mut self) {
|
||||
// self.data = 10;
|
||||
// println!("setup {}", self.data);
|
||||
// }
|
||||
// fn tick(&mut self) {
|
||||
// self.data -= 1;
|
||||
// println!("tick {}", self.data);
|
||||
// }
|
||||
// fn teardown(&mut self) {
|
||||
// self.data = 11;
|
||||
// println!("teardown {}", self.data);
|
||||
// }
|
||||
// }
|
||||
|
||||
fn main() {
|
||||
// let worker = Worker::new(TestJob { data: 1 });
|
||||
|
||||
let mut input = String::new();
|
||||
let string = io::stdin().read_line(&mut input).unwrap();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#![cfg_attr(
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
#![feature(div_duration)]
|
||||
#![feature(more_qualified_paths)]
|
||||
|
||||
pub mod slider_io;
|
||||
@@ -1,52 +0,0 @@
|
||||
// use serialport::SerialPort;
|
||||
// use std::io::{BufRead, BufReader};
|
||||
|
||||
// struct ArcadeSlider {
|
||||
// serial_port: BufReader<Box<dyn SerialPort>>,
|
||||
// }
|
||||
|
||||
// impl ArcadeSlider {
|
||||
// fn new() -> Self {
|
||||
// let serial_port = serialport::new("COM1", 152000).open().unwrap();
|
||||
// let serial_port_buf = BufReader::new(serial_port);
|
||||
// Self {
|
||||
// serial_port: serial_port_buf,
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn recv(&mut self) {
|
||||
// let mut consumed = 0;
|
||||
// {
|
||||
// let d = self.serial_port.fill_buf().unwrap();
|
||||
|
||||
// let mut packets = vec![];
|
||||
// let mut packet = vec![];
|
||||
|
||||
// let mut bytes_taken = 0;
|
||||
// let mut in_escape = 0;
|
||||
// let mut checksum = 0;
|
||||
// for b in d.iter() {
|
||||
// bytes_taken += 1;
|
||||
|
||||
// match b {
|
||||
// 0xff => {
|
||||
// consumed += bytes_taken;
|
||||
// bytes_taken = 0;
|
||||
// in_escape = 0;
|
||||
// }
|
||||
// 0xfd => {
|
||||
// in_escape = 1;
|
||||
// }
|
||||
// _ => {
|
||||
// let b = b + in_escape;
|
||||
// in_escape = 0;
|
||||
// packet.push(b);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// self.serial_port.consume(consumed);
|
||||
// }
|
||||
|
||||
// fn send(&mut self) {}
|
||||
// }
|
||||
File diff suppressed because one or more lines are too long
@@ -1,21 +0,0 @@
|
||||
var config = {
|
||||
// Inverted layout mode.
|
||||
// Set to "true" to have the "lift" key at the bottom of the screen rather than the top.
|
||||
invert: false,
|
||||
|
||||
// Use a solid background color
|
||||
bgImage: false,
|
||||
bgColor: "#000000",
|
||||
|
||||
// Use a custom background image
|
||||
// bgColor is overlayed on the image, so make it semi-transparent
|
||||
// bgImage:
|
||||
// "https://raw.githubusercontent.com/gist/4yn/df052666266ce25554110ca1b4f33ce3/raw/1e5e6ab966639bb6786a4690dc49097763b16ba0/subtle-prism.svg",
|
||||
// bgColor: "rgba(0, 0, 0, 0.5)",
|
||||
|
||||
// Key Press Color
|
||||
keyColor: "#FF00FF",
|
||||
|
||||
// Lift key color
|
||||
lkeyColor: "#00FFFF",
|
||||
};
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 5.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -1,120 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>slidershim-brokenithm</title>
|
||||
<meta charset="utf8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="/icon.png" />
|
||||
<style>
|
||||
#fullscreen {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
background: #000000;
|
||||
color: hotpink;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
touch-action: none;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.air-container {
|
||||
/* display: flex; */
|
||||
display: none;
|
||||
flex-flow: column nowrap;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.touch-container {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.grow > * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.key {
|
||||
flex: 1;
|
||||
border: 1px solid green;
|
||||
}
|
||||
|
||||
.key[data-active] {
|
||||
background-color: hotpink;
|
||||
}
|
||||
|
||||
.key.air[data-active] {
|
||||
background-color: skyblue;
|
||||
}
|
||||
|
||||
canvas {
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
touch-action: none;
|
||||
margin: 0px -1.5625vw;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="fullscreen">
|
||||
<!-- Offset for LED display -->
|
||||
<div class="container">
|
||||
<div class="air-container grow"></div>
|
||||
<div class="touch-container grow">
|
||||
<canvas id="canvas" width="33" height="1"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hitbox Divs -->
|
||||
<div class="container" id="main">
|
||||
<div class="air-container grow">
|
||||
<div class="air key" data-air="1" data-kflag="5"></div>
|
||||
<div class="air key" data-air="1" data-kflag="4"></div>
|
||||
<div class="air key" data-air="1" data-kflag="3"></div>
|
||||
<div class="air key" data-air="1" data-kflag="2"></div>
|
||||
<div class="air key" data-air="1" data-kflag="1"></div>
|
||||
<div class="air key" data-air="1" data-kflag="0"></div>
|
||||
</div>
|
||||
<div class="touch-container grow">
|
||||
<div class="key" data-kflag="0"></div>
|
||||
<div class="key" data-kflag="2"></div>
|
||||
<div class="key" data-kflag="4"></div>
|
||||
<div class="key" data-kflag="6"></div>
|
||||
<div class="key" data-kflag="8"></div>
|
||||
<div class="key" data-kflag="10"></div>
|
||||
<div class="key" data-kflag="12"></div>
|
||||
<div class="key" data-kflag="14"></div>
|
||||
<div class="key" data-kflag="16"></div>
|
||||
<div class="key" data-kflag="18"></div>
|
||||
<div class="key" data-kflag="20"></div>
|
||||
<div class="key" data-kflag="22"></div>
|
||||
<div class="key" data-kflag="24"></div>
|
||||
<div class="key" data-kflag="26"></div>
|
||||
<div class="key" data-kflag="28"></div>
|
||||
<div class="key" data-kflag="30"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/config.js"></script>
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,119 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>slidershim-brokenithm</title>
|
||||
<meta charset="utf8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="/icon.png" />
|
||||
<style>
|
||||
#fullscreen {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
background: #000000;
|
||||
color: hotpink;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
touch-action: none;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.air-container {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.touch-container {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.grow > * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.key {
|
||||
flex: 1;
|
||||
border: 1px solid green;
|
||||
}
|
||||
|
||||
.key[data-active] {
|
||||
background-color: hotpink;
|
||||
}
|
||||
|
||||
.key.air[data-active] {
|
||||
background-color: skyblue;
|
||||
}
|
||||
|
||||
canvas {
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
touch-action: none;
|
||||
margin: 0px -1.5625vw;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="fullscreen">
|
||||
<!-- Offset for LED display -->
|
||||
<div class="container">
|
||||
<div class="air-container grow"></div>
|
||||
<div class="touch-container grow">
|
||||
<canvas id="canvas" width="33" height="1"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hitbox Divs -->
|
||||
<div class="container" id="main">
|
||||
<div class="air-container grow">
|
||||
<div class="air key" data-air="1" data-kflag="5"></div>
|
||||
<div class="air key" data-air="1" data-kflag="4"></div>
|
||||
<div class="air key" data-air="1" data-kflag="3"></div>
|
||||
<div class="air key" data-air="1" data-kflag="2"></div>
|
||||
<div class="air key" data-air="1" data-kflag="1"></div>
|
||||
<div class="air key" data-air="1" data-kflag="0"></div>
|
||||
</div>
|
||||
<div class="touch-container grow">
|
||||
<div class="key" data-kflag="0"></div>
|
||||
<div class="key" data-kflag="2"></div>
|
||||
<div class="key" data-kflag="4"></div>
|
||||
<div class="key" data-kflag="6"></div>
|
||||
<div class="key" data-kflag="8"></div>
|
||||
<div class="key" data-kflag="10"></div>
|
||||
<div class="key" data-kflag="12"></div>
|
||||
<div class="key" data-kflag="14"></div>
|
||||
<div class="key" data-kflag="16"></div>
|
||||
<div class="key" data-kflag="18"></div>
|
||||
<div class="key" data-kflag="20"></div>
|
||||
<div class="key" data-kflag="22"></div>
|
||||
<div class="key" data-kflag="24"></div>
|
||||
<div class="key" data-kflag="26"></div>
|
||||
<div class="key" data-kflag="28"></div>
|
||||
<div class="key" data-kflag="30"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/config.js"></script>
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,347 +0,0 @@
|
||||
/*
|
||||
Post-process with https://babeljs.io/repl and https://javascript-minifier.com/
|
||||
*/
|
||||
|
||||
const throttle = (func, wait) => {
|
||||
var ready = true;
|
||||
var args = null;
|
||||
return function throttled() {
|
||||
var context = this;
|
||||
if (ready) {
|
||||
ready = false;
|
||||
setTimeout(function () {
|
||||
ready = true;
|
||||
if (args) {
|
||||
throttled.apply(context);
|
||||
}
|
||||
}, wait);
|
||||
if (args) {
|
||||
func.apply(this, args);
|
||||
args = null;
|
||||
} else {
|
||||
func.apply(this, arguments);
|
||||
}
|
||||
} else {
|
||||
args = arguments;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Element refs
|
||||
var keys = document.getElementsByClassName("key");
|
||||
var airKeys = [];
|
||||
var midline = 0;
|
||||
var touchKeys = [];
|
||||
var allKeys = [];
|
||||
var topKeys = airKeys;
|
||||
var bottomKeys = touchKeys;
|
||||
const compileKey = (key) => {
|
||||
const prev = key.previousElementSibling;
|
||||
const next = key.nextElementSibling;
|
||||
return {
|
||||
top: key.offsetTop,
|
||||
bottom: key.offsetTop + key.offsetHeight,
|
||||
left: key.offsetLeft,
|
||||
right: key.offsetLeft + key.offsetWidth,
|
||||
almostLeft: !!prev ? key.offsetLeft + key.offsetWidth / 4 : -99999,
|
||||
almostRight: !!next ? key.offsetLeft + (key.offsetWidth * 3) / 4 : 99999,
|
||||
kflag: parseInt(key.dataset.kflag) + (parseInt(key.dataset.air) ? 32 : 0),
|
||||
isAir: parseInt(key.dataset.air) ? true : false,
|
||||
prevKeyRef: prev,
|
||||
prevKeyKflag: prev
|
||||
? parseInt(prev.dataset.kflag) + (parseInt(prev.dataset.air) ? 32 : 0)
|
||||
: null,
|
||||
nextKeyRef: next,
|
||||
nextKeyKflag: next
|
||||
? parseInt(next.dataset.kflag) + (parseInt(next.dataset.air) ? 32 : 0)
|
||||
: null,
|
||||
ref: key,
|
||||
};
|
||||
};
|
||||
const isInside = (x, y, compiledKey) => {
|
||||
return (
|
||||
compiledKey.left <= x &&
|
||||
x < compiledKey.right &&
|
||||
compiledKey.top <= y &&
|
||||
y < compiledKey.bottom
|
||||
);
|
||||
};
|
||||
const compileKeys = () => {
|
||||
keys = document.getElementsByClassName("key");
|
||||
airKeys = [];
|
||||
touchKeys = [];
|
||||
for (var i = 0, key; i < keys.length; i++) {
|
||||
const compiledKey = compileKey(keys[i]);
|
||||
if (!compiledKey.isAir) {
|
||||
touchKeys.push(compiledKey);
|
||||
} else {
|
||||
airKeys.push(compiledKey);
|
||||
}
|
||||
allKeys.push(compiledKey);
|
||||
}
|
||||
|
||||
if (!config.invert) {
|
||||
// Not inverted
|
||||
topKeys = airKeys;
|
||||
bottomKeys = touchKeys;
|
||||
midline = touchKeys[0].top;
|
||||
} else {
|
||||
// Inverted
|
||||
topKeys = touchKeys;
|
||||
bottomKeys = airKeys;
|
||||
midline = touchKeys[0].bottom;
|
||||
}
|
||||
};
|
||||
|
||||
const getKey = (x, y) => {
|
||||
if (y < midline) {
|
||||
for (var i = 0; i < topKeys.length; i++) {
|
||||
if (isInside(x, y, topKeys[i])) return topKeys[i];
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < bottomKeys.length; i++) {
|
||||
if (isInside(x, y, bottomKeys[i])) {
|
||||
return bottomKeys[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Button State
|
||||
// prettier-ignore
|
||||
var lastState = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
function updateTouches(e) {
|
||||
try {
|
||||
e.preventDefault();
|
||||
|
||||
// prettier-ignore
|
||||
var keyFlags = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
];
|
||||
|
||||
throttledRequestFullscreen();
|
||||
|
||||
for (var i = 0; i < e.touches.length; i++) {
|
||||
const touch = e.touches[i];
|
||||
|
||||
const x = touch.clientX;
|
||||
const y = touch.clientY;
|
||||
|
||||
const key = getKey(x, y);
|
||||
|
||||
if (!key) continue;
|
||||
|
||||
setKey(keyFlags, key.kflag, key.isAir);
|
||||
|
||||
if (key.isAir) continue;
|
||||
|
||||
if (x < key.almostLeft) {
|
||||
setKey(keyFlags, key.prevKeyKflag, false);
|
||||
}
|
||||
|
||||
if (key.almostRight < x) {
|
||||
setKey(keyFlags, key.nextKeyKflag, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Render keys
|
||||
for (var i = 0; i < allKeys.length; i++) {
|
||||
const key = allKeys[i];
|
||||
const kflag = key.kflag;
|
||||
if (keyFlags[kflag] !== lastState[kflag]) {
|
||||
if (keyFlags[kflag]) {
|
||||
key.ref.setAttribute("data-active", "");
|
||||
} else {
|
||||
key.ref.removeAttribute("data-active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (keyFlags !== lastState) {
|
||||
throttledSendKeys(keyFlags);
|
||||
}
|
||||
lastState = keyFlags;
|
||||
} catch (err) {
|
||||
alert(err);
|
||||
}
|
||||
}
|
||||
const throttledUpdateTouches = throttle(updateTouches, 10);
|
||||
|
||||
const setKey = (keyFlags, kflag, isAir) => {
|
||||
var idx = kflag;
|
||||
if (keyFlags[idx] && !isAir) {
|
||||
idx++;
|
||||
}
|
||||
keyFlags[idx] = 1;
|
||||
};
|
||||
|
||||
const sendKeys = (keyFlags) => {
|
||||
if (wsConnected) {
|
||||
ws.send("b" + keyFlags.join(""));
|
||||
}
|
||||
};
|
||||
const throttledSendKeys = throttle(sendKeys, 10);
|
||||
|
||||
// Websockets
|
||||
var ws = null;
|
||||
var wsTimeout = 0;
|
||||
var wsConnected = false;
|
||||
const wsConnect = () => {
|
||||
ws = new WebSocket("ws://" + location.host + "/ws");
|
||||
ws.binaryType = "arraybuffer";
|
||||
ws.onopen = () => {
|
||||
ws.send("alive?");
|
||||
};
|
||||
ws.onmessage = (e) => {
|
||||
if (e.data.byteLength) {
|
||||
updateLed(e.data);
|
||||
} else if (e.data == "alive") {
|
||||
wsTimeout = 0;
|
||||
wsConnected = true;
|
||||
}
|
||||
};
|
||||
};
|
||||
const wsWatch = () => {
|
||||
if (wsTimeout++ > 2) {
|
||||
wsTimeout = 0;
|
||||
ws.close();
|
||||
wsConnected = false;
|
||||
wsConnect();
|
||||
return;
|
||||
}
|
||||
if (wsConnected) {
|
||||
ws.send("alive?");
|
||||
}
|
||||
};
|
||||
|
||||
// Canvas vars
|
||||
var canvas = document.getElementById("canvas");
|
||||
var canvasCtx = canvas.getContext("2d");
|
||||
var canvasData = canvasCtx.getImageData(0, 0, 33, 1);
|
||||
const setupLed = () => {
|
||||
for (var i = 0; i < 33; i++) {
|
||||
canvasData.data[i * 4 + 3] = 255;
|
||||
}
|
||||
};
|
||||
setupLed();
|
||||
const updateLed = (data) => {
|
||||
const buf = new Uint8Array(data);
|
||||
for (var i = 0; i < 31; i++) {
|
||||
canvasData.data[i * 4 + 4] = buf[i * 3]; // r
|
||||
canvasData.data[i * 4 + 5] = buf[i * 3 + 1]; // g
|
||||
canvasData.data[i * 4 + 6] = buf[i * 3 + 2]; // b
|
||||
}
|
||||
canvasData.data[0] = buf[0]
|
||||
canvasData.data[1] = buf[1]
|
||||
canvasData.data[2] = buf[2]
|
||||
canvasData.data[128] = buf[90];
|
||||
canvasData.data[129] = buf[91];
|
||||
canvasData.data[130] = buf[92];
|
||||
|
||||
canvasCtx.putImageData(canvasData, 0, 0);
|
||||
};
|
||||
|
||||
// Fullscreener
|
||||
const fs = document.getElementById("fullscreen");
|
||||
const requestFullscreen = () => {
|
||||
if (!document.fullscreenElement && screen.height <= 1024) {
|
||||
if (fs.requestFullscreen) {
|
||||
fs.requestFullscreen();
|
||||
} else if (fs.mozRequestFullScreen) {
|
||||
fs.mozRequestFullScreen();
|
||||
} else if (fs.webkitRequestFullScreen) {
|
||||
fs.webkitRequestFullScreen();
|
||||
}
|
||||
}
|
||||
};
|
||||
const throttledRequestFullscreen = throttle(requestFullscreen, 3000);
|
||||
|
||||
// Do update hooks
|
||||
const cnt = document.getElementById("main");
|
||||
|
||||
cnt.addEventListener("touchstart", updateTouches);
|
||||
cnt.addEventListener("touchmove", updateTouches);
|
||||
cnt.addEventListener("touchend", updateTouches);
|
||||
|
||||
// cnt.addEventListener("touchstart", throttledUpdateTouches);
|
||||
// cnt.addEventListener("touchmove", throttledUpdateTouches);
|
||||
// cnt.addEventListener("touchend", throttledUpdateTouches);
|
||||
|
||||
// Load config
|
||||
const readConfig = (config) => {
|
||||
var style = "";
|
||||
|
||||
if (!!config.invert) {
|
||||
style += `.container, .air-container {flex-flow: column-reverse nowrap;} `;
|
||||
}
|
||||
|
||||
var bgColor = config.bgColor || "rbga(0, 0, 0, 0.9)";
|
||||
if (!config.bgImage) {
|
||||
style += `#fullscreen {background: ${bgColor};} `;
|
||||
} else {
|
||||
style += `#fullscreen {background: ${bgColor} url("${config.bgImage}") fixed center / cover!important; background-repeat: no-repeat;} `;
|
||||
}
|
||||
|
||||
if (typeof config.ledOpacity === "number") {
|
||||
if (config.ledOpacity === 0) {
|
||||
style += `#canvas {display: none} `;
|
||||
} else {
|
||||
style += `#canvas {opacity: ${config.ledOpacity}} `;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof config.keyColor === "string") {
|
||||
style += `.key[data-active] {background-color: ${config.keyColor};} `;
|
||||
}
|
||||
if (typeof config.keyColor === "string") {
|
||||
style += `.key.air[data-active] {background-color: ${config.lkeyColor};} `;
|
||||
}
|
||||
if (typeof config.keyBorderColor === "string") {
|
||||
style += `.key {border: 1px solid ${config.keyBorderColor};} `;
|
||||
}
|
||||
if (!!config.keyColorFade && typeof config.keyColorFade === "number") {
|
||||
style += `.key:not([data-active]) {transition: background ${config.keyColorFade}ms ease-out;} `;
|
||||
}
|
||||
|
||||
if (typeof config.keyHeight === "number") {
|
||||
if (config.keyHeight === 0) {
|
||||
style += `.touch-container {display: none;} `;
|
||||
} else {
|
||||
style += `.touch-container {flex: ${config.keyHeight};} `;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof config.lkeyHeight === "number") {
|
||||
if (config.lkeyHeight === 0) {
|
||||
style += `.air-container {display: none;} `;
|
||||
} else {
|
||||
style += `.air-container {flex: ${config.keyHeight};} `;
|
||||
}
|
||||
}
|
||||
|
||||
var styleRef = document.createElement("style");
|
||||
styleRef.innerHTML = style;
|
||||
document.head.appendChild(styleRef);
|
||||
};
|
||||
|
||||
// Initialize
|
||||
const initialize = () => {
|
||||
readConfig(config);
|
||||
compileKeys();
|
||||
wsConnect();
|
||||
setInterval(wsWatch, 1000);
|
||||
};
|
||||
initialize();
|
||||
|
||||
// Update keys on resize
|
||||
window.onresize = compileKeys;
|
||||
@@ -1,306 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use hyper::{
|
||||
header,
|
||||
server::conn::AddrStream,
|
||||
service::{make_service_fn, service_fn},
|
||||
upgrade::{self, Upgraded},
|
||||
Body, Method, Request, Response, Server, StatusCode,
|
||||
};
|
||||
use log::{error, info};
|
||||
use phf::phf_map;
|
||||
use std::{convert::Infallible, future::Future, net::SocketAddr};
|
||||
use tokio::{
|
||||
select,
|
||||
sync::mpsc,
|
||||
time::{sleep, Duration},
|
||||
};
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use tungstenite::{handshake, Message};
|
||||
|
||||
use crate::slider_io::{controller_state::FullState, worker::AsyncHaltableJob};
|
||||
|
||||
// https://levelup.gitconnected.com/handling-websocket-and-http-on-the-same-port-with-rust-f65b770722c9
|
||||
|
||||
async fn error_response() -> Result<Response<Body>, Infallible> {
|
||||
Ok(
|
||||
Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Body::from(format!("Not found")))
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
// static x: &'static [u8] = include_bytes!("./brokenithm-www/favicon.ico");
|
||||
|
||||
static BROKENITHM_STR_FILES: phf::Map<&'static str, (&'static str, &'static str)> = phf_map! {
|
||||
"app.js" => (include_str!("./brokenithm-www/app.js"), "text/javascript"),
|
||||
"config.js" => (include_str!("./brokenithm-www/config.js"), "text/javascript"),
|
||||
"index-go.html" => (include_str!("./brokenithm-www/index-go.html"), "text/html"),
|
||||
"index.html" => (include_str!("./brokenithm-www/index.html"), "text/html"),
|
||||
};
|
||||
|
||||
static BROKENITHM_BIN_FILES: phf::Map<&'static str, (&'static [u8], &'static str)> = phf_map! {
|
||||
"favicon.ico" => (include_bytes!("./brokenithm-www/favicon.ico"), "image/x-icon"),
|
||||
"icon.png" => (include_bytes!("./brokenithm-www/icon.png"), "image/png"),
|
||||
};
|
||||
|
||||
async fn serve_file(path: &str) -> Result<Response<Body>, Infallible> {
|
||||
match (
|
||||
BROKENITHM_STR_FILES.get(path),
|
||||
BROKENITHM_BIN_FILES.get(path),
|
||||
) {
|
||||
(Some((data, mime)), _) => Ok(
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, *mime)
|
||||
.body(Body::from(*data))
|
||||
.unwrap(),
|
||||
),
|
||||
(_, Some((data, mime))) => Ok(
|
||||
Response::builder()
|
||||
.header(header::CONTENT_TYPE, *mime)
|
||||
.body(Body::from(*data))
|
||||
.unwrap(),
|
||||
),
|
||||
(None, None) => error_response().await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_brokenithm(
|
||||
ws_stream: WebSocketStream<Upgraded>,
|
||||
state: FullState,
|
||||
led_enabled: bool,
|
||||
) {
|
||||
let (mut ws_write, mut ws_read) = ws_stream.split();
|
||||
|
||||
let (msg_write, mut msg_read) = mpsc::unbounded_channel::<Message>();
|
||||
|
||||
let write_task = async move {
|
||||
// info!("Websocket write task open");
|
||||
loop {
|
||||
match msg_read.recv().await {
|
||||
Some(msg) => match ws_write.send(msg).await.ok() {
|
||||
Some(_) => {}
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// info!("Websocket write task done");
|
||||
};
|
||||
|
||||
let msg_write_handle = msg_write.clone();
|
||||
let state_handle = state.clone();
|
||||
let read_task = async move {
|
||||
// info!("Websocket read task open");
|
||||
loop {
|
||||
match ws_read.next().await {
|
||||
Some(msg) => match msg {
|
||||
Ok(msg) => match msg {
|
||||
Message::Text(msg) => {
|
||||
let chars = msg.chars().collect::<Vec<char>>();
|
||||
|
||||
match chars.len() {
|
||||
6 => {
|
||||
if chars[0] == 'a' {
|
||||
msg_write_handle
|
||||
.send(Message::Text("alive".to_string()))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
39 => {
|
||||
if chars[0] == 'b' {
|
||||
let mut controller_state_handle = state_handle.controller_state.lock();
|
||||
for (idx, c) in chars[0..32].iter().enumerate() {
|
||||
controller_state_handle.ground_state[idx] = match *c == '1' {
|
||||
false => 0,
|
||||
true => 255,
|
||||
}
|
||||
}
|
||||
for (idx, c) in chars[32..38].iter().enumerate() {
|
||||
controller_state_handle.air_state[idx] = match *c == '1' {
|
||||
false => 0,
|
||||
true => 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Close(_) => {
|
||||
info!("Websocket connection closed");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Websocket connection error: {}", e);
|
||||
break;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// info!("Websocket read task done");
|
||||
};
|
||||
|
||||
match led_enabled {
|
||||
false => {
|
||||
select! {
|
||||
_ = read_task => {}
|
||||
_ = write_task => {}
|
||||
};
|
||||
}
|
||||
true => {
|
||||
let msg_write_handle = msg_write.clone();
|
||||
let state_handle = state.clone();
|
||||
let led_task = async move {
|
||||
loop {
|
||||
let mut led_data = vec![0; 93];
|
||||
{
|
||||
let led_state_handle = state_handle.led_state.lock();
|
||||
(&mut led_data).copy_from_slice(&led_state_handle.led_state);
|
||||
}
|
||||
msg_write_handle.send(Message::Binary(led_data)).ok();
|
||||
|
||||
sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
};
|
||||
|
||||
select! {
|
||||
_ = read_task => {}
|
||||
_ = write_task => {}
|
||||
_ = led_task => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_websocket(
|
||||
mut request: Request<Body>,
|
||||
state: FullState,
|
||||
led_enabled: bool,
|
||||
) -> Result<Response<Body>, Infallible> {
|
||||
let res = match handshake::server::create_response_with_body(&request, || Body::empty()) {
|
||||
Ok(res) => {
|
||||
tokio::spawn(async move {
|
||||
match upgrade::on(&mut request).await {
|
||||
Ok(upgraded) => {
|
||||
let ws_stream = WebSocketStream::from_raw_socket(
|
||||
upgraded,
|
||||
tokio_tungstenite::tungstenite::protocol::Role::Server,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
handle_brokenithm(ws_stream, state, led_enabled).await;
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
error!("Websocket upgrade error: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
res
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Websocket creation error: {}", e);
|
||||
Response::builder()
|
||||
.status(StatusCode::BAD_REQUEST)
|
||||
.body(Body::from(format!("Failed to create websocket: {}", e)))
|
||||
.unwrap()
|
||||
}
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn handle_request(
|
||||
request: Request<Body>,
|
||||
remote_addr: SocketAddr,
|
||||
state: FullState,
|
||||
ground_only: bool,
|
||||
led_enabled: bool,
|
||||
) -> Result<Response<Body>, Infallible> {
|
||||
let method = request.method();
|
||||
let path = request.uri().path();
|
||||
if method != Method::GET {
|
||||
error!(
|
||||
"Server unknown method {} -> {} {}",
|
||||
remote_addr, method, path
|
||||
);
|
||||
return error_response().await;
|
||||
}
|
||||
info!("Server {} -> {} {}", remote_addr, method, path);
|
||||
|
||||
match (
|
||||
request.uri().path(),
|
||||
request.headers().contains_key(header::UPGRADE),
|
||||
) {
|
||||
("/", false) | ("/index.html", false) => match ground_only {
|
||||
false => serve_file("index.html").await,
|
||||
true => serve_file("index-go.html").await,
|
||||
},
|
||||
(filename, false) => serve_file(&filename[1..]).await,
|
||||
("/ws", true) => handle_websocket(request, state, led_enabled).await,
|
||||
_ => error_response().await,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BrokenithmJob {
|
||||
state: FullState,
|
||||
ground_only: bool,
|
||||
led_enabled: bool,
|
||||
}
|
||||
|
||||
impl BrokenithmJob {
|
||||
pub fn new(state: &FullState, ground_only: &bool, led_enabled: &bool) -> Self {
|
||||
Self {
|
||||
state: state.clone(),
|
||||
ground_only: *ground_only,
|
||||
led_enabled: *led_enabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AsyncHaltableJob for BrokenithmJob {
|
||||
async fn run<F: Future<Output = ()> + Send>(self, stop_signal: F) {
|
||||
let state = self.state.clone();
|
||||
let ground_only = self.ground_only;
|
||||
let led_enabled = self.led_enabled;
|
||||
let make_svc = make_service_fn(|conn: &AddrStream| {
|
||||
let remote_addr = conn.remote_addr();
|
||||
let make_svc_state = state.clone();
|
||||
async move {
|
||||
Ok::<_, Infallible>(service_fn(move |request: Request<Body>| {
|
||||
let svc_state = make_svc_state.clone();
|
||||
handle_request(request, remote_addr, svc_state, ground_only, led_enabled)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 1606));
|
||||
info!("Brokenithm server listening on {}", addr);
|
||||
|
||||
let server = Server::bind(&addr)
|
||||
// .http1_keepalive(false)
|
||||
// .http2_keep_alive_interval(None)
|
||||
// .tcp_keepalive(None)
|
||||
.serve(make_svc)
|
||||
.with_graceful_shutdown(stop_signal);
|
||||
|
||||
if let Err(e) = server.await {
|
||||
info!("Brokenithm server stopped: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,323 +0,0 @@
|
||||
use directories::ProjectDirs;
|
||||
use image::Luma;
|
||||
use log::{info, warn};
|
||||
use qrcode::QrCode;
|
||||
use serde_json::Value;
|
||||
use std::{convert::TryFrom, fs, path::PathBuf};
|
||||
|
||||
use crate::slider_io::utils::list_ips;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HardwareSpec {
|
||||
TasollerOne,
|
||||
TasollerTwo,
|
||||
Yuancon,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DeviceMode {
|
||||
None,
|
||||
Hardware {
|
||||
spec: HardwareSpec,
|
||||
},
|
||||
Brokenithm {
|
||||
ground_only: bool,
|
||||
led_enabled: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum PollingRate {
|
||||
Sixty,
|
||||
Hundred,
|
||||
TwoHundredFifty,
|
||||
FiveHundred,
|
||||
Thousand,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum KeyboardLayout {
|
||||
Tasoller,
|
||||
Yuancon,
|
||||
Deemo,
|
||||
Voltex,
|
||||
Neardayo,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum GamepadLayout {
|
||||
Voltex,
|
||||
Neardayo,
|
||||
}
|
||||
|
||||
impl PollingRate {
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"60" => Some(PollingRate::Sixty),
|
||||
"100" => Some(PollingRate::Hundred),
|
||||
"250" => Some(PollingRate::TwoHundredFifty),
|
||||
"500" => Some(PollingRate::FiveHundred),
|
||||
"1000" => Some(PollingRate::Thousand),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_t_u64(&self) -> u64 {
|
||||
match self {
|
||||
PollingRate::Sixty => 16666,
|
||||
PollingRate::Hundred => 10000,
|
||||
PollingRate::TwoHundredFifty => 4000,
|
||||
PollingRate::FiveHundred => 2000,
|
||||
PollingRate::Thousand => 1000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum OutputMode {
|
||||
None,
|
||||
Keyboard {
|
||||
layout: KeyboardLayout,
|
||||
polling: PollingRate,
|
||||
sensitivity: u8,
|
||||
},
|
||||
Gamepad {
|
||||
layout: GamepadLayout,
|
||||
polling: PollingRate,
|
||||
sensitivity: u8,
|
||||
},
|
||||
Websocket {
|
||||
url: String,
|
||||
polling: PollingRate,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ReactiveLayout {
|
||||
Even { splits: usize },
|
||||
Voltex,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LedMode {
|
||||
None,
|
||||
Reactive {
|
||||
layout: ReactiveLayout,
|
||||
sensitivity: u8,
|
||||
},
|
||||
Attract,
|
||||
Test,
|
||||
Websocket {
|
||||
url: String,
|
||||
},
|
||||
Serial {
|
||||
port: 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) -> Option<Config> {
|
||||
let v: Value = serde_json::from_str(s).ok()?;
|
||||
|
||||
Some(Config {
|
||||
raw: s.to_string(),
|
||||
device_mode: match v["deviceMode"].as_str()? {
|
||||
"none" => DeviceMode::None,
|
||||
"tasoller-one" => DeviceMode::Hardware {
|
||||
spec: HardwareSpec::TasollerOne,
|
||||
},
|
||||
"tasoller-two" => DeviceMode::Hardware {
|
||||
spec: HardwareSpec::TasollerTwo,
|
||||
},
|
||||
"yuancon" => DeviceMode::Hardware {
|
||||
spec: HardwareSpec::Yuancon,
|
||||
},
|
||||
"brokenithm" => DeviceMode::Brokenithm {
|
||||
ground_only: false,
|
||||
led_enabled: false,
|
||||
},
|
||||
"brokenithm-led" => DeviceMode::Brokenithm {
|
||||
ground_only: false,
|
||||
led_enabled: true,
|
||||
},
|
||||
"brokenithm-ground" => DeviceMode::Brokenithm {
|
||||
ground_only: true,
|
||||
led_enabled: false,
|
||||
},
|
||||
"brokenithm-ground-led" => DeviceMode::Brokenithm {
|
||||
ground_only: true,
|
||||
led_enabled: true,
|
||||
},
|
||||
_ => panic!("Invalid device mode"),
|
||||
},
|
||||
output_mode: match v["outputMode"].as_str().unwrap() {
|
||||
"none" => OutputMode::None,
|
||||
"kb-32-tasoller" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::Tasoller,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"kb-32-yuancon" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::Yuancon,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"kb-8-deemo" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::Deemo,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"kb-voltex" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::Voltex,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"kb-neardayo" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::Neardayo,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"gamepad-voltex" => OutputMode::Gamepad {
|
||||
layout: GamepadLayout::Voltex,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"gamepad-neardayo" => OutputMode::Gamepad {
|
||||
layout: GamepadLayout::Neardayo,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"websocket" => OutputMode::Websocket {
|
||||
url: v["outputWebsocketUrl"].as_str()?.to_string(),
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
},
|
||||
_ => panic!("Invalid output mode"),
|
||||
},
|
||||
led_mode: match v["ledMode"].as_str()? {
|
||||
"none" => LedMode::None,
|
||||
"reactive-4" => LedMode::Reactive {
|
||||
layout: ReactiveLayout::Even { splits: 4 },
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"reactive-8" => LedMode::Reactive {
|
||||
layout: ReactiveLayout::Even { splits: 8 },
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"reactive-16" => LedMode::Reactive {
|
||||
layout: ReactiveLayout::Even { splits: 16 },
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"reactive-voltex" => LedMode::Reactive {
|
||||
layout: ReactiveLayout::Voltex,
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"attract" => LedMode::Attract,
|
||||
"test" => LedMode::Test,
|
||||
"websocket" => LedMode::Websocket {
|
||||
url: v["ledWebsocketUrl"].as_str()?.to_string(),
|
||||
},
|
||||
"serial" => LedMode::Serial {
|
||||
port: v["ledSerialPort"].as_str()?.to_string(),
|
||||
},
|
||||
_ => panic!("Invalid led mode"),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_log_file_path() -> Option<Box<PathBuf>> {
|
||||
let project_dir = ProjectDirs::from("me", "impress labs", "slidershim").unwrap();
|
||||
let config_dir = project_dir.config_dir();
|
||||
fs::create_dir_all(config_dir).ok()?;
|
||||
|
||||
let log_path = config_dir.join("log.txt");
|
||||
|
||||
return Some(Box::new(log_path));
|
||||
}
|
||||
|
||||
pub fn get_brokenithm_qr_path() -> Option<Box<PathBuf>> {
|
||||
let project_dir = ProjectDirs::from("me", "impress labs", "slidershim").unwrap();
|
||||
let config_dir = project_dir.config_dir();
|
||||
fs::create_dir_all(config_dir).ok()?;
|
||||
|
||||
let brokenithm_qr_path = config_dir.join("brokenithm.png");
|
||||
|
||||
let ips = list_ips().ok()?;
|
||||
let link = "http://imp.ress.me/t/sshelper?d=".to_string()
|
||||
+ &ips
|
||||
.into_iter()
|
||||
.filter(|s| s.as_str().chars().filter(|x| *x == '.').count() == 3)
|
||||
.map(|s| base64::encode_config(s, base64::URL_SAFE_NO_PAD))
|
||||
.collect::<Vec<String>>()
|
||||
.join(";");
|
||||
let qr = QrCode::new(link).ok()?;
|
||||
let image = qr.render::<Luma<u8>>().build();
|
||||
image.save(brokenithm_qr_path.as_path()).ok()?;
|
||||
|
||||
return Some(Box::new(brokenithm_qr_path));
|
||||
}
|
||||
|
||||
fn get_config_path() -> Option<Box<PathBuf>> {
|
||||
let project_dir = ProjectDirs::from("me", "impress labs", "slidershim").unwrap();
|
||||
let config_dir = project_dir.config_dir();
|
||||
fs::create_dir_all(config_dir).ok()?;
|
||||
|
||||
let config_path = config_dir.join("config.json");
|
||||
|
||||
return Some(Box::new(config_path));
|
||||
}
|
||||
|
||||
fn default() -> Self {
|
||||
Self::from_str(
|
||||
r#"{
|
||||
"deviceMode": "none",
|
||||
"devicePolling": "100",
|
||||
"outputMode": "none",
|
||||
"ledMode": "none",
|
||||
"keyboardSensitivity": 20,
|
||||
"outputWebsocketUrl": "localhost:3000",
|
||||
"outputPolling": "100",
|
||||
"ledSensitivity": 20,
|
||||
"ledWebsocketUrl": "localhost:3001",
|
||||
"ledSerialPort": "COM5"
|
||||
}"#,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn load_saved() -> Option<Self> {
|
||||
let config_path = Self::get_config_path()?;
|
||||
if !config_path.exists() {
|
||||
return None;
|
||||
}
|
||||
info!("Config file found at {:?}", config_path);
|
||||
let saved_data = fs::read_to_string(config_path.as_path()).ok()?;
|
||||
return Self::from_str(saved_data.as_str());
|
||||
}
|
||||
|
||||
pub fn load() -> Self {
|
||||
Self::load_saved()
|
||||
.or_else(|| {
|
||||
warn!("Config loading from file failed, using default");
|
||||
Some(Self::default())
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Option<()> {
|
||||
info!("Config saving...");
|
||||
let config_path = Self::get_config_path()?;
|
||||
info!("Config saving to {:?}", config_path);
|
||||
fs::write(config_path.as_path(), self.raw.as_str()).unwrap();
|
||||
info!("Config saved");
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
use atomic_float::AtomicF64;
|
||||
use log::info;
|
||||
use std::sync::{atomic::Ordering, Arc};
|
||||
|
||||
use crate::slider_io::{
|
||||
brokenithm::BrokenithmJob,
|
||||
config::{Config, DeviceMode, LedMode, OutputMode},
|
||||
controller_state::FullState,
|
||||
device::HidDeviceJob,
|
||||
led::LedJob,
|
||||
output::OutputJob,
|
||||
utils::LoopTimer,
|
||||
worker::{AsyncHaltableWorker, AsyncWorker, ThreadWorker},
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Context {
|
||||
state: FullState,
|
||||
config: Config,
|
||||
device_worker: Option<ThreadWorker>,
|
||||
brokenithm_worker: Option<AsyncHaltableWorker>,
|
||||
output_worker: Option<AsyncWorker>,
|
||||
led_worker: Option<AsyncWorker>,
|
||||
timers: Vec<(&'static str, Arc<AtomicF64>)>,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
pub fn new(config: Config) -> Self {
|
||||
info!("Context creating");
|
||||
info!("Device config {:?}", config.device_mode);
|
||||
info!("Output config {:?}", config.output_mode);
|
||||
info!("LED config {:?}", config.led_mode);
|
||||
|
||||
let state = FullState::new();
|
||||
let mut timers = vec![];
|
||||
|
||||
let (device_worker, brokenithm_worker) = match &config.device_mode {
|
||||
DeviceMode::None => (None, None),
|
||||
DeviceMode::Brokenithm {
|
||||
ground_only,
|
||||
led_enabled,
|
||||
} => (
|
||||
None,
|
||||
Some(AsyncHaltableWorker::new(
|
||||
"brokenithm",
|
||||
BrokenithmJob::new(&state, ground_only, led_enabled),
|
||||
)),
|
||||
),
|
||||
DeviceMode::Hardware { spec } => (
|
||||
{
|
||||
let timer = LoopTimer::new();
|
||||
timers.push(("d", timer.fork()));
|
||||
Some(ThreadWorker::new(
|
||||
"device",
|
||||
HidDeviceJob::from_config(&state, spec),
|
||||
timer,
|
||||
))
|
||||
},
|
||||
None,
|
||||
),
|
||||
};
|
||||
let output_worker = match &config.output_mode {
|
||||
OutputMode::None => None,
|
||||
_ => {
|
||||
let timer = LoopTimer::new();
|
||||
timers.push(("o", timer.fork()));
|
||||
Some(AsyncWorker::new(
|
||||
"output",
|
||||
OutputJob::new(&state, &config.output_mode),
|
||||
timer,
|
||||
))
|
||||
}
|
||||
};
|
||||
let led_worker = match &config.led_mode {
|
||||
LedMode::None => None,
|
||||
_ => {
|
||||
let timer = LoopTimer::new();
|
||||
timers.push(("l", timer.fork()));
|
||||
Some(AsyncWorker::new(
|
||||
"led",
|
||||
LedJob::new(&state, &config.led_mode),
|
||||
timer,
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Self {
|
||||
state,
|
||||
config,
|
||||
device_worker,
|
||||
brokenithm_worker,
|
||||
output_worker,
|
||||
led_worker,
|
||||
timers,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_state(&self) -> FullState {
|
||||
self.state.clone()
|
||||
}
|
||||
|
||||
pub fn timer_state(&self) -> String {
|
||||
self
|
||||
.timers
|
||||
.iter()
|
||||
.map(|(s, f)| format!("{}:{:.1}/s", s, f.load(Ordering::SeqCst)))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" ")
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
use parking_lot::Mutex;
|
||||
use std::{sync::Arc, 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 fn flat(&self, sensitivity: &u8) -> Vec<bool> {
|
||||
self
|
||||
.ground_state
|
||||
.iter()
|
||||
.map(|x| x > sensitivity)
|
||||
.chain(
|
||||
self
|
||||
.air_state
|
||||
.iter()
|
||||
.chain(self.extra_state.iter())
|
||||
.map(|x| x > &0),
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn flip_vert(&mut self) {
|
||||
for i in 0..16 {
|
||||
self.ground_state.swap(i * 2, i * 2 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 fn paint(&mut self, idx: usize, color: &[u8; 3]) {
|
||||
self.led_state[3 * idx..3 * (idx + 1)].copy_from_slice(color);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn snapshot(&self) -> Vec<u8> {
|
||||
let mut buf: Vec<u8> = vec![];
|
||||
{
|
||||
let controller_state_handle = self.controller_state.lock();
|
||||
buf.extend(controller_state_handle.ground_state);
|
||||
buf.extend(controller_state_handle.air_state);
|
||||
buf.extend(controller_state_handle.extra_state);
|
||||
};
|
||||
{
|
||||
let led_state_handle = self.led_state.lock();
|
||||
buf.extend(led_state_handle.led_state);
|
||||
};
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for FullState {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
controller_state: self.clone_controller(),
|
||||
led_state: self.clone_led(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,293 +0,0 @@
|
||||
use log::{error, info};
|
||||
use rusb::{self, DeviceHandle, GlobalContext};
|
||||
use std::{
|
||||
error::Error,
|
||||
mem::swap,
|
||||
ops::{Deref, DerefMut},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::slider_io::{
|
||||
config::HardwareSpec,
|
||||
controller_state::{ControllerState, FullState, LedState},
|
||||
utils::{Buffer, ShimError},
|
||||
worker::ThreadJob,
|
||||
};
|
||||
|
||||
type HidReadCallback = fn(&Buffer, &mut ControllerState) -> ();
|
||||
type HidLedCallback = fn(&mut Buffer, &LedState) -> ();
|
||||
|
||||
enum WriteType {
|
||||
Bulk,
|
||||
Interrupt,
|
||||
}
|
||||
|
||||
pub struct HidDeviceJob {
|
||||
state: FullState,
|
||||
|
||||
vid: u16,
|
||||
pid: u16,
|
||||
read_endpoint: u8,
|
||||
led_endpoint: u8,
|
||||
|
||||
read_callback: HidReadCallback,
|
||||
read_buf: Buffer,
|
||||
last_read_buf: Buffer,
|
||||
|
||||
led_write_type: WriteType,
|
||||
led_callback: HidLedCallback,
|
||||
led_buf: Buffer,
|
||||
|
||||
handle: Option<DeviceHandle<GlobalContext>>,
|
||||
}
|
||||
|
||||
impl HidDeviceJob {
|
||||
fn new(
|
||||
state: FullState,
|
||||
vid: u16,
|
||||
pid: u16,
|
||||
read_endpoint: u8,
|
||||
led_endpoint: u8,
|
||||
read_callback: HidReadCallback,
|
||||
led_type: WriteType,
|
||||
led_callback: HidLedCallback,
|
||||
) -> Self {
|
||||
Self {
|
||||
state,
|
||||
vid,
|
||||
pid,
|
||||
read_endpoint,
|
||||
led_endpoint,
|
||||
read_callback,
|
||||
read_buf: Buffer::new(),
|
||||
last_read_buf: Buffer::new(),
|
||||
led_write_type: led_type,
|
||||
led_callback,
|
||||
led_buf: Buffer::new(),
|
||||
handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_config(state: &FullState, spec: &HardwareSpec) -> Self {
|
||||
match spec {
|
||||
HardwareSpec::TasollerOne => Self::new(
|
||||
state.clone(),
|
||||
0x1ccf,
|
||||
0x2333,
|
||||
0x84,
|
||||
0x03,
|
||||
|buf, controller_state| {
|
||||
if buf.len != 11 {
|
||||
return;
|
||||
}
|
||||
|
||||
let bits: Vec<u8> = buf
|
||||
.data
|
||||
.iter()
|
||||
.flat_map(|x| (0..8).map(move |i| ((x) >> i) & 1))
|
||||
.collect();
|
||||
for i in 0..32 {
|
||||
controller_state.ground_state[i] = bits[34 + i] * 255;
|
||||
}
|
||||
controller_state.flip_vert();
|
||||
|
||||
controller_state.air_state.copy_from_slice(&bits[28..34]);
|
||||
controller_state.extra_state[0..2].copy_from_slice(&bits[26..28]);
|
||||
},
|
||||
WriteType::Bulk,
|
||||
|buf, led_state| {
|
||||
buf.len = 240;
|
||||
buf.data[0] = 'B' as u8;
|
||||
buf.data[1] = 'L' as u8;
|
||||
buf.data[2] = '\x00' as u8;
|
||||
for (buf_chunk, state_chunk) in buf.data[3..96]
|
||||
.chunks_mut(3)
|
||||
.take(31)
|
||||
.zip(led_state.led_state.chunks(3).rev())
|
||||
{
|
||||
buf_chunk[0] = state_chunk[1];
|
||||
buf_chunk[1] = state_chunk[0];
|
||||
buf_chunk[2] = state_chunk[2];
|
||||
}
|
||||
buf.data[96..240].fill(0);
|
||||
},
|
||||
),
|
||||
HardwareSpec::TasollerTwo => Self::new(
|
||||
state.clone(),
|
||||
0x1ccf,
|
||||
0x2333,
|
||||
0x84,
|
||||
0x03,
|
||||
|buf, controller_state| {
|
||||
if buf.len != 36 {
|
||||
return;
|
||||
}
|
||||
|
||||
controller_state
|
||||
.ground_state
|
||||
.copy_from_slice(&buf.data[4..36]);
|
||||
controller_state.flip_vert();
|
||||
|
||||
let bits: Vec<u8> = (0..8).map(|x| (buf.data[3] >> x) & 1).collect();
|
||||
controller_state.air_state.copy_from_slice(&bits[0..6]);
|
||||
controller_state.extra_state[0..2].copy_from_slice(&bits[6..8]);
|
||||
},
|
||||
WriteType::Bulk,
|
||||
|buf, led_state| {
|
||||
buf.len = 240;
|
||||
buf.data[0] = 'B' as u8;
|
||||
buf.data[1] = 'L' as u8;
|
||||
buf.data[2] = '\x00' as u8;
|
||||
for (buf_chunk, state_chunk) in buf.data[3..96]
|
||||
.chunks_mut(3)
|
||||
.take(31)
|
||||
.zip(led_state.led_state.chunks(3).rev())
|
||||
{
|
||||
buf_chunk[0] = state_chunk[1];
|
||||
buf_chunk[1] = state_chunk[0];
|
||||
buf_chunk[2] = state_chunk[2];
|
||||
}
|
||||
buf.data[96..240].fill(0);
|
||||
},
|
||||
),
|
||||
HardwareSpec::Yuancon => Self::new(
|
||||
state.clone(),
|
||||
0x1973,
|
||||
0x2001,
|
||||
0x81,
|
||||
0x02,
|
||||
|buf, controller_state| {
|
||||
if buf.len != 34 {
|
||||
return;
|
||||
}
|
||||
|
||||
controller_state
|
||||
.ground_state
|
||||
.copy_from_slice(&buf.data[2..34]);
|
||||
for i in 0..6 {
|
||||
controller_state.air_state[i ^ 1] = (buf.data[0] >> i) & 1;
|
||||
}
|
||||
for i in 0..3 {
|
||||
controller_state.extra_state[2 - i] = (buf.data[1] >> i) & 1;
|
||||
}
|
||||
},
|
||||
WriteType::Interrupt,
|
||||
|buf, led_state| {
|
||||
buf.len = 31 * 2;
|
||||
for (buf_chunk, state_chunk) in buf
|
||||
.data
|
||||
.chunks_mut(2)
|
||||
.take(31)
|
||||
.zip(led_state.led_state.chunks(3).rev())
|
||||
{
|
||||
buf_chunk[0] = (state_chunk[0] << 3 & 0xe0) | (state_chunk[2] >> 3);
|
||||
buf_chunk[1] = (state_chunk[1] & 0xf8) | (state_chunk[0] >> 5);
|
||||
}
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_handle(&mut self) -> Result<(), Box<dyn Error>> {
|
||||
info!("Device finding vid {} pid {}", self.vid, self.pid);
|
||||
let handle = rusb::open_device_with_vid_pid(self.vid, self.pid);
|
||||
if handle.is_none() {
|
||||
error!("Device not found");
|
||||
return Err(Box::new(ShimError));
|
||||
}
|
||||
let mut handle = handle.unwrap();
|
||||
info!("Device found {:?}", handle);
|
||||
|
||||
if handle.kernel_driver_active(0).unwrap_or(false) {
|
||||
info!("Device detaching kernel driver");
|
||||
handle.detach_kernel_driver(0)?;
|
||||
}
|
||||
info!("Device setting configuration");
|
||||
handle.set_active_configuration(1)?;
|
||||
info!("Device claiming interface");
|
||||
handle.claim_interface(0)?;
|
||||
self.handle = Some(handle);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_millis(20);
|
||||
|
||||
impl ThreadJob for HidDeviceJob {
|
||||
fn setup(&mut self) -> bool {
|
||||
match self.get_handle() {
|
||||
Ok(_) => {
|
||||
info!("Device OK");
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Device setup failed: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tick(&mut self) -> bool {
|
||||
// Input loop
|
||||
let handle = self.handle.as_mut().unwrap();
|
||||
let mut work = false;
|
||||
|
||||
{
|
||||
let res = handle
|
||||
.read_interrupt(self.read_endpoint, &mut self.read_buf.data, TIMEOUT)
|
||||
.map_err(|e| {
|
||||
// debug!("Device read error {}", &e);
|
||||
e
|
||||
})
|
||||
.unwrap_or(0);
|
||||
self.read_buf.len = res;
|
||||
// debug!("{:?}", self.read_buf.slice());
|
||||
// if self.read_buf.len != 0 {
|
||||
if (self.read_buf.len != 0) && (self.read_buf.slice() != self.last_read_buf.slice()) {
|
||||
work = true;
|
||||
let mut controller_state_handle = self.state.controller_state.lock();
|
||||
(self.read_callback)(&self.read_buf, controller_state_handle.deref_mut());
|
||||
swap(&mut self.read_buf, &mut self.last_read_buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Led loop
|
||||
{
|
||||
{
|
||||
let mut led_state_handle = self.state.led_state.lock();
|
||||
if led_state_handle.dirty {
|
||||
(self.led_callback)(&mut self.led_buf, led_state_handle.deref());
|
||||
led_state_handle.dirty = false;
|
||||
}
|
||||
}
|
||||
|
||||
if self.led_buf.len != 0 {
|
||||
let res = (match self.led_write_type {
|
||||
WriteType::Bulk => handle.write_bulk(self.led_endpoint, self.led_buf.slice(), TIMEOUT),
|
||||
WriteType::Interrupt => {
|
||||
handle.write_interrupt(self.led_endpoint, &self.led_buf.slice(), TIMEOUT)
|
||||
}
|
||||
})
|
||||
.map_err(|e| {
|
||||
// debug!("Device write error {}", e);
|
||||
e
|
||||
})
|
||||
.unwrap_or(0);
|
||||
if res == self.led_buf.len + 1 {
|
||||
// work = true;
|
||||
self.led_buf.len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
work
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HidDeviceJob {
|
||||
fn drop(&mut self) {
|
||||
if let Some(handle) = self.handle.as_mut() {
|
||||
handle.release_interface(0).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
use log::error;
|
||||
use std::error::Error;
|
||||
use vigem_client::{Client, TargetId, XButtons, XGamepad, Xbox360Wired};
|
||||
|
||||
use crate::slider_io::{config::GamepadLayout, output::OutputHandler, voltex::VoltexState};
|
||||
|
||||
struct LastWind {
|
||||
left: bool,
|
||||
right: bool,
|
||||
out: i16,
|
||||
}
|
||||
|
||||
impl LastWind {
|
||||
fn new() -> Self {
|
||||
LastWind {
|
||||
left: false,
|
||||
right: false,
|
||||
out: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, left: bool, right: bool) -> i16 {
|
||||
let out = match (left, right) {
|
||||
(false, false) => 0,
|
||||
(true, false) => -1,
|
||||
(false, true) => 1,
|
||||
(true, true) => match (self.left, self.right) {
|
||||
(false, false) => 0,
|
||||
(true, false) => 1,
|
||||
(false, true) => -1,
|
||||
(true, true) => self.out,
|
||||
},
|
||||
};
|
||||
|
||||
self.left = left;
|
||||
self.right = right;
|
||||
self.out = out;
|
||||
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GamepadOutput {
|
||||
target: Xbox360Wired<Client>,
|
||||
use_air: bool,
|
||||
gamepad: XGamepad,
|
||||
left_wind: LastWind,
|
||||
right_wind: LastWind,
|
||||
}
|
||||
|
||||
impl GamepadOutput {
|
||||
pub fn new(layout: GamepadLayout) -> Option<Self> {
|
||||
let target = Self::get_target();
|
||||
let use_air = match layout {
|
||||
GamepadLayout::Neardayo => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
match target {
|
||||
Ok(target) => Some(Self {
|
||||
target,
|
||||
use_air,
|
||||
gamepad: XGamepad::default(),
|
||||
left_wind: LastWind::new(),
|
||||
right_wind: LastWind::new(),
|
||||
}),
|
||||
Err(e) => {
|
||||
error!("Gamepad connection error: {}", e);
|
||||
error!("Gamepad connection error: Is ViGEMBus missing?");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_target() -> Result<Xbox360Wired<Client>, Box<dyn Error>> {
|
||||
let client = Client::connect()?;
|
||||
|
||||
let mut target = Xbox360Wired::new(client, TargetId::XBOX360_WIRED);
|
||||
target.plugin()?;
|
||||
target.wait_ready()?;
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
fn update(&mut self) -> bool {
|
||||
match self.target.update(&self.gamepad) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
error!("Gamepad update error: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputHandler for GamepadOutput {
|
||||
fn tick(&mut self, flat_controller_state: &Vec<bool>) -> bool {
|
||||
let voltex_state = VoltexState::from_flat(flat_controller_state);
|
||||
|
||||
let buttons = voltex_state
|
||||
.bt
|
||||
.iter()
|
||||
.chain(voltex_state.fx.iter())
|
||||
.chain(voltex_state.extra.iter())
|
||||
.zip([
|
||||
XButtons::A,
|
||||
XButtons::B,
|
||||
XButtons::X,
|
||||
XButtons::Y,
|
||||
XButtons::LB,
|
||||
XButtons::RB,
|
||||
XButtons::START,
|
||||
XButtons::BACK,
|
||||
XButtons::GUIDE,
|
||||
])
|
||||
.fold(0, |buttons, (state, code)| {
|
||||
buttons
|
||||
| match state {
|
||||
true => code,
|
||||
false => 0,
|
||||
}
|
||||
});
|
||||
|
||||
let lx = self.left_wind.update(
|
||||
voltex_state.laser[0] || (self.use_air && flat_controller_state[32]),
|
||||
voltex_state.laser[1]
|
||||
|| (self.use_air && (flat_controller_state[33] || flat_controller_state[34])),
|
||||
) * 20000;
|
||||
|
||||
let rx = self.right_wind.update(
|
||||
voltex_state.laser[2]
|
||||
|| (self.use_air && (flat_controller_state[35] || flat_controller_state[36])),
|
||||
voltex_state.laser[3] || (self.use_air && flat_controller_state[37]),
|
||||
) * 20000;
|
||||
|
||||
let mut dirty = false;
|
||||
if self.gamepad.buttons.raw != buttons {
|
||||
self.gamepad.buttons.raw = buttons;
|
||||
dirty = true;
|
||||
}
|
||||
if self.gamepad.thumb_lx != lx {
|
||||
self.gamepad.thumb_lx = lx;
|
||||
dirty = true;
|
||||
}
|
||||
if self.gamepad.thumb_rx != rx {
|
||||
self.gamepad.thumb_rx = rx;
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
match dirty {
|
||||
true => self.update(),
|
||||
false => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.gamepad = XGamepad::default();
|
||||
self.update();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GamepadOutput {
|
||||
fn drop(&mut self) {
|
||||
match self.target.unplug() {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("Gamepad unplug error: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dammit vigem_client::Event
|
||||
unsafe impl Send for GamepadOutput {}
|
||||
@@ -1,208 +0,0 @@
|
||||
use std::mem;
|
||||
use winapi::{
|
||||
ctypes::c_int,
|
||||
um::winuser::{SendInput, INPUT, INPUT_KEYBOARD, KEYBDINPUT, KEYEVENTF_KEYUP},
|
||||
};
|
||||
|
||||
use crate::slider_io::{config::KeyboardLayout, output::OutputHandler};
|
||||
|
||||
#[rustfmt::skip]
|
||||
const TASOLLER_KB_MAP: [usize; 41] = [
|
||||
0x41 /* A */, 0x31 /* 1 */, 0x5a /* Z */, 0x51 /* Q */, 0x53 /* S */, 0x32 /* 2 */, 0x58 /* X */, 0x57 /* W */,
|
||||
0x44 /* D */, 0x33 /* 3 */, 0x43 /* C */, 0x45 /* E */, 0x46 /* F */, 0x34 /* 4 */, 0x56 /* V */, 0x52 /* R */,
|
||||
0x47 /* G */, 0x35 /* 5 */, 0x42 /* B */, 0x54 /* T */, 0x48 /* H */, 0x36 /* 6 */, 0x4e /* N */, 0x59 /* Y */,
|
||||
0x4a /* J */, 0x37 /* 7 */, 0x4d /* M */, 0x55 /* U */, 0x4b /* K */, 0x38 /* 8 */, 0xbc /* VK_OEM_COMMA */, 0x49 /* I */,
|
||||
0xbf, 0xde, 0xbe, // VK_OEM_2, VK_OEM_7, VK_OEM_PERIOD,
|
||||
0xba, 0xdd, 0xdb, // VK_OEM_1, VK_OEM_6, VK_OEM_4
|
||||
0x0d, 0x20, 0x1b // VK_RETURN, VK_SPACE, VK_ESCAPE
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
const YUANCON_KB_MAP: [usize; 41] = [
|
||||
0x36 /* 6 */, 0x35 /* 5 */, 0x34 /* 4 */, 0x33 /* 3 */, 0x32 /* 2 */, 0x31 /* 1 */, 0x5a /* Z */, 0x59 /* Y */,
|
||||
0x58 /* X */, 0x57 /* W */, 0x56 /* V */, 0x55 /* U */, 0x54 /* T */, 0x53 /* S */, 0x52 /* R */, 0x51 /* Q */,
|
||||
0x50 /* P */, 0x4f /* O */, 0x4e /* N */, 0x4d /* M */, 0x4c /* L */, 0x4b /* K */, 0x4a /* J */, 0x49 /* I */,
|
||||
0x48 /* H */, 0x47 /* G */, 0x46 /* F */, 0x45 /* E */, 0x44 /* D */, 0x43 /* C */, 0x42 /* B */, 0x41 /* A */,
|
||||
0xbd, 0xbb, 0xdb, // VK_OEM_MINUS, VK_OEM_PLUS, VK_OEM_4,
|
||||
0xdd, 0xdc, 0xba, // VK_OEM_6, VK_OEM_5, VK_OEM_1,
|
||||
0x0d, 0x20, 0x1b, // VK_RETURN, VK_SPACE, VK_ESCAPE
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
const DEEMO_KB_MAP: [usize; 41] = [
|
||||
0x41, 0x41, 0x41, 0x41, // A
|
||||
0x53, 0x53, 0x53, 0x53, // S
|
||||
0x44, 0x44, 0x44, 0x44, // D
|
||||
0x46, 0x46, 0x46, 0x46, // F
|
||||
0x4a, 0x4a, 0x4a, 0x4a, // J
|
||||
0x4b, 0x4b, 0x4b, 0x4b, // K
|
||||
0x4c, 0x4c, 0x4c, 0x4c, // L
|
||||
0xba, 0xba, 0xba, 0xba, // VK_OEM_1
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // VK_SPACE
|
||||
0x00, 0x00, 0x00, // Disabled
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
const VOLTEX_KB_MAP: [usize; 41] = [
|
||||
0x57, 0x57, 0x57, 0x57, // W
|
||||
0x45, 0x45, 0x45, 0x45, // E
|
||||
0x43, 0x44,
|
||||
0x43, 0x44,
|
||||
0x43, 0x46, // D
|
||||
0x43, 0x46, // C // F
|
||||
0x4d, 0x4a, // M // J
|
||||
0x4d, 0x4a, // K
|
||||
0x4d, 0x4b,
|
||||
0x4d, 0x4b,
|
||||
0x4f, 0x4f, 0x4f, 0x4f, // O
|
||||
0x50, 0x50, 0x50, 0x50, // P
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Disabled
|
||||
0x31, 0x0d, 0x1b, // 1, VK_RETURN, VK_ESCAPE
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
const VOLTEX_KB_MAP_NEARDAYO: [usize; 41] = [
|
||||
0x57, 0x57, 0x57, 0x57, // W
|
||||
0x45, 0x45, 0x45, 0x45, // E
|
||||
0x43, 0x44,
|
||||
0x43, 0x44,
|
||||
0x43, 0x46, // D
|
||||
0x43, 0x46, // C // F
|
||||
0x4d, 0x4a, // M // J
|
||||
0x4d, 0x4a, // K
|
||||
0x4d, 0x4b,
|
||||
0x4d, 0x4b,
|
||||
0x4f, 0x4f, 0x4f, 0x4f, // O
|
||||
0x50, 0x50, 0x50, 0x50, // P
|
||||
0x57, 0x45, 0x45, 0x4f, 0x4f, 0x50, // Disabled
|
||||
0x31, 0x0d, 0x1b, // 1, VK_RETURN, VK_ESCAPE
|
||||
];
|
||||
|
||||
pub struct KeyboardOutput {
|
||||
ground_to_idx: [usize; 41],
|
||||
idx_to_keycode: [u16; 41],
|
||||
// keycode_to_idx: [usize; 256],
|
||||
next_keys: [bool; 41],
|
||||
last_keys: [bool; 41],
|
||||
|
||||
kb_buf: [INPUT; 41],
|
||||
n_kb_buf: u32,
|
||||
}
|
||||
|
||||
impl KeyboardOutput {
|
||||
pub fn new(layout: KeyboardLayout) -> Self {
|
||||
let kb_map = match layout {
|
||||
KeyboardLayout::Tasoller => &TASOLLER_KB_MAP,
|
||||
KeyboardLayout::Yuancon => &YUANCON_KB_MAP,
|
||||
KeyboardLayout::Deemo => &DEEMO_KB_MAP,
|
||||
KeyboardLayout::Voltex => &VOLTEX_KB_MAP,
|
||||
KeyboardLayout::Neardayo => &VOLTEX_KB_MAP_NEARDAYO,
|
||||
};
|
||||
|
||||
let mut ground_to_idx = [0 as usize; 41];
|
||||
let mut idx_to_keycode = [0 as u16; 41];
|
||||
let mut keycode_to_idx = [0xffff as usize; 256];
|
||||
let mut keycode_count: usize = 0;
|
||||
|
||||
for (ground, keycode) in kb_map.iter().enumerate() {
|
||||
if keycode_to_idx[*keycode] == 0xffff {
|
||||
keycode_to_idx[*keycode] = keycode_count;
|
||||
idx_to_keycode[keycode_count] = *keycode as u16;
|
||||
keycode_count += 1;
|
||||
}
|
||||
ground_to_idx[ground] = keycode_to_idx[*keycode]
|
||||
}
|
||||
|
||||
let mut kb_buf = [INPUT {
|
||||
type_: INPUT_KEYBOARD,
|
||||
u: unsafe { mem::zeroed() },
|
||||
}; 41];
|
||||
|
||||
for i in kb_buf.iter_mut() {
|
||||
let mut inner = unsafe { i.u.ki_mut() };
|
||||
inner.wVk = 0;
|
||||
inner.wScan = 0;
|
||||
inner.dwFlags = 0;
|
||||
inner.time = 0;
|
||||
inner.dwExtraInfo = 0;
|
||||
}
|
||||
|
||||
Self {
|
||||
ground_to_idx,
|
||||
idx_to_keycode,
|
||||
// keycode_to_idx,
|
||||
next_keys: [false; 41],
|
||||
last_keys: [false; 41],
|
||||
kb_buf,
|
||||
n_kb_buf: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn send(&mut self) {
|
||||
self.n_kb_buf = 0;
|
||||
|
||||
for (i, (n, l)) in self
|
||||
.next_keys
|
||||
.iter_mut()
|
||||
.zip(self.last_keys.iter_mut())
|
||||
.enumerate()
|
||||
{
|
||||
let keycode = self.idx_to_keycode[i];
|
||||
if keycode == 0 {
|
||||
continue;
|
||||
}
|
||||
match (*n, *l) {
|
||||
(true, false) => {
|
||||
let inner: &mut KEYBDINPUT = unsafe { self.kb_buf[self.n_kb_buf as usize].u.ki_mut() };
|
||||
inner.wVk = keycode;
|
||||
inner.dwFlags = 0;
|
||||
self.n_kb_buf += 1;
|
||||
// println!("{} down", keycode);
|
||||
}
|
||||
(false, true) => {
|
||||
let inner: &mut KEYBDINPUT = unsafe { self.kb_buf[self.n_kb_buf as usize].u.ki_mut() };
|
||||
inner.wVk = keycode;
|
||||
inner.dwFlags = KEYEVENTF_KEYUP;
|
||||
self.n_kb_buf += 1;
|
||||
// println!("{} up", keycode);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
*l = *n;
|
||||
}
|
||||
|
||||
if self.n_kb_buf != 0 {
|
||||
unsafe {
|
||||
SendInput(
|
||||
self.n_kb_buf,
|
||||
self.kb_buf.as_mut_ptr(),
|
||||
mem::size_of::<INPUT>() as c_int,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputHandler for KeyboardOutput {
|
||||
fn tick(&mut self, flat_controller_state: &Vec<bool>) -> bool {
|
||||
self.next_keys.fill(false);
|
||||
for (idx, x) in flat_controller_state.iter().enumerate() {
|
||||
if *x {
|
||||
self.next_keys[self.ground_to_idx[idx]] = true;
|
||||
}
|
||||
}
|
||||
self.send();
|
||||
true
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.next_keys.fill(false);
|
||||
self.send();
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for KeyboardOutput {
|
||||
fn drop(&mut self) {
|
||||
self.reset();
|
||||
}
|
||||
}
|
||||
@@ -1,235 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info};
|
||||
use palette::{FromColor, Hsv, Srgb};
|
||||
use serialport::{ClearBuffer, SerialPort};
|
||||
use std::{
|
||||
ops::DerefMut,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::time::{interval, Interval};
|
||||
|
||||
use crate::slider_io::{
|
||||
config::{LedMode, ReactiveLayout},
|
||||
controller_state::{FullState, LedState},
|
||||
utils::Buffer,
|
||||
voltex::VoltexState,
|
||||
worker::AsyncJob,
|
||||
};
|
||||
|
||||
pub struct LedJob {
|
||||
state: FullState,
|
||||
mode: LedMode,
|
||||
serial_port: Option<Box<dyn SerialPort>>,
|
||||
started: Instant,
|
||||
timer: Interval,
|
||||
}
|
||||
|
||||
impl LedJob {
|
||||
pub fn new(state: &FullState, mode: &LedMode) -> Self {
|
||||
Self {
|
||||
state: state.clone(),
|
||||
mode: mode.clone(),
|
||||
serial_port: None,
|
||||
started: Instant::now(),
|
||||
timer: interval(Duration::from_micros(33333)),
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_lights(
|
||||
&self,
|
||||
flat_controller_state: Option<&Vec<bool>>,
|
||||
serial_buffer: Option<&Buffer>,
|
||||
led_state: &mut LedState,
|
||||
) {
|
||||
match self.mode {
|
||||
LedMode::Reactive { layout, .. } => {
|
||||
let flat_controller_state = flat_controller_state.unwrap();
|
||||
|
||||
match layout {
|
||||
ReactiveLayout::Even { splits } => {
|
||||
let buttons_per_split = 32 / splits;
|
||||
|
||||
let banks: Vec<bool> = flat_controller_state
|
||||
.chunks(32 / splits)
|
||||
.take(splits)
|
||||
.map(|x| x.contains(&true))
|
||||
.collect();
|
||||
|
||||
for idx in 0..31 {
|
||||
led_state.paint(
|
||||
idx,
|
||||
match (idx + 1) % buttons_per_split {
|
||||
0 => &[255, 0, 255],
|
||||
_ => match banks[idx / buttons_per_split] {
|
||||
true => &[255, 0, 255],
|
||||
false => &[255, 255, 0],
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
ReactiveLayout::Voltex => {
|
||||
led_state.led_state.fill(0);
|
||||
|
||||
// Fixed
|
||||
led_state.paint(3, &[10, 100, 180]);
|
||||
for idx in 0..5 {
|
||||
led_state.paint(7 + idx * 4, &[64, 64, 64]);
|
||||
}
|
||||
led_state.paint(27, &[180, 10, 110]);
|
||||
|
||||
let voltex_state = VoltexState::from_flat(flat_controller_state);
|
||||
|
||||
// Left laser
|
||||
for (idx, state) in voltex_state.laser[0..2].iter().enumerate() {
|
||||
if *state {
|
||||
led_state.paint(0 + idx * 4, &[70, 230, 250]);
|
||||
led_state.paint(1 + idx * 4, &[70, 230, 250]);
|
||||
led_state.paint(2 + idx * 4, &[70, 230, 250]);
|
||||
}
|
||||
}
|
||||
|
||||
// Right laser
|
||||
for (idx, state) in voltex_state.laser[2..4].iter().enumerate() {
|
||||
if *state {
|
||||
led_state.paint(24 + idx * 4, &[250, 60, 200]);
|
||||
led_state.paint(25 + idx * 4, &[255, 60, 200]);
|
||||
led_state.paint(26 + idx * 4, &[255, 60, 200]);
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons
|
||||
for (idx, state) in voltex_state.bt.iter().enumerate() {
|
||||
if *state {
|
||||
led_state.paint(8 + idx * 4, &[255, 255, 255]);
|
||||
led_state.paint(10 + idx * 4, &[255, 255, 255]);
|
||||
}
|
||||
}
|
||||
|
||||
// Fx
|
||||
for (idx, state) in voltex_state.fx.iter().enumerate() {
|
||||
if *state {
|
||||
led_state.paint(9 + idx * 8, &[250, 100, 30]);
|
||||
led_state.paint(11 + idx * 8, &[250, 100, 30]);
|
||||
led_state.paint(13 + idx * 8, &[250, 100, 30]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LedMode::Attract => {
|
||||
let theta = self
|
||||
.started
|
||||
.elapsed()
|
||||
.div_duration_f64(Duration::from_secs(4))
|
||||
% 1.0;
|
||||
for idx in 0..31 {
|
||||
let slice_theta = (&theta + (idx as f64) / 32.0) % 1.0;
|
||||
let color = Srgb::from_color(Hsv::new(slice_theta * 360.0, 1.0, 1.0)).into_format::<u8>();
|
||||
led_state.paint(idx, &[color.red, color.green, color.blue]);
|
||||
}
|
||||
}
|
||||
LedMode::Serial { .. } => {
|
||||
// https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialProcessor.h
|
||||
// https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialProcessor.cpp
|
||||
// https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialLeds.h
|
||||
// https://github.com/jmontineri/OpeNITHM/blob/89e9a43f7484e8949cd31bbff79c32f21ea3ec1d/Firmware/OpeNITHM/SerialLeds.cpp
|
||||
if let Some(serial_buffer) = serial_buffer {
|
||||
// println!("buffer {:?}", serial_buffer.data);
|
||||
if serial_buffer.data[0] == 0xaa && serial_buffer.data[1] == 0xaa {
|
||||
for (idx, buf_chunk) in serial_buffer.data[2..95]
|
||||
.chunks(3)
|
||||
.take(31)
|
||||
.rev()
|
||||
.enumerate()
|
||||
{
|
||||
led_state.paint(idx, &[(*buf_chunk)[1], (*buf_chunk)[2], (*buf_chunk)[0]]);
|
||||
}
|
||||
// println!("leds {:?}", led_state.led_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => panic!("Not implemented"),
|
||||
}
|
||||
|
||||
led_state.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AsyncJob for LedJob {
|
||||
async fn setup(&mut self) -> bool {
|
||||
match &self.mode {
|
||||
LedMode::Serial { port } => {
|
||||
info!(
|
||||
"Serial port for led opening at {} {:?}",
|
||||
port.as_str(),
|
||||
115200
|
||||
);
|
||||
self.serial_port = match serialport::new(port, 115200).open() {
|
||||
Ok(s) => {
|
||||
info!("Serial port opened");
|
||||
Some(s)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Serial port could not open: {}", e);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
self.serial_port.is_some()
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
async fn tick(&mut self) -> bool {
|
||||
let mut flat_controller_state: Option<Vec<bool>> = None;
|
||||
let mut serial_buffer: Option<Buffer> = None;
|
||||
|
||||
// Do the IO here
|
||||
match self.mode {
|
||||
LedMode::Reactive { sensitivity, .. } => {
|
||||
let controller_state_handle = self.state.controller_state.lock();
|
||||
flat_controller_state = Some(controller_state_handle.flat(&sensitivity));
|
||||
}
|
||||
LedMode::Serial { .. } => {
|
||||
if let Some(serial_port) = self.serial_port.as_mut() {
|
||||
let mut serial_data_avail = serial_port.bytes_to_read().unwrap_or(0);
|
||||
if serial_data_avail >= 100 {
|
||||
if serial_data_avail % 100 == 0 {
|
||||
let mut serial_buffer_working = Buffer::new();
|
||||
serial_port
|
||||
.as_mut()
|
||||
.read_exact(&mut serial_buffer_working.data[..100])
|
||||
.ok()
|
||||
.unwrap();
|
||||
serial_data_avail -= 100;
|
||||
serial_buffer = Some(serial_buffer_working);
|
||||
}
|
||||
|
||||
if serial_data_avail > 0 {
|
||||
serial_port.clear(ClearBuffer::All).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// Then calculate and transfer
|
||||
{
|
||||
let mut led_state_handle = self.state.led_state.lock();
|
||||
self.calc_lights(
|
||||
flat_controller_state.as_ref(),
|
||||
serial_buffer.as_ref(),
|
||||
led_state_handle.deref_mut(),
|
||||
);
|
||||
}
|
||||
// thread::sleep(Duration::from_millis(30));
|
||||
// spin_sleep::sleep(Duration::from_micros(33333));
|
||||
self.timer.tick().await;
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
use log::info;
|
||||
use parking_lot::Mutex;
|
||||
use std::{
|
||||
sync::Arc,
|
||||
thread::{self, JoinHandle},
|
||||
};
|
||||
use tokio::{
|
||||
select,
|
||||
sync::{mpsc, oneshot},
|
||||
};
|
||||
|
||||
use crate::slider_io::{config::Config, context::Context};
|
||||
|
||||
use super::controller_state::FullState;
|
||||
|
||||
pub struct Manager {
|
||||
state: Arc<Mutex<Option<FullState>>>,
|
||||
context: Arc<Mutex<Option<Context>>>,
|
||||
join_handle: Option<JoinHandle<()>>,
|
||||
tx_config: mpsc::UnboundedSender<Config>,
|
||||
tx_stop: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn new() -> Self {
|
||||
let state = Arc::new(Mutex::new(None));
|
||||
let (tx_config, mut rx_config) = mpsc::unbounded_channel::<Config>();
|
||||
let (tx_stop, rx_stop) = oneshot::channel::<()>();
|
||||
|
||||
let context: Arc<Mutex<Option<Context>>> = Arc::new(Mutex::new(None));
|
||||
|
||||
let state_cloned = Arc::clone(&state);
|
||||
let context_cloned = Arc::clone(&context);
|
||||
|
||||
let join_handle = thread::spawn(move || {
|
||||
info!("Manager thread started");
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(4)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
runtime.block_on(async move {
|
||||
info!("Manager runtime started");
|
||||
|
||||
select! {
|
||||
_ = async {
|
||||
loop {
|
||||
match rx_config.recv().await {
|
||||
Some(config) => {
|
||||
info!("Rebuilding context");
|
||||
let mut context_handle = context_cloned.lock();
|
||||
context_handle.take();
|
||||
|
||||
let new_context = Context::new(config);
|
||||
let new_state = new_context.clone_state();
|
||||
context_handle.replace(new_context);
|
||||
|
||||
let mut state_handle = state_cloned.lock();
|
||||
state_handle.replace(new_state);
|
||||
},
|
||||
None => {
|
||||
let mut context_handle = context_cloned.lock();
|
||||
context_handle.take();
|
||||
}
|
||||
}
|
||||
}
|
||||
} => {},
|
||||
_ = rx_stop => {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Self {
|
||||
state,
|
||||
context,
|
||||
join_handle: Some(join_handle),
|
||||
tx_config,
|
||||
tx_stop: Some(tx_stop),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_config(&self, config: Config) {
|
||||
self.tx_config.send(config).unwrap();
|
||||
}
|
||||
|
||||
pub fn try_get_state(&self) -> Option<FullState> {
|
||||
let state_handle = self.state.lock();
|
||||
state_handle.as_ref().map(|x| x.clone())
|
||||
}
|
||||
|
||||
pub fn get_timer_state(&self) -> String {
|
||||
let context_handle = self.context.lock();
|
||||
context_handle
|
||||
.as_ref()
|
||||
.map(|context| context.timer_state())
|
||||
.unwrap_or("".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Manager {
|
||||
fn drop(&mut self) {
|
||||
if let Some(tx_stop) = self.tx_stop.take() {
|
||||
tx_stop.send(()).ok();
|
||||
}
|
||||
if let Some(join_handle) = self.join_handle.take() {
|
||||
join_handle.join().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
mod config;
|
||||
mod utils;
|
||||
mod worker;
|
||||
|
||||
mod controller_state;
|
||||
mod voltex;
|
||||
|
||||
mod acio;
|
||||
mod brokenithm;
|
||||
mod gamepad;
|
||||
mod keyboard;
|
||||
|
||||
mod device;
|
||||
mod led;
|
||||
mod output;
|
||||
|
||||
mod context;
|
||||
mod manager;
|
||||
|
||||
pub use config::Config;
|
||||
pub use manager::Manager;
|
||||
pub use utils::list_ips;
|
||||
@@ -1,97 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use log::error;
|
||||
use std::time::Duration;
|
||||
use tokio::time::{interval, Interval};
|
||||
|
||||
use crate::slider_io::{
|
||||
config::OutputMode, controller_state::FullState, gamepad::GamepadOutput,
|
||||
keyboard::KeyboardOutput, worker::AsyncJob,
|
||||
};
|
||||
|
||||
pub trait OutputHandler: Send {
|
||||
fn tick(&mut self, flat_controller_state: &Vec<bool>) -> bool;
|
||||
fn reset(&mut self);
|
||||
}
|
||||
|
||||
pub struct OutputJob {
|
||||
state: FullState,
|
||||
mode: OutputMode,
|
||||
sensitivity: u8,
|
||||
handler: Option<Box<dyn OutputHandler>>,
|
||||
timer: Interval,
|
||||
}
|
||||
|
||||
impl OutputJob {
|
||||
pub fn new(state: &FullState, mode: &OutputMode) -> Self {
|
||||
Self {
|
||||
state: state.clone(),
|
||||
mode: mode.clone(),
|
||||
sensitivity: 0,
|
||||
handler: None,
|
||||
timer: interval(Duration::MAX),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AsyncJob for OutputJob {
|
||||
async fn setup(&mut self) -> bool {
|
||||
match self.mode {
|
||||
OutputMode::Keyboard {
|
||||
layout,
|
||||
polling,
|
||||
sensitivity,
|
||||
} => {
|
||||
self.sensitivity = sensitivity;
|
||||
self.handler = Some(Box::new(KeyboardOutput::new(layout.clone())));
|
||||
self.timer = interval(Duration::from_micros(polling.to_t_u64()));
|
||||
|
||||
true
|
||||
}
|
||||
OutputMode::Gamepad {
|
||||
layout,
|
||||
polling,
|
||||
sensitivity,
|
||||
} => {
|
||||
self.sensitivity = sensitivity;
|
||||
let handler = GamepadOutput::new(layout.clone());
|
||||
self.timer = interval(Duration::from_micros(polling.to_t_u64()));
|
||||
|
||||
match handler {
|
||||
Some(handler) => {
|
||||
self.handler = Some(Box::new(handler));
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
error!("Not implemented");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn tick(&mut self) -> bool {
|
||||
let flat_controller_state: Vec<bool>;
|
||||
{
|
||||
let controller_state_handle = self.state.controller_state.lock();
|
||||
flat_controller_state = controller_state_handle.flat(&self.sensitivity);
|
||||
}
|
||||
|
||||
if let Some(handler) = self.handler.as_mut() {
|
||||
handler.tick(&flat_controller_state);
|
||||
}
|
||||
self.timer.tick().await;
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OutputJob {
|
||||
fn drop(&mut self) {
|
||||
if let Some(handler) = self.handler.as_mut() {
|
||||
handler.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
use atomic_float::AtomicF64;
|
||||
use std::{
|
||||
error::Error,
|
||||
fmt,
|
||||
sync::{atomic::Ordering, Arc},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
pub struct Buffer {
|
||||
pub data: [u8; 256],
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Buffer {
|
||||
pub fn new() -> Self {
|
||||
Buffer {
|
||||
data: [0; 256],
|
||||
len: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn slice(&self) -> &[u8] {
|
||||
&self.data[0..self.len]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShimError;
|
||||
|
||||
impl<'a> fmt::Display for ShimError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "ShimError")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ShimError {
|
||||
fn description(&self) -> &str {
|
||||
"shimError"
|
||||
}
|
||||
}
|
||||
|
||||
pub fn list_ips() -> Result<Vec<String>, Box<dyn Error>> {
|
||||
let mut ips = vec![];
|
||||
for adapter in ipconfig::get_adapters()? {
|
||||
for ip_address in adapter.ip_addresses() {
|
||||
ips.push(format!("{}", ip_address));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ips)
|
||||
}
|
||||
|
||||
pub struct LoopTimer {
|
||||
cap: usize,
|
||||
cur: usize,
|
||||
buf: Vec<Instant>,
|
||||
freq: Arc<AtomicF64>,
|
||||
}
|
||||
|
||||
impl LoopTimer {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cap: 100,
|
||||
cur: 0,
|
||||
buf: vec![Instant::now() - Duration::from_secs(10); 100],
|
||||
freq: Arc::new(AtomicF64::new(0.0)),
|
||||
}
|
||||
}
|
||||
pub fn tick(&mut self) {
|
||||
let last = self.buf[self.cur];
|
||||
let now = Instant::now();
|
||||
self.buf[self.cur] = now;
|
||||
|
||||
let delta = (now - last) / 100 + Duration::from_micros(1);
|
||||
let freq = Duration::from_millis(1000)
|
||||
.div_duration_f64(delta)
|
||||
.clamp(0.0, 9999.0);
|
||||
self.freq.store(freq, Ordering::SeqCst);
|
||||
|
||||
self.cur = match self.cur + 1 {
|
||||
cur if cur == self.cap => 0,
|
||||
cur => cur,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn reset(&mut self) {
|
||||
self.buf = vec![Instant::now() - Duration::from_secs(10); 100];
|
||||
self.cur = 0;
|
||||
}
|
||||
|
||||
pub fn fork(&self) -> Arc<AtomicF64> {
|
||||
Arc::clone(&self.freq)
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
pub struct VoltexState {
|
||||
pub laser: [bool; 4],
|
||||
pub bt: [bool; 4],
|
||||
pub fx: [bool; 2],
|
||||
pub extra: [bool; 3],
|
||||
}
|
||||
|
||||
impl VoltexState {
|
||||
pub fn from_flat(flat_controller_state: &Vec<bool>) -> Self {
|
||||
let mut voltex_state = Self {
|
||||
laser: [false; 4],
|
||||
bt: [false; 4],
|
||||
fx: [false; 2],
|
||||
extra: [false; 3],
|
||||
};
|
||||
|
||||
voltex_state.laser[0] = flat_controller_state[0..4].contains(&true);
|
||||
voltex_state.laser[1] = flat_controller_state[4..8].contains(&true);
|
||||
voltex_state.laser[2] = flat_controller_state[24..28].contains(&true);
|
||||
voltex_state.laser[3] = flat_controller_state[28..32].contains(&true);
|
||||
|
||||
for i in 0..4 {
|
||||
voltex_state.bt[i] = flat_controller_state[9 + i * 4] || flat_controller_state[11 + i * 4];
|
||||
}
|
||||
|
||||
for i in 0..2 {
|
||||
voltex_state.fx[i] = flat_controller_state[8 + i * 8]
|
||||
|| flat_controller_state[10 + i * 8]
|
||||
|| flat_controller_state[12 + i * 8]
|
||||
|| flat_controller_state[14 + i * 8];
|
||||
}
|
||||
|
||||
for i in 0..3 {
|
||||
voltex_state.extra[i] = flat_controller_state[38 + i];
|
||||
}
|
||||
|
||||
voltex_state
|
||||
}
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use log::info;
|
||||
use std::{
|
||||
future::Future,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use tokio::{sync::oneshot, task};
|
||||
|
||||
use crate::slider_io::utils::LoopTimer;
|
||||
|
||||
pub trait ThreadJob: Send {
|
||||
fn setup(&mut self) -> bool;
|
||||
fn tick(&mut self) -> bool;
|
||||
}
|
||||
|
||||
pub struct ThreadWorker {
|
||||
name: &'static str,
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl ThreadWorker {
|
||||
pub fn new<T: 'static + ThreadJob>(name: &'static str, mut job: T, mut timer: LoopTimer) -> Self {
|
||||
info!("Thread worker starting {}", name);
|
||||
|
||||
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let stop_signal_clone = Arc::clone(&stop_signal);
|
||||
Self {
|
||||
name,
|
||||
thread: Some(thread::spawn(move || {
|
||||
let setup_res = job.setup();
|
||||
stop_signal_clone.store(!setup_res, Ordering::SeqCst);
|
||||
|
||||
loop {
|
||||
if stop_signal_clone.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
if job.tick() {
|
||||
timer.tick();
|
||||
}
|
||||
}
|
||||
info!("Thread worker received stop {}", name);
|
||||
})),
|
||||
stop_signal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ThreadWorker {
|
||||
fn drop(&mut self) {
|
||||
info!("Thread worker stopping gracefully {}", self.name);
|
||||
|
||||
self.stop_signal.store(true, Ordering::SeqCst);
|
||||
if let Some(thread) = self.thread.take() {
|
||||
thread.join().ok();
|
||||
};
|
||||
|
||||
info!("Thread worker stopped {}", self.name);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait AsyncJob: Send + 'static {
|
||||
async fn setup(&mut self) -> bool;
|
||||
async fn tick(&mut self) -> bool;
|
||||
}
|
||||
|
||||
pub struct AsyncWorker {
|
||||
name: &'static str,
|
||||
task: Option<task::JoinHandle<()>>,
|
||||
stop_signal: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl AsyncWorker {
|
||||
pub fn new<T>(name: &'static str, mut job: T, mut timer: LoopTimer) -> Self
|
||||
where
|
||||
T: AsyncJob,
|
||||
{
|
||||
let stop_signal = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let stop_signal_clone = Arc::clone(&stop_signal);
|
||||
let task = tokio::spawn(async move {
|
||||
let setup_res = job.setup().await;
|
||||
stop_signal_clone.store(!setup_res, Ordering::SeqCst);
|
||||
|
||||
loop {
|
||||
if stop_signal_clone.load(Ordering::SeqCst) {
|
||||
break;
|
||||
}
|
||||
if job.tick().await {
|
||||
timer.tick();
|
||||
}
|
||||
}
|
||||
info!("Async worker received stop {}", name);
|
||||
});
|
||||
|
||||
Self {
|
||||
name,
|
||||
task: Some(task),
|
||||
stop_signal,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AsyncWorker {
|
||||
fn drop(&mut self) {
|
||||
info!("Async worker stopping gracefully {}", self.name);
|
||||
|
||||
self.stop_signal.store(true, Ordering::SeqCst);
|
||||
drop(self.task.take());
|
||||
|
||||
info!("Async worker stopped {}", self.name);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait AsyncHaltableJob: Send + 'static {
|
||||
async fn run<F: Future<Output = ()> + Send>(self, stop_signal: F);
|
||||
}
|
||||
|
||||
pub struct AsyncHaltableWorker {
|
||||
name: &'static str,
|
||||
task: Option<task::JoinHandle<()>>,
|
||||
stop_signal: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl AsyncHaltableWorker {
|
||||
pub fn new<T>(name: &'static str, job: T) -> Self
|
||||
where
|
||||
T: AsyncHaltableJob,
|
||||
{
|
||||
info!("AsyncHaltable worker starting {}", name);
|
||||
|
||||
let (send_stop, recv_stop) = oneshot::channel::<()>();
|
||||
|
||||
let task = tokio::spawn(async move {
|
||||
job
|
||||
.run(async move {
|
||||
recv_stop.await.ok();
|
||||
info!("AsyncHaltable worker received stop {}", name);
|
||||
})
|
||||
.await;
|
||||
});
|
||||
|
||||
Self {
|
||||
name,
|
||||
task: Some(task),
|
||||
stop_signal: Some(send_stop),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AsyncHaltableWorker {
|
||||
fn drop(&mut self) {
|
||||
info!("AsyncHaltable worker stopping gracefully {}", self.name);
|
||||
|
||||
if let Some(stop_signal) = self.stop_signal.take() {
|
||||
stop_signal.send(()).ok();
|
||||
}
|
||||
self.task.take();
|
||||
info!("AsyncHaltable worker stopped {}", self.name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user