1
0
mirror of https://github.com/4yn/slidershim.git synced 2025-02-13 00:54:41 +01:00

better brokenithm

This commit is contained in:
4yn 2022-02-07 02:01:34 +08:00
parent fcf80538b4
commit cc24e628aa
20 changed files with 556 additions and 152 deletions

View File

@ -12,7 +12,7 @@ body {
color: #ddd; color: #ddd;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
font-size: 1rem; /* font-size: 1rem; */
user-select: none; user-select: none;
} }
@ -23,20 +23,49 @@ pre {
body { body {
margin: 0; margin: 0;
padding: 1rem; padding: 1rem;
border: 0.125rem solid #333;
} }
/* titlebar */
.titlebar {
user-select: none;
background: #333;
display: flex;
align-items: center;
justify-content: flex-start;
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 0.25rem 0.5rem;
height: 2rem;
}
.header-icon {
max-height: 100%;
}
.header-icon img {
max-height: 1.5rem;
pointer-events: none;
}
.header {
font-size: 1.5rem;
font-weight: 500;
}
/* main */
.main { .main {
margin-top: 2rem;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
align-items: stretch; align-items: stretch;
justify-content: flex-start; justify-content: flex-start;
} }
.header {
font-size: 2rem;
font-weight: 500;
}
.row, .row,
.row-2 { .row-2 {
margin: 0 0 0.5rem 0; margin: 0 0 0.5rem 0;
@ -50,6 +79,14 @@ body {
flex: 1 1 0; flex: 1 1 0;
} }
.row .label[title] {
text-decoration: underline dotted;
}
.row .label[title]:hover {
text-decoration: underline;
}
.row .input { .row .input {
flex: 2 2 0; flex: 2 2 0;
} }
@ -58,9 +95,7 @@ body {
width: 100%; width: 100%;
max-height: 5rem; max-height: 5rem;
overflow-x: auto; overflow-x: auto;
} font-size: 0.75rem;
.serverlist > pre {
font-size: 0.75em;
} }
input, input,
@ -69,6 +104,7 @@ select {
background-color: #444; background-color: #444;
color: #ddd; color: #ddd;
border: none; border: none;
font-size: 1rem;
} }
button { button {
@ -92,6 +128,8 @@ button.primary {
background: rgb(35, 67, 211); background: rgb(35, 67, 211);
} }
/* Preview */
.preview { .preview {
width: 100%; width: 100%;
display: flex; display: flex;
@ -107,6 +145,9 @@ button.primary {
flex-flow: column-reverse nowrap; flex-flow: column-reverse nowrap;
align-items: stretch; align-items: stretch;
justify-content: flex-start; justify-content: flex-start;
border-radius: 0.5rem 0.5rem 0 0;
overflow: clip;
} }
.air-data { .air-data {
flex: 1 0; flex: 1 0;
@ -121,6 +162,9 @@ button.primary {
.ground { .ground {
position: relative; position: relative;
height: 3rem; height: 3rem;
border-radius: 0 0 0.5rem 0.5rem;
overflow: clip;
} }
.ground-btn, .ground-btn,
@ -176,7 +220,7 @@ button.primary {
display: flex; display: flex;
flex-flow: row nowrap; flex-flow: row nowrap;
align-items: stretch; align-items: stretch;
justify-content: flex-start; justify-content: center;
} }
.extra-data { .extra-data {
width: 1rem; width: 1rem;

BIN
public/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,18 +1,24 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset='utf-8'> <meta charset="utf-8" />
<meta name='viewport' content='width=device-width,initial-scale=1'> <meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Svelte app</title> <title>Svelte app</title>
<link rel='icon' type='image/png' href='/favicon.png'> <link rel="icon" type="image/png" href="/favicon.png" />
<link rel='stylesheet' href='/global.css'> <link rel="stylesheet" href="/global.css" />
<link rel='stylesheet' href='/build/bundle.css'> <link rel="stylesheet" href="/build/bundle.css" />
<script defer src='/build/bundle.js'></script> <script defer src="/build/bundle.js"></script>
</head> </head>
<body> <body>
</body> <div data-tauri-drag-region class="titlebar">
<div data-tauri-drag-region class="header-icon">
<img src="/icon.png" />
</div>
<div data-tauri-drag-region class="header">&nbsp;slidershim</div>
</div>
</body>
</html> </html>

64
res/sshelper/index.html Normal file
View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
body {
max-width: 650px;
margin: 40px auto;
padding: 0 10px;
font: 18px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
color: #444;
}
h1,
h2,
h3 {
line-height: 1.2;
}
@media (prefers-color-scheme: dark) {
body {
color: #ccc;
background: black;
}
a:link {
color: #5bf;
}
a:visited {
color: #ccf;
}
}
</style>
<title>brokenithm-qr</title>
</head>
<body>
<h1>slidershim brokenithm helper</h1>
<ul class="links"></ul>
<script>
(function () {
const search = window.location.search;
if (search.slice(0, 3) !== "?d=" || search.length <= 3) {
return;
}
const ul = document.querySelector(".links");
search
.slice(3)
.split(";")
.map((x) => atob(x))
.forEach((ip) => {
const li = document.createElement("li");
const a = document.createElement("a");
a.innerText = ip;
a.setAttribute("target", "_blank");
a.setAttribute("rel", "noopener");
a.href = `http://${ip}:1606/`;
li.appendChild(a);
ul.appendChild(li);
});
})();
</script>
</body>
</html>

104
src-tauri/Cargo.lock generated
View File

@ -225,6 +225,12 @@ version = "3.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
[[package]]
name = "bytemuck"
version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439989e6b8c38d1b6570a384ef1e49c8848128f5a97f3914baef02920842712f"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
@ -328,6 +334,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "checked_int_cast"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919"
[[package]] [[package]]
name = "cocoa" name = "cocoa"
version = "0.24.0" version = "0.24.0"
@ -359,6 +371,12 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "com" name = "com"
version = "0.2.0" version = "0.2.0"
@ -1073,6 +1091,16 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1", "wasi 0.10.2+wasi-snapshot-preview1",
] ]
[[package]]
name = "gif"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b"
dependencies = [
"color_quant",
"weezl",
]
[[package]] [[package]]
name = "gio" name = "gio"
version = "0.14.8" version = "0.14.8"
@ -1433,6 +1461,25 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "image"
version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"gif",
"jpeg-decoder",
"num-iter",
"num-rational",
"num-traits",
"png 0.16.8",
"scoped_threadpool",
"tiff",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.8.0" version = "1.8.0"
@ -1537,6 +1584,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "jpeg-decoder"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
dependencies = [
"rayon",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.56" version = "0.3.56"
@ -1872,6 +1928,17 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-rational"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.14"
@ -2357,6 +2424,16 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "qrcode"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16d2f1455f3630c6e5107b4f2b94e74d76dea80736de0981fd27644216cff57f"
dependencies = [
"checked_int_cast",
"image",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.15" version = "1.0.15"
@ -2649,6 +2726,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "scoped_threadpool"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8"
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.1.0" version = "1.1.0"
@ -2853,15 +2936,19 @@ name = "slidershim"
version = "0.1.1" version = "0.1.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64",
"directories", "directories",
"env_logger", "env_logger",
"futures", "futures",
"futures-util", "futures-util",
"hyper", "hyper",
"image",
"ipconfig", "ipconfig",
"log", "log",
"open",
"palette", "palette",
"path-clean", "path-clean",
"qrcode",
"rusb", "rusb",
"serde", "serde",
"serde_json", "serde_json",
@ -3336,6 +3423,17 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "tiff"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437"
dependencies = [
"jpeg-decoder",
"miniz_oxide 0.4.4",
"weezl",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.43" version = "0.1.43"
@ -3805,6 +3903,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "weezl"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e"
[[package]] [[package]]
name = "widestring" name = "widestring"
version = "0.5.1" version = "0.5.1"

View File

@ -20,6 +20,7 @@ serde = { version = "1.0", features = ["derive"] }
log = "0.4.14" log = "0.4.14"
env_logger = "0.9.0" env_logger = "0.9.0"
simple-logging = "2.0.2" simple-logging = "2.0.2"
open = "2.0.2"
tauri = { version = "1.0.0-beta.8", features = ["shell-open", "system-tray"] } tauri = { version = "1.0.0-beta.8", features = ["shell-open", "system-tray"] }
futures = "0.3.19" futures = "0.3.19"
@ -37,6 +38,9 @@ winapi = "0.3.9"
ipconfig = "0.3.0" ipconfig = "0.3.0"
hyper = { version="0.14.16", features= ["server", "http1", "http2", "tcp", "stream", "runtime"] } hyper = { version="0.14.16", features= ["server", "http1", "http2", "tcp", "stream", "runtime"] }
base64 = "0.13.0"
image = "0.23.14"
qrcode = { version="0.12.0", features= ["image"] }
path-clean = "0.1.0" path-clean = "0.1.0"
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"

File diff suppressed because one or more lines are too long

View File

@ -67,6 +67,9 @@
} }
canvas { canvas {
-ms-interpolation-mode: nearest-neighbor;
image-rendering: crisp-edges;
image-rendering: pixelated;
touch-action: none; touch-action: none;
margin: 0px -1.5625vw; margin: 0px -1.5625vw;
} }
@ -112,6 +115,6 @@
</div> </div>
</div> </div>
<script src="/config.js"></script> <script src="/config.js"></script>
<script src="/app.js"></script> <script src="/src.js"></script>
</body> </body>
</html> </html>

View File

@ -66,6 +66,9 @@
} }
canvas { canvas {
-ms-interpolation-mode: nearest-neighbor;
image-rendering: crisp-edges;
image-rendering: pixelated;
touch-action: none; touch-action: none;
margin: 0px -1.5625vw; margin: 0px -1.5625vw;
} }
@ -111,6 +114,6 @@
</div> </div>
</div> </div>
<script src="/config.js"></script> <script src="/config.js"></script>
<script src="/app.js"></script> <script src="/src.js"></script>
</body> </body>
</html> </html>

View File

@ -236,15 +236,18 @@ const setupLed = () => {
setupLed(); setupLed();
const updateLed = (data) => { const updateLed = (data) => {
const buf = new Uint8Array(data); const buf = new Uint8Array(data);
for (var i = 0; i < 32; i++) { for (var i = 0; i < 31; i++) {
canvasData.data[i * 4] = buf[(31 - i) * 3 + 1]; // r canvasData.data[i * 4 + 4] = buf[i * 3]; // r
canvasData.data[i * 4 + 1] = buf[(31 - i) * 3 + 2]; // g canvasData.data[i * 4 + 5] = buf[i * 3 + 1]; // g
canvasData.data[i * 4 + 2] = buf[(31 - i) * 3 + 0]; // b canvasData.data[i * 4 + 6] = buf[i * 3 + 2]; // b
} }
// Copy from first led canvasData.data[0] = buf[0]
canvasData.data[128] = buf[94]; canvasData.data[1] = buf[1]
canvasData.data[129] = buf[95]; canvasData.data[2] = buf[2]
canvasData.data[130] = buf[93]; canvasData.data[128] = buf[90];
canvasData.data[129] = buf[91];
canvasData.data[130] = buf[92];
canvasCtx.putImageData(canvasData, 0, 0); canvasCtx.putImageData(canvasData, 0, 0);
}; };

View File

@ -9,7 +9,6 @@ mod slider_io;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
// use env_logger;
use log::info; use log::info;
use tauri::{ use tauri::{
@ -32,9 +31,18 @@ fn quit_app() {
fn main() { fn main() {
// Setup logger // Setup logger
let log_file_path = slider_io::Config::get_log_file_path().unwrap();
simple_logging::log_to_file(log_file_path.as_path(), log::LevelFilter::Debug).unwrap(); #[cfg(debug_assertions)]
// simple_logging::log_to_file("./log.txt", log::LevelFilter::Debug).unwrap(); env_logger::Builder::new()
.filter_level(log::LevelFilter::Debug)
.init();
#[cfg(not(debug_assertions))]
{
let log_file_path = slider_io::Config::get_log_file_path().unwrap();
simple_logging::log_to_file(log_file_path.as_path(), log::LevelFilter::Debug).unwrap();
// simple_logging::log_to_file("./log.txt", log::LevelFilter::Debug).unwrap();
}
let config = Arc::new(Mutex::new(Some(slider_io::Config::default()))); let config = Arc::new(Mutex::new(Some(slider_io::Config::default())));
let manager = Arc::new(Mutex::new(slider_io::Manager::new())); let manager = Arc::new(Mutex::new(slider_io::Manager::new()));
@ -92,6 +100,22 @@ fn main() {
quit_app(); quit_app();
}); });
// Show logs
app.listen_global("openLogfile", |_| {
let log_file_path = slider_io::Config::get_log_file_path();
if let Some(log_file_path) = log_file_path {
open::that(log_file_path.as_path()).ok();
}
});
// Show brokenithm qr
app.listen_global("openBrokenithmQr", |_| {
let brokenithm_qr_path = slider_io::Config::get_brokenithm_qr_path();
if let Some(brokenithm_qr_path) = brokenithm_qr_path {
open::that(brokenithm_qr_path.as_path()).ok();
}
});
// UI ready event // UI ready event
let app_handle = app.handle(); let app_handle = app.handle();
let config_clone = Arc::clone(&config); let config_clone = Arc::clone(&config);
@ -109,11 +133,6 @@ fn main() {
if let Ok(ips) = ips { if let Ok(ips) = ips {
app_handle.emit_all("listIps", &ips).unwrap(); app_handle.emit_all("listIps", &ips).unwrap();
} }
let log_file_path = slider_io::Config::get_log_file_path().unwrap();
app_handle
.emit_all("updateLogPath", log_file_path.as_path().to_str().unwrap())
.unwrap();
}); });
// UI update event // UI update event

View File

@ -10,7 +10,12 @@ use hyper::{
use log::{error, info}; use log::{error, info};
use path_clean::PathClean; use path_clean::PathClean;
use std::{convert::Infallible, env::current_exe, future::Future, net::SocketAddr}; use std::{convert::Infallible, env::current_exe, future::Future, net::SocketAddr};
use tokio::fs::File; use tokio::{
fs::File,
select,
sync::mpsc,
time::{sleep, Duration},
};
use tokio_tungstenite::WebSocketStream; use tokio_tungstenite::WebSocketStream;
use tokio_util::codec::{BytesCodec, FramedRead}; use tokio_util::codec::{BytesCodec, FramedRead};
use tungstenite::{handshake, Message}; use tungstenite::{handshake, Message};
@ -51,67 +56,111 @@ async fn serve_file(path: &str) -> Result<Response<Body>, Infallible> {
async fn handle_brokenithm(ws_stream: WebSocketStream<Upgraded>, state: FullState) { async fn handle_brokenithm(ws_stream: WebSocketStream<Upgraded>, state: FullState) {
let (mut ws_write, mut ws_read) = ws_stream.split(); let (mut ws_write, mut ws_read) = ws_stream.split();
loop { let (msg_write, mut msg_read) = mpsc::unbounded_channel::<Message>();
match ws_read.next().await {
Some(msg) => match msg { let write_task = async move {
Ok(msg) => match msg { // info!("Websocket write task open");
Message::Text(msg) => { loop {
let mut chars = msg.chars(); match msg_read.recv().await {
let head = chars.next().unwrap(); Some(msg) => match ws_write.send(msg).await.ok() {
match head { Some(_) => {}
'a' => { None => {
ws_write
.send(Message::Text("alive".to_string()))
.await
.unwrap();
}
'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; break;
} }
_ => {}
}, },
Err(e) => { None => {
error!("Websocket connection error: {}", e);
break; 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 mut chars = msg.chars();
let head = chars.next().unwrap();
match head {
'a' => {
msg_write_handle
.send(Message::Text("alive".to_string()))
.ok();
}
'b' => {
let flat_state: Vec<bool> = chars
.map(|x| match x {
'0' => false,
'1' => true,
_ => unreachable!(),
})
.collect();
let mut controller_state_handle = state_handle.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,
}
}
}
_ => {
break;
}
}
}
Message::Close(_) => {
info!("Websocket connection closed");
break;
}
_ => {}
},
Err(e) => {
error!("Websocket connection error: {}", e);
break;
}
},
None => {
break;
}
}
}
// info!("Websocket read task done");
};
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().unwrap();
(&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;
}
};
info!("Websocket handling");
select! {
_ = read_task => {}
_ = write_task => {}
_ = led_task => {}
};
info!("Websocket done");
} }
async fn handle_websocket( async fn handle_websocket(

View File

@ -1,8 +1,12 @@
use directories::ProjectDirs; use directories::ProjectDirs;
use image::Luma;
use log::{info, warn}; use log::{info, warn};
use qrcode::QrCode;
use serde_json::Value; use serde_json::Value;
use std::{convert::TryFrom, fs, path::PathBuf}; use std::{convert::TryFrom, fs, path::PathBuf};
use crate::slider_io::utils::list_ips;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum DeviceMode { pub enum DeviceMode {
None, None,
@ -12,6 +16,38 @@ pub enum DeviceMode {
Brokenithm { ground_only: bool }, Brokenithm { ground_only: bool },
} }
#[derive(Debug, Clone, Copy)]
pub enum OutputPolling {
Sixty,
Hundred,
ThreeHundred,
FiveHundred,
Thousand,
}
impl OutputPolling {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"60" => Some(OutputPolling::Sixty),
"100" => Some(OutputPolling::Hundred),
"330" => Some(OutputPolling::ThreeHundred),
"500" => Some(OutputPolling::FiveHundred),
"1000" => Some(OutputPolling::Thousand),
_ => None,
}
}
pub fn to_t_u64(&self) -> u64 {
match self {
OutputPolling::Sixty => 16,
OutputPolling::Hundred => 10,
OutputPolling::ThreeHundred => 3,
OutputPolling::FiveHundred => 2,
OutputPolling::Thousand => 1,
}
}
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum KeyboardLayout { pub enum KeyboardLayout {
Tasoller, Tasoller,
@ -31,14 +67,17 @@ pub enum OutputMode {
None, None,
Keyboard { Keyboard {
layout: KeyboardLayout, layout: KeyboardLayout,
polling: OutputPolling,
sensitivity: u8, sensitivity: u8,
}, },
Gamepad { Gamepad {
layout: GamepadLayout, layout: GamepadLayout,
polling: OutputPolling,
sensitivity: u8, sensitivity: u8,
}, },
Websocket { Websocket {
url: String, url: String,
polling: OutputPolling,
}, },
} }
@ -92,30 +131,37 @@ impl Config {
"none" => OutputMode::None, "none" => OutputMode::None,
"kb-32-tasoller" => OutputMode::Keyboard { "kb-32-tasoller" => OutputMode::Keyboard {
layout: KeyboardLayout::Tasoller, layout: KeyboardLayout::Tasoller,
polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
}, },
"kb-32-yuancon" => OutputMode::Keyboard { "kb-32-yuancon" => OutputMode::Keyboard {
layout: KeyboardLayout::Yuancon, layout: KeyboardLayout::Yuancon,
polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
}, },
"kb-8-deemo" => OutputMode::Keyboard { "kb-8-deemo" => OutputMode::Keyboard {
layout: KeyboardLayout::Deemo, layout: KeyboardLayout::Deemo,
polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
}, },
"kb-voltex" => OutputMode::Keyboard { "kb-voltex" => OutputMode::Keyboard {
layout: KeyboardLayout::Voltex, layout: KeyboardLayout::Voltex,
polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
}, },
"gamepad-voltex" => OutputMode::Gamepad { "gamepad-voltex" => OutputMode::Gamepad {
layout: GamepadLayout::Voltex, layout: GamepadLayout::Voltex,
polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
}, },
"gamepad-neardayo" => OutputMode::Gamepad { "gamepad-neardayo" => OutputMode::Gamepad {
layout: GamepadLayout::Neardayo, layout: GamepadLayout::Neardayo,
polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?,
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?, sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
}, },
"websocket" => OutputMode::Websocket { "websocket" => OutputMode::Websocket {
url: v["outputWebsocketUrl"].as_str()?.to_string(), url: v["outputWebsocketUrl"].as_str()?.to_string(),
polling: OutputPolling::from_str(v["outputPolling"].as_str()?)?,
}, },
_ => panic!("Invalid output mode"), _ => panic!("Invalid output mode"),
}, },
@ -158,6 +204,7 @@ impl Config {
"ledMode": "none", "ledMode": "none",
"keyboardSensitivity": 20, "keyboardSensitivity": 20,
"outputWebsocketUrl": "localhost:3000", "outputWebsocketUrl": "localhost:3000",
"outputPolling": "60",
"ledSensitivity": 20, "ledSensitivity": 20,
"ledWebsocketUrl": "localhost:3001", "ledWebsocketUrl": "localhost:3001",
"ledSerialPort": "COM5" "ledSerialPort": "COM5"
@ -176,6 +223,28 @@ impl Config {
return Some(Box::new(log_path)); 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).unwrap();
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_saved_path() -> Option<Box<PathBuf>> { fn get_saved_path() -> Option<Box<PathBuf>> {
let project_dir = ProjectDirs::from("me", "impress labs", "slidershim").unwrap(); let project_dir = ProjectDirs::from("me", "impress labs", "slidershim").unwrap();
let config_dir = project_dir.config_dir(); let config_dir = project_dir.config_dir();

View File

@ -102,8 +102,8 @@ impl HidDeviceJob {
.zip(led_state.led_state.chunks(3).rev()) .zip(led_state.led_state.chunks(3).rev())
{ {
buf_chunk[0] = state_chunk[2]; buf_chunk[0] = state_chunk[2];
buf_chunk[1] = state_chunk[1]; buf_chunk[1] = state_chunk[0];
buf_chunk[2] = state_chunk[0]; buf_chunk[2] = state_chunk[1];
} }
buf.data[96..240].fill(0); buf.data[96..240].fill(0);
}, },
@ -140,8 +140,8 @@ impl HidDeviceJob {
.zip(led_state.led_state.chunks(3).rev()) .zip(led_state.led_state.chunks(3).rev())
{ {
buf_chunk[0] = state_chunk[2]; buf_chunk[0] = state_chunk[2];
buf_chunk[1] = state_chunk[1]; buf_chunk[1] = state_chunk[0];
buf_chunk[2] = state_chunk[0]; buf_chunk[2] = state_chunk[1];
} }
buf.data[96..240].fill(0); buf.data[96..240].fill(0);
}, },

View File

@ -137,7 +137,7 @@ impl LedJob {
{ {
led_state.paint(idx, &[(*buf_chunk)[1], (*buf_chunk)[2], (*buf_chunk)[0]]); led_state.paint(idx, &[(*buf_chunk)[1], (*buf_chunk)[2], (*buf_chunk)[0]]);
} }
println!("leds {:?}", led_state.led_state); // println!("leds {:?}", led_state.led_state);
} }
} }
} }
@ -187,23 +187,21 @@ impl ThreadJob for LedJob {
LedMode::Serial { .. } => { LedMode::Serial { .. } => {
if let Some(serial_port) = self.serial_port.as_mut() { if let Some(serial_port) = self.serial_port.as_mut() {
let mut serial_data_avail = serial_port.bytes_to_read().unwrap_or(0); let mut serial_data_avail = serial_port.bytes_to_read().unwrap_or(0);
if serial_data_avail < 100 { if serial_data_avail >= 100 {
return; 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 % 100 == 0 { if serial_data_avail > 0 {
let mut serial_buffer_working = Buffer::new(); serial_port.clear(ClearBuffer::All).unwrap();
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();
} }
} }
} }

View File

@ -33,7 +33,7 @@ impl Manager {
let join_handle = thread::spawn(move || { let join_handle = thread::spawn(move || {
info!("Manager thread started"); info!("Manager thread started");
let runtime = tokio::runtime::Builder::new_multi_thread() let runtime = tokio::runtime::Builder::new_multi_thread()
.worker_threads(1) .worker_threads(2)
.enable_all() .enable_all()
.build() .build()
.unwrap(); .unwrap();

View File

@ -12,6 +12,7 @@ pub trait OutputHandler: Send {
pub struct OutputJob { pub struct OutputJob {
state: FullState, state: FullState,
t: u64,
sensitivity: u8, sensitivity: u8,
handler: Box<dyn OutputHandler>, handler: Box<dyn OutputHandler>,
} }
@ -21,17 +22,21 @@ impl OutputJob {
match mode { match mode {
OutputMode::Keyboard { OutputMode::Keyboard {
layout, layout,
polling,
sensitivity, sensitivity,
} => Self { } => Self {
state: state.clone(), state: state.clone(),
t: polling.to_t_u64(),
sensitivity: *sensitivity, sensitivity: *sensitivity,
handler: Box::new(KeyboardOutput::new(layout.clone())), handler: Box::new(KeyboardOutput::new(layout.clone())),
}, },
OutputMode::Gamepad { OutputMode::Gamepad {
layout, layout,
polling,
sensitivity, sensitivity,
} => Self { } => Self {
state: state.clone(), state: state.clone(),
t: polling.to_t_u64(),
sensitivity: *sensitivity, sensitivity: *sensitivity,
handler: Box::new(GamepadOutput::new(layout.clone())), handler: Box::new(GamepadOutput::new(layout.clone())),
}, },
@ -53,7 +58,7 @@ impl ThreadJob for OutputJob {
} }
self.handler.tick(&flat_controller_state); self.handler.tick(&flat_controller_state);
thread::sleep(Duration::from_millis(10)); thread::sleep(Duration::from_millis(self.t));
} }
fn teardown(&mut self) { fn teardown(&mut self) {

View File

@ -51,10 +51,7 @@
"active": false "active": false
}, },
"allowlist": { "allowlist": {
"all": false, "all": false
"shell": {
"open": true
}
}, },
"windows": [ "windows": [
{ {
@ -62,7 +59,9 @@
"width": 500, "width": 500,
"height": 550, "height": 550,
"resizable": false, "resizable": false,
"fullscreen": false "fullscreen": false,
"decorations": false,
"transparent": true
} }
], ],
"security": { "security": {

View File

@ -11,6 +11,7 @@
let ledMode = "none"; let ledMode = "none";
let keyboardSensitivity = 20; let keyboardSensitivity = 20;
let outputPolling = "100";
let outputWebsocketUrl = "http://localhost:3000"; let outputWebsocketUrl = "http://localhost:3000";
let ledSensitivity = 20; let ledSensitivity = 20;
let ledWebsocketUrl = "http://localhost:3001"; let ledWebsocketUrl = "http://localhost:3001";
@ -28,7 +29,6 @@
let polling = null; let polling = null;
let tick = 0; let tick = 0;
let previewData = Array(131).fill(0); let previewData = Array(131).fill(0);
let logfile = "";
function updatePolling(enabled) { function updatePolling(enabled) {
if (!!polling) { if (!!polling) {
@ -44,6 +44,7 @@
// console.log(enabled, polling, tick); // console.log(enabled, polling, tick);
} }
// Receive events
onMount(async () => { onMount(async () => {
// console.log(emit, listen); // console.log(emit, listen);
await listen("showConfig", (event) => { await listen("showConfig", (event) => {
@ -51,7 +52,9 @@
deviceMode = payload.deviceMode || "none"; deviceMode = payload.deviceMode || "none";
outputMode = payload.outputMode || "none"; outputMode = payload.outputMode || "none";
ledMode = payload.ledMode || "none"; ledMode = payload.ledMode || "none";
keyboardSensitivity = payload.keyboardSensitivity || 20; keyboardSensitivity = payload.keyboardSensitivity || 20;
outputPolling = payload.outputPolling || "100";
outputWebsocketUrl = outputWebsocketUrl =
payload.outputWebsocketUrl || "http://localhost:3000/"; payload.outputWebsocketUrl || "http://localhost:3000/";
ledSensitivity = payload.ledSensitivity || 20; ledSensitivity = payload.ledSensitivity || 20;
@ -69,10 +72,6 @@
); );
}); });
await listen("updateLogPath", (event) => {
logfile = event.payload as string;
});
await emit("ready", ""); await emit("ready", "");
updatePolling(true); updatePolling(true);
@ -86,6 +85,8 @@
}); });
}); });
// Emit events
async function setConfig() { async function setConfig() {
console.log("Updating config"); console.log("Updating config");
await emit( await emit(
@ -95,6 +96,7 @@
outputMode, outputMode,
ledMode, ledMode,
keyboardSensitivity, keyboardSensitivity,
outputPolling,
outputWebsocketUrl, outputWebsocketUrl,
ledSensitivity, ledSensitivity,
ledWebsocketUrl, ledWebsocketUrl,
@ -114,17 +116,21 @@
} }
async function logs() { async function logs() {
await open(logfile); await emit("openLogfile", "");
}
async function brokenithmQr() {
await emit("openBrokenithmQr");
} }
</script> </script>
<main class="main"> <main class="main">
<div class="row"> <!-- <div class="row titlebar" data-tauri-drag-region> -->
<div class="header"> <!-- <div class="header"> -->
<!-- slidershim by @4yn --> <!-- slidershim by @4yn -->
slidershim <!-- slidershim -->
</div> <!-- </div> -->
</div> <!-- </div> -->
<!-- <div> <!-- <div>
{debugstr} {debugstr}
</div> --> </div> -->
@ -149,7 +155,7 @@
<div class="label" /> <div class="label" />
<div class="input"> <div class="input">
<div class="serverlist"> <div class="serverlist">
Brokenithm open at: Brokenithm server running, access at one of:
<pre> <pre>
{ips.map((x) => `http://${x}:1606/`).join("\n")} {ips.map((x) => `http://${x}:1606/`).join("\n")}
</pre> </pre>
@ -175,7 +181,21 @@
</select> </select>
</div> </div>
</div> </div>
{#if outputMode === "gamepad-voltex"} {#if outputMode !== "none"}
<div class="row">
<div class="label">Output Polling</div>
<div class="input">
<select bind:value={outputPolling} on:change={markDirty}>
<option value="60">60 Hz</option>
<option value="100">100 Hz</option>
<option value="330">330 Hz</option>
<option value="500">500 Hz</option>
<option value="1000">1000 Hz</option>
</select>
</div>
</div>
{/if}
{#if outputMode.slice(0, 7) === "gamepad"}
<div class="row"> <div class="row">
<div class="label" /> <div class="label" />
<div class="input"> <div class="input">
@ -187,7 +207,9 @@
{/if} {/if}
{#if outputMode.slice(0, 2) === "kb" && deviceMode.slice(0, 10) !== "brokenithm"} {#if outputMode.slice(0, 2) === "kb" && deviceMode.slice(0, 10) !== "brokenithm"}
<div class="row"> <div class="row">
<div class="label">Sensitivity</div> <div class="label" title="Larger means harder to trigger">
Sensitivity
</div>
<div class="input"> <div class="input">
<input <input
type="number" type="number"
@ -242,7 +264,9 @@
</div> </div>
{#if ledMode.slice(0, 8) === "reactive" && deviceMode.slice(0, 10) !== "brokenithm"} {#if ledMode.slice(0, 8) === "reactive" && deviceMode.slice(0, 10) !== "brokenithm"}
<div class="row"> <div class="row">
<div class="label">Sensitivity</div> <div class="label" title="Larger means harder to trigger">
Sensitivity
</div>
<div class="input"> <div class="input">
<input <input
type="number" type="number"
@ -314,8 +338,9 @@
> >
<button on:click={async () => await hide()}>Hide</button> <button on:click={async () => await hide()}>Hide</button>
<button on:click={async () => await quit()}>Quit</button> <button on:click={async () => await quit()}>Quit</button>
{#if !!logfile.length} <button on:click={async () => await logs()}>Logs</button>
<button on:click={async () => await logs()}>Logs</button> {#if deviceMode.slice(0, 10) === "brokenithm"}
<button on:click={async () => await brokenithmQr()}>Brokenithm QR</button>
{/if} {/if}
</div> </div>
</main> </main>

View File

@ -5,12 +5,9 @@
let botDatas = Array(16).fill(0); let botDatas = Array(16).fill(0);
let airDatas = Array(6).fill(0); let airDatas = Array(6).fill(0);
let extraDatas = Array(3).fill(0); let extraDatas = Array(3).fill(0);
let ledDatas = Array(31)
.fill(0) let ledDatas = Array(16).fill("#ff0");
.map((_, idx) => ({ let ledDividerDatas = Array(15).fill("#ff0");
color: !!(idx % 2) ? "#f0f" : "#ff0",
spec: idx % 2,
}));
$: { $: {
if (data.length === 134) { if (data.length === 134) {
@ -27,9 +24,14 @@
} }
for (let i = 0; i < 31; i++) { for (let i = 0; i < 31; i++) {
ledDatas[i].color = `rgb(${data[41 + i * 3]}, ${data[42 + i * 3]}, ${ let rgbstr = `rgb(${data[41 + i * 3]}, ${data[42 + i * 3]}, ${
data[43 + i * 3] data[43 + i * 3]
})`; })`;
if (i % 2 == 0) {
ledDatas[i / 2] = rgbstr;
} else {
ledDividerDatas[(i - 1) / 2] = rgbstr;
}
} }
} }
} }
@ -44,11 +46,18 @@
<div class="ground"> <div class="ground">
<div class="ground-led"> <div class="ground-led">
<div class="ground-row"> <div class="ground-row">
{#each ledDatas as ledData, idx (idx)}
<div class={`ground-led-0`} style={`background-color: ${ledData}`} />
{/each}
</div>
</div>
<div class="ground-led">
<div class="ground-row ground-row-divider">
<div class="ground-led-2" /> <div class="ground-led-2" />
{#each ledDatas as { color, spec }, idx (idx)} {#each ledDividerDatas as ledDividerData, idx (idx)}
<div <div
class={`ground-led-${spec}`} class="ground-led-1"
style={`background-color: ${color}`} style={`background-color: ${ledDividerData}`}
/> />
{/each} {/each}
<div class="ground-led-2" /> <div class="ground-led-2" />