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",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "path-clean"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecba01bf2678719532c5e3059e0b5f0811273d94b397088b82e3bd0a78c78fdd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathdiff"
|
name = "pathdiff"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -3056,9 +3062,11 @@ dependencies = [
|
|||||||
"directories",
|
"directories",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
|
"futures-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"log",
|
"log",
|
||||||
"palette",
|
"palette",
|
||||||
|
"path-clean",
|
||||||
"rusb",
|
"rusb",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -3067,6 +3075,7 @@ dependencies = [
|
|||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
|
"tokio-util",
|
||||||
"tungstenite",
|
"tungstenite",
|
||||||
"vigem-client",
|
"vigem-client",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
@ -22,6 +22,12 @@ log = "0.4.14"
|
|||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
tauri = { version = "1.0.0-beta.8", features = ["api-all", "system-tray"] }
|
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"
|
directories = "4.0.1"
|
||||||
rusb = "0.9.0"
|
rusb = "0.9.0"
|
||||||
serialport = "4.0.1"
|
serialport = "4.0.1"
|
||||||
@ -29,10 +35,8 @@ vigem-client = "0.1.1"
|
|||||||
palette = "0.6.0"
|
palette = "0.6.0"
|
||||||
winapi = "0.3.9"
|
winapi = "0.3.9"
|
||||||
|
|
||||||
futures = "0.3.19"
|
hyper = { version="0.14.16", features=["server", "http1", "http2", "tcp", "stream", "runtime"] }
|
||||||
async-trait = "0.1.52"
|
path-clean = "0.1.0"
|
||||||
tokio = { version="1.16.1", features=["rt-multi-thread","macros"] }
|
|
||||||
hyper = { version="0.14.16", features=["server","http1","http2","tcp"] }
|
|
||||||
tungstenite = { version="0.16.0", default-features=false }
|
tungstenite = { version="0.16.0", default-features=false }
|
||||||
tokio-tungstenite = "0.16.1"
|
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 tokio::{select, time::sleep};
|
||||||
|
|
||||||
use slidershim::slider_io::worker::{AsyncJob, AsyncWorker};
|
// use slidershim::slider_io::worker::{AsyncJob, AsyncWorker};
|
||||||
|
|
||||||
struct CounterJob;
|
// struct CounterJob;
|
||||||
|
|
||||||
#[async_trait]
|
// #[async_trait]
|
||||||
impl AsyncJob for CounterJob {
|
// impl AsyncJob for CounterJob {
|
||||||
async fn do_work<F: Future<Output = ()> + Send>(self, stop_signal: F) {
|
// async fn run<F: Future<Output = ()> + Send>(self, stop_signal: F) {
|
||||||
let job_a = async {
|
// let job_a = async {
|
||||||
println!("Start job A");
|
// println!("Start job A");
|
||||||
let mut x = 0;
|
// let mut x = 0;
|
||||||
loop {
|
// loop {
|
||||||
x += 1;
|
// x += 1;
|
||||||
println!("{}", x);
|
// println!("{}", x);
|
||||||
sleep(Duration::from_millis(100)).await;
|
// sleep(Duration::from_millis(100)).await;
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
let job_b = async move {
|
// let job_b = async move {
|
||||||
println!("Start job B");
|
// println!("Start job B");
|
||||||
stop_signal.await;
|
// stop_signal.await;
|
||||||
println!("Stop signal hit at job B");
|
// println!("Stop signal hit at job B");
|
||||||
};
|
// };
|
||||||
|
|
||||||
select! {
|
// select! {
|
||||||
_ = job_a => {},
|
// _ = job_a => {},
|
||||||
_ = job_b => {},
|
// _ = job_b => {},
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::Builder::new()
|
env_logger::Builder::new()
|
||||||
.filter_level(log::LevelFilter::Debug)
|
.filter_level(log::LevelFilter::Debug)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let worker = AsyncWorker::new("counter", CounterJob);
|
// let worker = AsyncWorker::new("counter", CounterJob);
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
let string = io::stdin().read_line(&mut input).unwrap();
|
let string = io::stdin().read_line(&mut input).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,16 @@ use std::{io, time::Duration};
|
|||||||
|
|
||||||
use tokio::time::sleep;
|
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() {
|
fn main() {
|
||||||
env_logger::Builder::new()
|
env_logger::Builder::new()
|
||||||
.filter_level(log::LevelFilter::Debug)
|
.filter_level(log::LevelFilter::Debug)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
// let worker = AsyncWorker::new("brokenithm", BrokenithmJob);
|
let worker = AsyncWorker::new("brokenithm", BrokenithmJob::new(FullState::new()));
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
let string = io::stdin().read_line(&mut input).unwrap();
|
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() {
|
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();
|
tauri_build::build();
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,12 @@ fn main() {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
let config = Arc::new(Mutex::new(Some(slider_io::Config::default())));
|
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()
|
tauri::Builder::default()
|
||||||
|
@ -1,50 +1,213 @@
|
|||||||
use std::{convert::Infallible, net::SocketAddr};
|
use async_trait::async_trait;
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
use log::info;
|
|
||||||
use tokio::time::sleep;
|
|
||||||
|
|
||||||
use hyper::{
|
use hyper::{
|
||||||
|
header,
|
||||||
server::conn::AddrStream,
|
server::conn::AddrStream,
|
||||||
service::{make_service_fn, service_fn},
|
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(
|
async fn handle_request(
|
||||||
request: Request<Body>,
|
request: Request<Body>,
|
||||||
remote_addr: SocketAddr,
|
remote_addr: SocketAddr,
|
||||||
|
state: FullState,
|
||||||
) -> Result<Response<Body>, Infallible> {
|
) -> Result<Response<Body>, Infallible> {
|
||||||
Ok(Response::new(Body::from(format!(
|
let method = request.method();
|
||||||
"Hello there connection {}\n",
|
let path = request.uri().path();
|
||||||
remote_addr
|
if method != Method::GET {
|
||||||
))))
|
error!("Server unknown method {} {}", method, path);
|
||||||
}
|
return error_response().await;
|
||||||
|
}
|
||||||
|
info!("Server {} {}", method, path);
|
||||||
|
|
||||||
async fn brokenithm_server() {
|
match (
|
||||||
let addr = SocketAddr::from(([0, 0, 0, 0], 1666));
|
request.uri().path(),
|
||||||
|
request.headers().contains_key(header::UPGRADE),
|
||||||
info!("Brokenithm opening on {:?}", addr);
|
) {
|
||||||
|
("/", false) | ("/index.html", false) => serve_file("index.html").await,
|
||||||
let make_svc = make_service_fn(|conn: &AddrStream| {
|
(filename, false) => serve_file(&filename[1..]).await,
|
||||||
let remote_addr = conn.remote_addr();
|
("/ws", true) => handle_websocket(request, state).await,
|
||||||
async move {
|
_ => error_response().await,
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// struct BrokenithmJob;
|
pub struct BrokenithmJob {
|
||||||
|
state: FullState,
|
||||||
|
}
|
||||||
|
|
||||||
// impl AsyncJob {
|
impl BrokenithmJob {
|
||||||
// fn job(self, mut recv_stop: AsyncJobRecvStop) -> AsyncJobFut {
|
pub fn new(state: FullState) -> Self {
|
||||||
// return Box::pin()
|
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;
|
mod utils;
|
||||||
pub mod worker;
|
pub mod worker;
|
||||||
|
|
||||||
mod controller_state;
|
pub mod controller_state;
|
||||||
mod voltex;
|
mod voltex;
|
||||||
|
|
||||||
mod brokenithm;
|
pub mod brokenithm;
|
||||||
mod gamepad;
|
mod gamepad;
|
||||||
mod keyboard;
|
mod keyboard;
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ impl Drop for ThreadWorker {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait AsyncJob: Send + 'static {
|
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 {
|
pub struct AsyncWorker {
|
||||||
@ -94,7 +94,7 @@ impl AsyncWorker {
|
|||||||
|
|
||||||
let task = runtime.spawn(async move {
|
let task = runtime.spawn(async move {
|
||||||
job
|
job
|
||||||
.do_work(async move {
|
.run(async move {
|
||||||
recv_stop.await;
|
recv_stop.await;
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user