From 786ac83c4c2eb56ded077936f912f063fe2ca969 Mon Sep 17 00:00:00 2001 From: 4yn Date: Sat, 5 Feb 2022 04:22:29 +0800 Subject: [PATCH] brokenithm working --- src-tauri/Cargo.lock | 9 + src-tauri/Cargo.toml | 12 +- src-tauri/res/www/app.js | 1 + src-tauri/res/www/config.js | 21 ++ src-tauri/res/www/favicon.ico | Bin 0 -> 116139 bytes src-tauri/res/www/index.html | 116 +++++++++ src-tauri/res/www/src.js | 344 ++++++++++++++++++++++++++ src-tauri/src/bin/test_async.rs | 52 ++-- src-tauri/src/bin/test_brokenithm.rs | 6 +- src-tauri/src/build.rs | 41 +++ src-tauri/src/main.rs | 6 +- src-tauri/src/slider_io/brokenithm.rs | 233 ++++++++++++++--- src-tauri/src/slider_io/mod.rs | 4 +- src-tauri/src/slider_io/worker.rs | 4 +- 14 files changed, 777 insertions(+), 72 deletions(-) create mode 100644 src-tauri/res/www/app.js create mode 100644 src-tauri/res/www/config.js create mode 100644 src-tauri/res/www/favicon.ico create mode 100644 src-tauri/res/www/index.html create mode 100644 src-tauri/res/www/src.js diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 281bbaf..b0c38c9 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -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", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 5100d3e..31bf82d 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -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" diff --git a/src-tauri/res/www/app.js b/src-tauri/res/www/app.js new file mode 100644 index 0000000..59828f1 --- /dev/null +++ b/src-tauri/res/www/app.js @@ -0,0 +1 @@ +var throttle=function(e,t){var a=!0,n=null;return function o(){var s=this;a?(a=!1,setTimeout(function(){a=!0,n&&o.apply(s)},t),n?(e.apply(this,n),n=null):e.apply(this,arguments)):n=arguments}},keys=document.getElementsByClassName("key"),airKeys=[],midline=0,touchKeys=[],allKeys=[],topKeys=airKeys,bottomKeys=touchKeys,compileKey=function(e){var t=e.previousElementSibling,a=e.nextElementSibling;return{top:e.offsetTop,bottom:e.offsetTop+e.offsetHeight,left:e.offsetLeft,right:e.offsetLeft+e.offsetWidth,almostLeft:t?e.offsetLeft+e.offsetWidth/4:-99999,almostRight:a?e.offsetLeft+3*e.offsetWidth/4:99999,kflag:parseInt(e.dataset.kflag)+(parseInt(e.dataset.air)?32:0),isAir:!!parseInt(e.dataset.air),prevKeyRef:t,prevKeyKflag:t?parseInt(t.dataset.kflag)+(parseInt(t.dataset.air)?32:0):null,nextKeyRef:a,nextKeyKflag:a?parseInt(a.dataset.kflag)+(parseInt(a.dataset.air)?32:0):null,ref:e}},isInside=function(e,t,a){return a.left<=e&&e2)return wsTimeout=0,ws.close(),wsConnected=!1,void wsConnect();wsConnected&&ws.send("alive?")},canvas=document.getElementById("canvas"),canvasCtx=canvas.getContext("2d"),canvasData=canvasCtx.getImageData(0,0,33,1),setupLed=function(){for(var e=0;e<33;e++)canvasData.data[4*e+3]=255};setupLed();var updateLed=function(e){for(var t=new Uint8Array(e),a=0;a<32;a++)canvasData.data[4*a]=t[3*(31-a)+1],canvasData.data[4*a+1]=t[3*(31-a)+2],canvasData.data[4*a+2]=t[3*(31-a)+0];canvasData.data[128]=t[94],canvasData.data[129]=t[95],canvasData.data[130]=t[93],canvasCtx.putImageData(canvasData,0,0)},fs=document.getElementById("fullscreen"),requestFullscreen=function(){!document.fullscreenElement&&screen.height<=1024&&(fs.requestFullscreen?fs.requestFullscreen():fs.mozRequestFullScreen?fs.mozRequestFullScreen():fs.webkitRequestFullScreen&&fs.webkitRequestFullScreen())},throttledRequestFullscreen=throttle(requestFullscreen,3e3),cnt=document.getElementById("main");cnt.addEventListener("touchstart",updateTouches),cnt.addEventListener("touchmove",updateTouches),cnt.addEventListener("touchend",updateTouches);var readConfig=function(e){var t="";e.invert&&(t+=".container, .air-container {flex-flow: column-reverse nowrap;} ");var a=e.bgColor||"rbga(0, 0, 0, 0.9)";e.bgImage?t+="#fullscreen {background: ".concat(a,' url("').concat(e.bgImage,'") fixed center / cover!important; background-repeat: no-repeat;} '):t+="#fullscreen {background: ".concat(a,";} "),"number"==typeof e.ledOpacity&&(0===e.ledOpacity?t+="#canvas {display: none} ":t+="#canvas {opacity: ".concat(e.ledOpacity,"} ")),"string"==typeof e.keyColor&&(t+=".key[data-active] {background-color: ".concat(e.keyColor,";} ")),"string"==typeof e.keyColor&&(t+=".key.air[data-active] {background-color: ".concat(e.lkeyColor,";} ")),"string"==typeof e.keyBorderColor&&(t+=".key {border: 1px solid ".concat(e.keyBorderColor,";} ")),e.keyColorFade&&"number"==typeof e.keyColorFade&&(t+=".key:not([data-active]) {transition: background ".concat(e.keyColorFade,"ms ease-out;} ")),"number"==typeof e.keyHeight&&(0===e.keyHeight?t+=".touch-container {display: none;} ":t+=".touch-container {flex: ".concat(e.keyHeight,";} ")),"number"==typeof e.lkeyHeight&&(0===e.lkeyHeight?t+=".air-container {display: none;} ":t+=".air-container {flex: ".concat(e.keyHeight,";} "));var n=document.createElement("style");n.innerHTML=t,document.head.appendChild(n)},initialize=function(){readConfig(config),compileKeys(),wsConnect(),setInterval(wsWatch,1e3)};initialize(),window.onresize=compileKeys; \ No newline at end of file diff --git a/src-tauri/res/www/config.js b/src-tauri/res/www/config.js new file mode 100644 index 0000000..56114fa --- /dev/null +++ b/src-tauri/res/www/config.js @@ -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", +}; diff --git a/src-tauri/res/www/favicon.ico b/src-tauri/res/www/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..9ed6dc60afae7a24b2b16c026ba373c01f8ab4bc GIT binary patch literal 116139 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 literal 0 HcmV?d00001 diff --git a/src-tauri/res/www/index.html b/src-tauri/res/www/index.html new file mode 100644 index 0000000..8728f08 --- /dev/null +++ b/src-tauri/res/www/index.html @@ -0,0 +1,116 @@ + + + + brokenithm-kb + + + + + + + +
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + diff --git a/src-tauri/res/www/src.js b/src-tauri/res/www/src.js new file mode 100644 index 0000000..94cd9e4 --- /dev/null +++ b/src-tauri/res/www/src.js @@ -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; diff --git a/src-tauri/src/bin/test_async.rs b/src-tauri/src/bin/test_async.rs index 9361af2..303424b 100644 --- a/src-tauri/src/bin/test_async.rs +++ b/src-tauri/src/bin/test_async.rs @@ -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 + 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 + 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(); } diff --git a/src-tauri/src/bin/test_brokenithm.rs b/src-tauri/src/bin/test_brokenithm.rs index 212517a..f698e38 100644 --- a/src-tauri/src/bin/test_brokenithm.rs +++ b/src-tauri/src/bin/test_brokenithm.rs @@ -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(); } diff --git a/src-tauri/src/build.rs b/src-tauri/src/build.rs index 311900d..14b1182 100644 --- a/src-tauri/src/build.rs +++ b/src-tauri/src/build.rs @@ -1,3 +1,44 @@ +use std::{ + env, fs, + path::{Path, PathBuf}, +}; + +const COPY_DIR: &'static str = "res"; + +fn copy_dir(from: P, to: Q) +where + P: AsRef, + Q: AsRef, +{ + // 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(); } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 5114f56..76e18d8 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -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() diff --git a/src-tauri/src/slider_io/brokenithm.rs b/src-tauri/src/slider_io/brokenithm.rs index c427c88..676b2e4 100644 --- a/src-tauri/src/slider_io/brokenithm.rs +++ b/src-tauri/src/slider_io/brokenithm.rs @@ -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, Infallible> { + Ok( + Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::from(format!("Not found"))) + .unwrap(), + ) +} + +async fn serve_file(path: &str) -> Result, 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, 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 = 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, + state: FullState, +) -> Result, 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, remote_addr: SocketAddr, + state: FullState, ) -> Result, 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| { - 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 + 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| { + 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); + } + } +} diff --git a/src-tauri/src/slider_io/mod.rs b/src-tauri/src/slider_io/mod.rs index f6a6080..18ab1a6 100644 --- a/src-tauri/src/slider_io/mod.rs +++ b/src-tauri/src/slider_io/mod.rs @@ -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; diff --git a/src-tauri/src/slider_io/worker.rs b/src-tauri/src/slider_io/worker.rs index a3131c8..896cc0e 100644 --- a/src-tauri/src/slider_io/worker.rs +++ b/src-tauri/src/slider_io/worker.rs @@ -68,7 +68,7 @@ impl Drop for ThreadWorker { #[async_trait] pub trait AsyncJob: Send + 'static { - async fn do_work + Send>(self, stop_signal: F); + async fn run + 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;