restructuring

This commit is contained in:
4yn
2022-02-12 22:14:50 +08:00
parent 3d20472c7f
commit 4f4d951610
37 changed files with 201 additions and 173 deletions
+1 -1
View File
@@ -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;
-45
View File
@@ -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();
}
-19
View File
@@ -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();
}
-11
View File
@@ -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();
}
-64
View File
@@ -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();
}
-31
View File
@@ -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();
}
-8
View File
@@ -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;
-52
View File
@@ -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;
-306
View File
@@ -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);
}
}
}
-323
View File
@@ -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(())
}
}
-110
View File
@@ -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(" ")
}
}
-106
View File
@@ -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(),
}
}
}
-293
View File
@@ -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();
}
}
}
-173
View File
@@ -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 {}
-208
View File
@@ -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();
}
}
-235
View File
@@ -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
}
}
-109
View File
@@ -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();
}
}
}
-22
View File
@@ -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;
-97
View File
@@ -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();
}
}
}
-96
View File
@@ -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)
}
}
-39
View File
@@ -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
}
}
-169
View File
@@ -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);
}
}