mirror of
https://github.com/4yn/slidershim.git
synced 2025-02-01 20:18:07 +01:00
brokenithm working
This commit is contained in:
parent
7f155903ca
commit
786ac83c4c
9
src-tauri/Cargo.lock
generated
9
src-tauri/Cargo.lock
generated
@ -2247,6 +2247,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "path-clean"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd"
|
||||
|
||||
[[package]]
|
||||
name = "pathdiff"
|
||||
version = "0.2.1"
|
||||
@ -3056,9 +3062,11 @@ dependencies = [
|
||||
"directories",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"hyper",
|
||||
"log",
|
||||
"palette",
|
||||
"path-clean",
|
||||
"rusb",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -3067,6 +3075,7 @@ dependencies = [
|
||||
"tauri-build",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tokio-util",
|
||||
"tungstenite",
|
||||
"vigem-client",
|
||||
"winapi",
|
||||
|
@ -22,6 +22,12 @@ log = "0.4.14"
|
||||
env_logger = "0.9.0"
|
||||
tauri = { version = "1.0.0-beta.8", features = ["api-all", "system-tray"] }
|
||||
|
||||
futures = "0.3.19"
|
||||
futures-util = "0.3.19"
|
||||
async-trait = "0.1.52"
|
||||
tokio = { version="1.16.1", features=["rt-multi-thread","macros"] }
|
||||
tokio-util = "0.6.9"
|
||||
|
||||
directories = "4.0.1"
|
||||
rusb = "0.9.0"
|
||||
serialport = "4.0.1"
|
||||
@ -29,10 +35,8 @@ vigem-client = "0.1.1"
|
||||
palette = "0.6.0"
|
||||
winapi = "0.3.9"
|
||||
|
||||
futures = "0.3.19"
|
||||
async-trait = "0.1.52"
|
||||
tokio = { version="1.16.1", features=["rt-multi-thread","macros"] }
|
||||
hyper = { version="0.14.16", features=["server","http1","http2","tcp"] }
|
||||
hyper = { version="0.14.16", features=["server", "http1", "http2", "tcp", "stream", "runtime"] }
|
||||
path-clean = "0.1.0"
|
||||
tungstenite = { version="0.16.0", default-features=false }
|
||||
tokio-tungstenite = "0.16.1"
|
||||
|
||||
|
1
src-tauri/res/www/app.js
Normal file
1
src-tauri/res/www/app.js
Normal file
File diff suppressed because one or more lines are too long
21
src-tauri/res/www/config.js
Normal file
21
src-tauri/res/www/config.js
Normal file
@ -0,0 +1,21 @@
|
||||
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",
|
||||
};
|
BIN
src-tauri/res/www/favicon.ico
Normal file
BIN
src-tauri/res/www/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 113 KiB |
116
src-tauri/res/www/index.html
Normal file
116
src-tauri/res/www/index.html
Normal file
@ -0,0 +1,116 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>brokenithm-kb</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" href="favicon.ico" />
|
||||
<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 {
|
||||
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>
|
344
src-tauri/res/www/src.js
Normal file
344
src-tauri/res/www/src.js
Normal file
@ -0,0 +1,344 @@
|
||||
/*
|
||||
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 < 32; i++) {
|
||||
canvasData.data[i * 4] = buf[(31 - i) * 3 + 1]; // r
|
||||
canvasData.data[i * 4 + 1] = buf[(31 - i) * 3 + 2]; // g
|
||||
canvasData.data[i * 4 + 2] = buf[(31 - i) * 3 + 0]; // b
|
||||
}
|
||||
// Copy from first led
|
||||
canvasData.data[128] = buf[94];
|
||||
canvasData.data[129] = buf[95];
|
||||
canvasData.data[130] = buf[93];
|
||||
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;
|
@ -5,41 +5,41 @@ use std::{future::Future, io, time::Duration};
|
||||
|
||||
use tokio::{select, time::sleep};
|
||||
|
||||
use slidershim::slider_io::worker::{AsyncJob, AsyncWorker};
|
||||
// use slidershim::slider_io::worker::{AsyncJob, AsyncWorker};
|
||||
|
||||
struct CounterJob;
|
||||
// struct CounterJob;
|
||||
|
||||
#[async_trait]
|
||||
impl AsyncJob for CounterJob {
|
||||
async fn do_work<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");
|
||||
};
|
||||
// #[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 => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
// select! {
|
||||
// _ = job_a => {},
|
||||
// _ = job_b => {},
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fn main() {
|
||||
env_logger::Builder::new()
|
||||
.filter_level(log::LevelFilter::Debug)
|
||||
.init();
|
||||
|
||||
let worker = AsyncWorker::new("counter", CounterJob);
|
||||
// let worker = AsyncWorker::new("counter", CounterJob);
|
||||
let mut input = String::new();
|
||||
let string = io::stdin().read_line(&mut input).unwrap();
|
||||
}
|
||||
|
@ -4,14 +4,16 @@ use std::{io, time::Duration};
|
||||
|
||||
use tokio::time::sleep;
|
||||
|
||||
// use slidershim::slider_io::{brokenithm::BrokenithmJob, worker::AsyncWorker};
|
||||
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);
|
||||
let worker = AsyncWorker::new("brokenithm", BrokenithmJob::new(FullState::new()));
|
||||
let mut input = String::new();
|
||||
let string = io::stdin().read_line(&mut input).unwrap();
|
||||
}
|
||||
|
@ -1,3 +1,44 @@
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
const COPY_DIR: &'static str = "res";
|
||||
|
||||
fn copy_dir<P, Q>(from: P, to: Q)
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
Q: AsRef<Path>,
|
||||
{
|
||||
// https://stackoverflow.com/a/68950006
|
||||
let to = to.as_ref().to_path_buf();
|
||||
|
||||
for path in fs::read_dir(from).unwrap() {
|
||||
let path = path.unwrap().path();
|
||||
let to = to.clone().join(path.file_name().unwrap());
|
||||
|
||||
if path.is_file() {
|
||||
fs::copy(&path, to).unwrap();
|
||||
} else if path.is_dir() {
|
||||
if !to.exists() {
|
||||
fs::create_dir(&to).unwrap();
|
||||
}
|
||||
|
||||
copy_dir(&path, to);
|
||||
} else { /* Skip other content */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let out = env::var("PROFILE").unwrap();
|
||||
let out = PathBuf::from(format!("target/{}/{}", out, COPY_DIR));
|
||||
|
||||
if out.exists() {
|
||||
fs::remove_dir_all(&out).unwrap()
|
||||
};
|
||||
fs::create_dir(&out).unwrap();
|
||||
copy_dir(COPY_DIR, &out);
|
||||
|
||||
tauri_build::build();
|
||||
}
|
||||
|
@ -32,8 +32,12 @@ fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let config = Arc::new(Mutex::new(Some(slider_io::Config::default())));
|
||||
let manager: slider_io::Manager;
|
||||
{
|
||||
config.lock().unwrap().as_ref().unwrap().save();
|
||||
let c = config.lock().unwrap();
|
||||
let cr = c.as_ref().unwrap();
|
||||
cr.save();
|
||||
manager = slider_io::Manager::new(cr.clone());
|
||||
}
|
||||
|
||||
tauri::Builder::default()
|
||||
|
@ -1,50 +1,213 @@
|
||||
use std::{convert::Infallible, net::SocketAddr};
|
||||
|
||||
use log::info;
|
||||
use tokio::time::sleep;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use hyper::{
|
||||
header,
|
||||
server::conn::AddrStream,
|
||||
service::{make_service_fn, service_fn},
|
||||
Body, Request, Response, Server,
|
||||
upgrade::{self, Upgraded},
|
||||
Body, Method, Request, Response, Server, StatusCode,
|
||||
};
|
||||
use log::{error, info};
|
||||
use path_clean::PathClean;
|
||||
use std::{convert::Infallible, future::Future, net::SocketAddr, path::PathBuf};
|
||||
use tokio::{fs::File, select};
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||
use tungstenite::{handshake, Error, Message};
|
||||
|
||||
// use crate::slider_io::worker::{AsyncJob, AsyncJobFut, AsyncJobRecvStop};
|
||||
use crate::slider_io::{controller_state::FullState, worker::AsyncJob};
|
||||
|
||||
// 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(),
|
||||
)
|
||||
}
|
||||
|
||||
async fn serve_file(path: &str) -> Result<Response<Body>, Infallible> {
|
||||
let mut pb = PathBuf::from("res/www/");
|
||||
pb.push(path);
|
||||
pb.clean();
|
||||
|
||||
// println!("CWD {:?}", std::env::current_dir());
|
||||
// println!("Serving file {:?}", pb);
|
||||
|
||||
match File::open(pb).await {
|
||||
Ok(f) => {
|
||||
let stream = FramedRead::new(f, BytesCodec::new());
|
||||
let body = Body::wrap_stream(stream);
|
||||
Ok(Response::new(body))
|
||||
}
|
||||
Err(_) => error_response().await,
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_brokenithm(ws_stream: WebSocketStream<Upgraded>, state: FullState) {
|
||||
let (mut ws_write, mut ws_read) = ws_stream.split();
|
||||
|
||||
loop {
|
||||
match ws_read.next().await {
|
||||
Some(msg) => match msg {
|
||||
Ok(msg) => match msg {
|
||||
Message::Text(msg) => {
|
||||
let mut chars = msg.chars();
|
||||
let head = chars.next().unwrap();
|
||||
match head {
|
||||
'a' => {
|
||||
ws_write.send(Message::Text("alive".to_string())).await;
|
||||
}
|
||||
'b' => {
|
||||
let flat_state: Vec<bool> = chars
|
||||
.map(|x| match x {
|
||||
'0' => false,
|
||||
'1' => true,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
let mut controller_state_handle = state.controller_state.lock().unwrap();
|
||||
for (idx, c) in flat_state[0..32].iter().enumerate() {
|
||||
controller_state_handle.ground_state[idx] = match c {
|
||||
false => 0,
|
||||
true => 255,
|
||||
}
|
||||
}
|
||||
for (idx, c) in flat_state[32..38].iter().enumerate() {
|
||||
controller_state_handle.air_state[idx] = match c {
|
||||
false => 0,
|
||||
true => 1,
|
||||
}
|
||||
}
|
||||
// println!(
|
||||
// "{:?} {:?}",
|
||||
// controller_state_handle.ground_state, controller_state_handle.air_state
|
||||
// );
|
||||
}
|
||||
_ => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::Close(_) => {
|
||||
info!("Websocket connection closed");
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Websocket connection error: {}", e);
|
||||
break;
|
||||
}
|
||||
},
|
||||
None => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_websocket(
|
||||
mut request: Request<Body>,
|
||||
state: FullState,
|
||||
) -> 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).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,
|
||||
) -> Result<Response<Body>, Infallible> {
|
||||
Ok(Response::new(Body::from(format!(
|
||||
"Hello there connection {}\n",
|
||||
remote_addr
|
||||
))))
|
||||
}
|
||||
let method = request.method();
|
||||
let path = request.uri().path();
|
||||
if method != Method::GET {
|
||||
error!("Server unknown method {} {}", method, path);
|
||||
return error_response().await;
|
||||
}
|
||||
info!("Server {} {}", method, path);
|
||||
|
||||
async fn brokenithm_server() {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 1666));
|
||||
|
||||
info!("Brokenithm opening on {:?}", addr);
|
||||
|
||||
let make_svc = make_service_fn(|conn: &AddrStream| {
|
||||
let remote_addr = conn.remote_addr();
|
||||
async move {
|
||||
Ok::<_, Infallible>(service_fn(move |request: Request<Body>| {
|
||||
handle_request(request, remote_addr)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
let server = Server::bind(&addr).serve(make_svc);
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("Server error: {}", e);
|
||||
match (
|
||||
request.uri().path(),
|
||||
request.headers().contains_key(header::UPGRADE),
|
||||
) {
|
||||
("/", false) | ("/index.html", false) => serve_file("index.html").await,
|
||||
(filename, false) => serve_file(&filename[1..]).await,
|
||||
("/ws", true) => handle_websocket(request, state).await,
|
||||
_ => error_response().await,
|
||||
}
|
||||
}
|
||||
|
||||
// struct BrokenithmJob;
|
||||
pub struct BrokenithmJob {
|
||||
state: FullState,
|
||||
}
|
||||
|
||||
// impl AsyncJob {
|
||||
// fn job(self, mut recv_stop: AsyncJobRecvStop) -> AsyncJobFut {
|
||||
// return Box::pin()
|
||||
// }
|
||||
// }
|
||||
impl BrokenithmJob {
|
||||
pub fn new(state: FullState) -> Self {
|
||||
Self { state }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AsyncJob for BrokenithmJob {
|
||||
async fn run<F: Future<Output = ()> + Send>(self, stop_signal: F) {
|
||||
let state = self.state.clone();
|
||||
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)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,10 @@ mod config;
|
||||
mod utils;
|
||||
pub mod worker;
|
||||
|
||||
mod controller_state;
|
||||
pub mod controller_state;
|
||||
mod voltex;
|
||||
|
||||
mod brokenithm;
|
||||
pub mod brokenithm;
|
||||
mod gamepad;
|
||||
mod keyboard;
|
||||
|
||||
|
@ -68,7 +68,7 @@ impl Drop for ThreadWorker {
|
||||
|
||||
#[async_trait]
|
||||
pub trait AsyncJob: Send + 'static {
|
||||
async fn do_work<F: Future<Output = ()> + Send>(self, stop_signal: F);
|
||||
async fn run<F: Future<Output = ()> + Send>(self, stop_signal: F);
|
||||
}
|
||||
|
||||
pub struct AsyncWorker {
|
||||
@ -94,7 +94,7 @@ impl AsyncWorker {
|
||||
|
||||
let task = runtime.spawn(async move {
|
||||
job
|
||||
.do_work(async move {
|
||||
.run(async move {
|
||||
recv_stop.await;
|
||||
})
|
||||
.await;
|
||||
|
Loading…
x
Reference in New Issue
Block a user