Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
856d2ea49a | ||
|
|
d24bd93084 | ||
|
|
1802ca12fd | ||
|
|
87f6c7db95 | ||
|
|
ec0cc7d2e4 | ||
|
|
169d981e6f | ||
|
|
2fb8995e68 | ||
|
|
3a3e339aca | ||
|
|
cd2c1d73c0 | ||
|
|
d89de5e6fc | ||
|
|
8fbb9e4d9f | ||
|
|
61505c2375 | ||
|
|
8c96763a8a | ||
|
|
748a76cf06 | ||
|
|
ff6e971f62 | ||
|
|
bcff45da56 | ||
|
|
ec1a146449 | ||
|
|
63d2724f35 | ||
|
|
7c0d5af821 | ||
|
|
ca06dc025f | ||
|
|
60098bba46 | ||
|
|
358b53c0be | ||
|
|
47b90603e2 | ||
|
|
af16cd43c8 | ||
|
|
e9510bcfa0 | ||
|
|
dedc8cace4 | ||
|
|
35da7d4874 |
@@ -2,9 +2,9 @@
|
||||
|
||||
# [slidershim](https://github.com/4yn/slidershim)
|
||||
|
||||
Software adapter for various Chunithm slider controllers with a built-in Brokenithm web controller. Has support for keyboard/gamepad output and LED lighting.
|
||||
Software adapter for various Chunithm slider controllers with a built-in Brokenithm web controller. Has support for keyboard/gamepad output and LED lighting. Requires Windows 8 or later.
|
||||
|
||||
[Download link](https://github.com/4yn/slidershim/releases/latest)
|
||||
[Download link](https://github.com/4yn/slidershim/releases/latest) | [Demo Video (Brokestalgia)](https://youtu.be/1PEtBP3cOew)
|
||||
|
||||
> Starting 0.2.0, there are reports that windows defender may block this software. You may need to disable windows defender the first time you run slidershim. If you are not comfortable with this, you can [download an older version here](https://github.com/4yn/slidershim/releases/tag/v0.1.4) that is known to be OK with windows defender.
|
||||
|
||||
@@ -12,8 +12,26 @@ Software adapter for various Chunithm slider controllers with a built-in Brokeni
|
||||
|
||||
## Changelog
|
||||
|
||||
- v0.2.5
|
||||
- Add 6k and 4k keyboard output options
|
||||
- Add settings for reactive LED colors
|
||||
- v0.2.4
|
||||
- Restore support for older tablet devices for brokenithm by compiling brokenithm JS with older babel preset
|
||||
- Fix keys staying pressed even after brokenithm controller is closed
|
||||
- New Brokestalgia 28k web touch controller option
|
||||
- v0.2.3
|
||||
- Fix first and last buttons not working with brokenithm
|
||||
- Add option to disable air strings for controllers
|
||||
- Add option to slow down lighting updates
|
||||
- v0.2.2
|
||||
- Retcon serial slider name
|
||||
- Fix crash on some systems due to time math
|
||||
- Add link to repo
|
||||
- v0.2.1
|
||||
- Reactive + Rainbow mode
|
||||
- Fixed byte order for serial slider lights
|
||||
- v0.2.0
|
||||
- Added support for Hori Project DIVA slider protocol
|
||||
- Added support for serial slider protocol
|
||||
- Added 16 key keyboard layout
|
||||
- v0.1.4
|
||||
- Initial public release
|
||||
@@ -21,7 +39,7 @@ Software adapter for various Chunithm slider controllers with a built-in Brokeni
|
||||
## Setup Instructions
|
||||
|
||||
1. [Download here](https://github.com/4yn/slidershim/releases/latest)
|
||||
2. Extract to any location you like.
|
||||
2. Run the msi installer (recommended for first run) or extract zip to any location you like.
|
||||
3. Run `slidershim.exe`.
|
||||
4. Pick your input device, output method and preferred lighting.
|
||||
5. Click apply.
|
||||
@@ -39,9 +57,10 @@ Software adapter for various Chunithm slider controllers with a built-in Brokeni
|
||||
|
||||
1. Set the device option to "Brokenithm" and apply.
|
||||
2. Allow firewall access to `slidershim.exe`
|
||||
> For more info on setting up firewall, check out [instructions at brokenithm-kb](https://github.com/4yn/brokenithm-kb#setup)
|
||||
3. Connect a touch device to the same wifi or local network as your windows machine
|
||||
> Windows hotspot works too, but make sure to give "public" firewall access
|
||||
4. Open the web controller in your tablet device web browser. One of the links should work.
|
||||
4. Open the web controller in your touch device web browser. One of the links should work.
|
||||
1. Either manually type in the IP address if you know what IP it is;
|
||||
2. Or scan the generated "Broknenithm QR" to get a list of clickable links and save some typing.
|
||||
5. (Optional) Add the webpage to home screen and/or enable guided access to avoid accidentally exiting the web controller.
|
||||
@@ -54,13 +73,20 @@ Voltex layout disables air tower, Neardayo layout enables air tower.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<details>
|
||||
<summary><strong>slidershim crashes when I am running it for the first time</strong></summary>
|
||||
|
||||
- Use the .msi installer file instead of the .zip. You may be missing some windows addons that the installer will take care of (specifically Edge WebView).
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>My controller is not recognised</strong></summary>
|
||||
|
||||
- slidershim supports the GAMO2 Tasoller and Yuancon Laverita v2 controllers on HID firmware.
|
||||
- If you are using a Tasoller controller, make sure it has the correct HID firmware installed.
|
||||
- If your hardware controller is not one of these, it will not work.
|
||||
- If you have a device that communicates using serial protocol, chances are it will work with the "Diva Slider over Serial" option
|
||||
- If you have a device that communicates using serial protocol, chances are it will work with the "Slider over Serial" option
|
||||
- Hotplug is not supported. If you plugged in the controller after starting slidershim, re-connect your controller by just clicking the "Apply" button (even if it is grey).
|
||||
|
||||
</details>
|
||||
@@ -73,6 +99,13 @@ Voltex layout disables air tower, Neardayo layout enables air tower.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>Keyboard / XBOX Controller output feels sluggish</strong></summary>
|
||||
|
||||
- Increase output polling rate. This does not change how fast your controller is updated, but it does reduce lag with keyboard / XBOX controller simulation.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>The Brokenithm controller cannot be loaded</strong></summary>
|
||||
|
||||
@@ -92,9 +125,10 @@ Voltex layout disables air tower, Neardayo layout enables air tower.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><strong>slidershim 2.0 crashes right after I turn it on</strong></summary>
|
||||
<summary><strong>slidershim 0.2.0 crashes right after I turn it on</strong></summary>
|
||||
|
||||
I'm not sure why this happens. I suspect some cases are because of windows defender and other antivirus software stopping the process while running. Try disabling antivirus or using an [older version of slidershim](https://github.com/4yn/slidershim/releases/tag/v0.1.4).
|
||||
- I'm not sure why this happens. I suspect some cases are because of windows defender and other antivirus software stopping the process while running.
|
||||
- Try disabling antivirus or using an [older version of slidershim](https://github.com/4yn/slidershim/releases/tag/v0.1.4).
|
||||
|
||||
</details>
|
||||
|
||||
@@ -118,11 +152,23 @@ I'm not sure why this happens. I suspect some cases are because of windows defen
|
||||
- Built with Rust, [tauri](https://github.com/tauri-apps/tauri) + [Svelte](https://github.com/sveltejs/svelte), [rusb](https://github.com/a1ien/rusb), [vigem](https://github.com/ViGEm/ViGEmClient)[-client](https://github.com/CasualX/vigem-client), [hyper](https://github.com/hyperium/hyper/), [tokio-](https://github.com/snapview/tokio-tungstenite)[tungstenite](https://github.com/snapview/tungstenite-rs), [serialport](https://crates.io/crates/serialport), [dtolnay/cxx](https://github.com/dtolnay/cxx) and [wjwwood/serial](https://github.com/wjwwood/serial)
|
||||
- USB device and serial polling is done on a dedicated thread while everything else is done on a async runtime.
|
||||
|
||||
### Building from Source
|
||||
|
||||
1. Install [Visual Studio Community 2019/2022](https://visualstudio.microsoft.com/vs/) with C++ developer kit
|
||||
2. Install [rustup](https://rustup.rs/) with nightly toolchain
|
||||
3. Install [node.js](https://nodejs.org/en/download/) and [yarn](https://www.npmjs.com/package/yarn)
|
||||
4. (Optional) Setup [VSCode](https://code.visualstudio.com/) with [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=matklad.rust-analyzer)
|
||||
5. Clone this repository
|
||||
6. (Optional) If using powershell, `Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass` to whitelist `yarn` script
|
||||
7. `yarn install`
|
||||
8. `yarn tauri dev` / `yarn tauri build`
|
||||
|
||||
### Repository Layout
|
||||
|
||||
- [`public`](./public) / [`src`](./src): Svelte frontend
|
||||
- [`src-tauri`](./src-tauri): Tauri adapter between frontend and backend
|
||||
- [`src-slider_io`](./src-slider_io): Backend logic for handling usb, brokenithm, output emulation and lighting.
|
||||
- [`src-wwserial`](./src-wwserial): FFI to [wjwwood/serial](https://github.com/wjwwood/serial) using [dtolnay/cxx](https://github.com/dtolnay/cxx). For some reason [serialport](https://crates.io/crates/serialport) does not work well with hardware devices, so we use this as an alternative.
|
||||
- [`res`](./res): Miscellaneous tools such as Brokenithm QR parser and keyboard layout visualisation.
|
||||
|
||||
## Planned Features
|
||||
|
||||
Generated
-11683
File diff suppressed because it is too large
Load Diff
+31
-4
@@ -16,10 +16,6 @@ body {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 1rem;
|
||||
@@ -75,10 +71,32 @@ body {
|
||||
|
||||
.main {
|
||||
margin-top: 2rem;
|
||||
height: calc(100% - 2rem);
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.preview-row {
|
||||
flex: 0 1;
|
||||
}
|
||||
|
||||
.options {
|
||||
padding: 0.5rem 0.25rem;
|
||||
|
||||
flex: 1 1;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: stretch;
|
||||
justify-content: flex-start;
|
||||
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.buttons-row {
|
||||
flex: 0 1;
|
||||
}
|
||||
|
||||
.row,
|
||||
@@ -106,6 +124,10 @@ body {
|
||||
flex: 2 2 0;
|
||||
}
|
||||
|
||||
.comment {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.serverlist {
|
||||
width: 100%;
|
||||
max-height: 5rem;
|
||||
@@ -113,6 +135,11 @@ body {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.iplist {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
width: 100%;
|
||||
|
||||
+50
-2
@@ -93,6 +93,14 @@
|
||||
grid: repeat(1, 1fr) / repeat(8, 1fr);
|
||||
}
|
||||
|
||||
.ground.ground-6 {
|
||||
grid: repeat(1, 1fr) / 3fr 2fr 3fr 3fr 2fr 3fr;
|
||||
}
|
||||
|
||||
.ground.ground-4 {
|
||||
grid: repeat(1, 1fr) / repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.ground.ground-voltex {
|
||||
grid: repeat(2, 1fr) / repeat(8, 1fr);
|
||||
grid-template-areas:
|
||||
@@ -261,7 +269,7 @@
|
||||
</div>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<div class="name">Keyboard 16-zone, Tasoller (Bottom Half) Layout</div>
|
||||
<div class="name">Keyboard 16-zone, Linear</div>
|
||||
<div class="ground ground-16">
|
||||
<div>A</div>
|
||||
<div>Z</div>
|
||||
@@ -295,7 +303,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout">
|
||||
<div class="name">Keyboard 8-zone, Deemo Layout</div>
|
||||
<div class="name">Keyboard 8-zone, Linear</div>
|
||||
<div class="ground ground-8">
|
||||
<div>A</div>
|
||||
<div>S</div>
|
||||
@@ -317,6 +325,46 @@
|
||||
<div>[disabled]</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout">
|
||||
<div class="name">Keyboard 6-zone, Linear</div>
|
||||
<div class="ground ground-6">
|
||||
<div>S</div>
|
||||
<div>D</div>
|
||||
<div>F</div>
|
||||
<div>J</div>
|
||||
<div>K</div>
|
||||
<div>L</div>
|
||||
</div>
|
||||
<div class="air air-single">
|
||||
<div>
|
||||
<span style="writing-mode: vertical-lr">[space]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="extra">
|
||||
<div>[disabled]</div>
|
||||
<div>[disabled]</div>
|
||||
<div>[disabled]</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout">
|
||||
<div class="name">Keyboard 4-zone, Linear</div>
|
||||
<div class="ground ground-4">
|
||||
<div>D</div>
|
||||
<div>F</div>
|
||||
<div>J</div>
|
||||
<div>K</div>
|
||||
</div>
|
||||
<div class="air air-single">
|
||||
<div>
|
||||
<span style="writing-mode: vertical-lr">[space]</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="extra">
|
||||
<div>[disabled]</div>
|
||||
<div>[disabled]</div>
|
||||
<div>[disabled]</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout">
|
||||
<div class="name">Keyboard 10-zone, Voltex/Neardayo Layout</div>
|
||||
<div class="ground ground-voltex">
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 73 KiB |
@@ -3,7 +3,9 @@ extern crate slider_io;
|
||||
use std::io;
|
||||
|
||||
use slider_io::{
|
||||
device::brokenithm::BrokenithmJob, shared::worker::AsyncHaltableWorker, state::SliderState,
|
||||
device::{brokenithm::BrokenithmJob, config::BrokenithmSpec},
|
||||
shared::worker::AsyncHaltableWorker,
|
||||
state::SliderState,
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
@@ -14,7 +16,10 @@ async fn main() {
|
||||
|
||||
let state = SliderState::new();
|
||||
|
||||
let _worker = AsyncHaltableWorker::new("brokenithm", BrokenithmJob::new(&state, &false, &false));
|
||||
let _worker = AsyncHaltableWorker::new(
|
||||
"brokenithm",
|
||||
BrokenithmJob::new(&state, &BrokenithmSpec::Nostalgia, &false),
|
||||
);
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input).unwrap();
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
extern crate slider_io;
|
||||
|
||||
use std::{
|
||||
io,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use std::io;
|
||||
|
||||
use slider_io::{device::diva, shared::worker::ThreadJob, state::SliderState};
|
||||
|
||||
|
||||
+16
-10
@@ -28,18 +28,23 @@ impl Config {
|
||||
|
||||
fn default() -> Self {
|
||||
Self::from_str(
|
||||
r#"{
|
||||
r##"{
|
||||
"deviceMode": "none",
|
||||
"devicePolling": "100",
|
||||
"outputMode": "none",
|
||||
"ledMode": "none",
|
||||
"disableAirStrings": false,
|
||||
"divaSerialPort": "COM1",
|
||||
"divaBrightness": 63,
|
||||
"keyboardSensitivity": 20,
|
||||
"outputWebsocketUrl": "localhost:3000",
|
||||
"outputPolling": "100",
|
||||
"outputWebsocketUrl": "localhost:3000",
|
||||
"ledFaster": false,
|
||||
"ledColorActive": "#ff00ff",
|
||||
"ledColorInactive": "#ffff00",
|
||||
"ledSensitivity": 20,
|
||||
"ledWebsocketUrl": "localhost:3001",
|
||||
"ledSerialPort": "COM5"
|
||||
}"#,
|
||||
}"##,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
@@ -55,12 +60,13 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn load() -> Self {
|
||||
Self::load_saved()
|
||||
.or_else(|| {
|
||||
warn!("Config loading from file failed, using default");
|
||||
Some(Self::default())
|
||||
})
|
||||
.unwrap()
|
||||
let t = Self::load_saved();
|
||||
warn!("{:?}", t);
|
||||
t.or_else(|| {
|
||||
warn!("Config loading from file failed, using default");
|
||||
Some(Self::default())
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn save(&self) -> Option<()> {
|
||||
|
||||
@@ -40,23 +40,23 @@ impl Context {
|
||||
match &config.device_mode {
|
||||
DeviceMode::None => (None, None, None),
|
||||
DeviceMode::Brokenithm {
|
||||
ground_only,
|
||||
spec,
|
||||
lights_enabled,
|
||||
} => (
|
||||
None,
|
||||
None,
|
||||
Some(AsyncHaltableWorker::new(
|
||||
"brokenithm",
|
||||
BrokenithmJob::new(&state, ground_only, lights_enabled),
|
||||
BrokenithmJob::new(&state, spec, lights_enabled),
|
||||
)),
|
||||
),
|
||||
DeviceMode::Hardware { spec } => (
|
||||
DeviceMode::Hardware { spec, disable_air } => (
|
||||
{
|
||||
let timer = LoopTimer::new();
|
||||
timers.push(("d", timer.fork()));
|
||||
Some(ThreadWorker::new(
|
||||
"device",
|
||||
HidJob::from_config(&state, spec),
|
||||
HidJob::from_config(&state, spec, disable_air),
|
||||
timer,
|
||||
))
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>slidershim-brokenithm</title>
|
||||
<meta charset="utf8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<link rel="apple-touch-icon" sizes="192x192" href="/icon.png" />
|
||||
<style>
|
||||
#fullscreen {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
background: #000000;
|
||||
color: hotpink;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
touch-action: none;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.air-container {
|
||||
/* display: flex; */
|
||||
display: none;
|
||||
flex-flow: column nowrap;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.touch-container {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: stretch;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.grow > * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.key {
|
||||
flex: 1;
|
||||
border: 1px solid green;
|
||||
}
|
||||
|
||||
.key[data-active] {
|
||||
background-color: hotpink;
|
||||
}
|
||||
|
||||
.key.air[data-active] {
|
||||
background-color: skyblue;
|
||||
}
|
||||
|
||||
canvas {
|
||||
-ms-interpolation-mode: nearest-neighbor;
|
||||
image-rendering: crisp-edges;
|
||||
image-rendering: pixelated;
|
||||
touch-action: none;
|
||||
margin: 0px -1.5625vw;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="fullscreen">
|
||||
<!-- Offset for LED display -->
|
||||
<div class="container">
|
||||
<div class="air-container grow"></div>
|
||||
<div class="touch-container grow">
|
||||
<canvas id="canvas" width="33" height="1"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Hitbox Divs -->
|
||||
<div class="container" id="main">
|
||||
<div class="air-container grow">
|
||||
<div class="air key" data-air="1" data-kflag="5"></div>
|
||||
<div class="air key" data-air="1" data-kflag="4"></div>
|
||||
<div class="air key" data-air="1" data-kflag="3"></div>
|
||||
<div class="air key" data-air="1" data-kflag="2"></div>
|
||||
<div class="air key" data-air="1" data-kflag="1"></div>
|
||||
<div class="air key" data-air="1" data-kflag="0"></div>
|
||||
</div>
|
||||
<div class="touch-container grow">
|
||||
<div class="key" data-kflag="0"></div>
|
||||
<div class="key" data-kflag="1"></div>
|
||||
<div class="key" data-kflag="2"></div>
|
||||
<div class="key" data-kflag="3"></div>
|
||||
<div class="key" data-kflag="4"></div>
|
||||
<div class="key" data-kflag="5"></div>
|
||||
<div class="key" data-kflag="6"></div>
|
||||
<div class="key" data-kflag="7"></div>
|
||||
<div class="key" data-kflag="8"></div>
|
||||
<div class="key" data-kflag="9"></div>
|
||||
<div class="key" data-kflag="10"></div>
|
||||
<div class="key" data-kflag="11"></div>
|
||||
<div class="key" data-kflag="12"></div>
|
||||
<div class="key" data-kflag="13"></div>
|
||||
<div class="key" data-kflag="14"></div>
|
||||
<div class="key" data-kflag="15"></div>
|
||||
<div class="key" data-kflag="16"></div>
|
||||
<div class="key" data-kflag="17"></div>
|
||||
<div class="key" data-kflag="18"></div>
|
||||
<div class="key" data-kflag="19"></div>
|
||||
<div class="key" data-kflag="20"></div>
|
||||
<div class="key" data-kflag="21"></div>
|
||||
<div class="key" data-kflag="22"></div>
|
||||
<div class="key" data-kflag="23"></div>
|
||||
<div class="key" data-kflag="24"></div>
|
||||
<div class="key" data-kflag="25"></div>
|
||||
<div class="key" data-kflag="26"></div>
|
||||
<div class="key" data-kflag="27"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/config.js"></script>
|
||||
<script>
|
||||
var allAir = true;
|
||||
</script>
|
||||
<script src="/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -46,7 +46,7 @@ const compileKey = (key) => {
|
||||
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,
|
||||
isAir: parseInt(key.dataset.air) ? true : window.allAir || false,
|
||||
prevKeyRef: prev,
|
||||
prevKeyKflag: prev
|
||||
? parseInt(prev.dataset.kflag) + (parseInt(prev.dataset.air) ? 32 : 0)
|
||||
@@ -70,9 +70,9 @@ const compileKeys = () => {
|
||||
keys = document.getElementsByClassName("key");
|
||||
airKeys = [];
|
||||
touchKeys = [];
|
||||
for (var i = 0, key; i < keys.length; i++) {
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
const compiledKey = compileKey(keys[i]);
|
||||
if (!compiledKey.isAir) {
|
||||
if (compiledKey.kflag < 32) {
|
||||
touchKeys.push(compiledKey);
|
||||
} else {
|
||||
airKeys.push(compiledKey);
|
||||
@@ -80,6 +80,38 @@ const compileKeys = () => {
|
||||
allKeys.push(compiledKey);
|
||||
}
|
||||
|
||||
touchKeys.memo = {};
|
||||
airKeys.memo = {};
|
||||
|
||||
touchKeys.getAxis = (x, y) => x;
|
||||
airKeys.getAxis = (x, y) => y;
|
||||
|
||||
var getKey = function (x, y) {
|
||||
var c = this.getAxis(x, y);
|
||||
var res = this.memo[c];
|
||||
if (res === undefined) {
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (isInside(x, y, this[i])) {
|
||||
res = this[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.memo[c] = res;
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
touchKeys.getKey = getKey;
|
||||
airKeys.getKey = getKey;
|
||||
|
||||
for (var i = 0; i < window.outerWidth; i++) {
|
||||
touchKeys.getKey(i, touchKeys[0].top);
|
||||
}
|
||||
|
||||
for (var i = 0; i < window.outerHeight; i++) {
|
||||
airKeys.getKey(airKeys[0].left, i);
|
||||
}
|
||||
|
||||
if (!config.invert) {
|
||||
// Not inverted
|
||||
topKeys = airKeys;
|
||||
@@ -95,15 +127,9 @@ const compileKeys = () => {
|
||||
|
||||
const getKey = (x, y) => {
|
||||
if (y < midline) {
|
||||
for (var i = 0; i < topKeys.length; i++) {
|
||||
if (isInside(x, y, topKeys[i])) return topKeys[i];
|
||||
}
|
||||
return topKeys.getKey(x, y);
|
||||
} else {
|
||||
for (var i = 0; i < bottomKeys.length; i++) {
|
||||
if (isInside(x, y, bottomKeys[i])) {
|
||||
return bottomKeys[i];
|
||||
}
|
||||
}
|
||||
return bottomKeys.getKey(x, y);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -241,9 +267,9 @@ const updateLed = (data) => {
|
||||
canvasData.data[i * 4 + 5] = buf[i * 3 + 1]; // g
|
||||
canvasData.data[i * 4 + 6] = buf[i * 3 + 2]; // b
|
||||
}
|
||||
canvasData.data[0] = buf[0]
|
||||
canvasData.data[1] = buf[1]
|
||||
canvasData.data[2] = buf[2]
|
||||
canvasData.data[0] = buf[0];
|
||||
canvasData.data[1] = buf[1];
|
||||
canvasData.data[2] = buf[2];
|
||||
canvasData.data[128] = buf[90];
|
||||
canvasData.data[129] = buf[91];
|
||||
canvasData.data[130] = buf[92];
|
||||
|
||||
@@ -18,7 +18,7 @@ use tokio::{
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use tungstenite::{handshake, Message};
|
||||
|
||||
use crate::{shared::worker::AsyncHaltableJob, state::SliderState};
|
||||
use crate::{device::config::BrokenithmSpec, shared::worker::AsyncHaltableJob, state::SliderState};
|
||||
|
||||
// https://levelup.gitconnected.com/handling-websocket-and-http-on-the-same-port-with-rust-f65b770722c9
|
||||
|
||||
@@ -36,6 +36,7 @@ async fn error_response() -> Result<Response<Body>, Infallible> {
|
||||
static BROKENITHM_STR_FILES: phf::Map<&'static str, (&'static str, &'static str)> = phf_map! {
|
||||
"app.js" => (include_str!("./brokenithm-www/app.js"), "text/javascript"),
|
||||
"config.js" => (include_str!("./brokenithm-www/config.js"), "text/javascript"),
|
||||
"index-ns.html" => (include_str!("./brokenithm-www/index-ns.html"), "text/html"),
|
||||
"index-go.html" => (include_str!("./brokenithm-www/index-go.html"), "text/html"),
|
||||
"index.html" => (include_str!("./brokenithm-www/index.html"), "text/html"),
|
||||
};
|
||||
@@ -115,13 +116,13 @@ async fn handle_brokenithm(
|
||||
39 => {
|
||||
if chars[0] == 'b' {
|
||||
let mut input_handle = state_handle.input.lock();
|
||||
for (idx, c) in chars[0..32].iter().enumerate() {
|
||||
for (idx, c) in chars[1..33].iter().enumerate() {
|
||||
input_handle.ground[idx] = match *c == '1' {
|
||||
false => 0,
|
||||
true => 255,
|
||||
}
|
||||
}
|
||||
for (idx, c) in chars[32..38].iter().enumerate() {
|
||||
for (idx, c) in chars[33..39].iter().enumerate() {
|
||||
input_handle.air[idx] = match *c == '1' {
|
||||
false => 0,
|
||||
true => 1,
|
||||
@@ -136,12 +137,18 @@ async fn handle_brokenithm(
|
||||
}
|
||||
Message::Close(_) => {
|
||||
info!("Websocket connection closed");
|
||||
let mut input_handle = state_handle.input.lock();
|
||||
input_handle.ground.fill(0);
|
||||
input_handle.air.fill(0);
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Websocket connection error: {}", e);
|
||||
let mut input_handle = state_handle.input.lock();
|
||||
input_handle.ground.fill(0);
|
||||
input_handle.air.fill(0);
|
||||
break;
|
||||
}
|
||||
},
|
||||
@@ -228,7 +235,7 @@ async fn handle_request(
|
||||
request: Request<Body>,
|
||||
remote_addr: SocketAddr,
|
||||
state: SliderState,
|
||||
ground_only: bool,
|
||||
spec: BrokenithmSpec,
|
||||
lights_enabled: bool,
|
||||
) -> Result<Response<Body>, Infallible> {
|
||||
let method = request.method();
|
||||
@@ -246,9 +253,10 @@ async fn handle_request(
|
||||
request.uri().path(),
|
||||
request.headers().contains_key(header::UPGRADE),
|
||||
) {
|
||||
("/", false) | ("/index.html", false) => match ground_only {
|
||||
false => serve_file("index.html").await,
|
||||
true => serve_file("index-go.html").await,
|
||||
("/", false) | ("/index.html", false) => match spec {
|
||||
BrokenithmSpec::Basic => serve_file("index.html").await,
|
||||
BrokenithmSpec::GroundOnly => serve_file("index-go.html").await,
|
||||
BrokenithmSpec::Nostalgia => serve_file("index-ns.html").await,
|
||||
},
|
||||
(filename, false) => serve_file(&filename[1..]).await,
|
||||
("/ws", true) => handle_websocket(request, state, lights_enabled).await,
|
||||
@@ -258,15 +266,15 @@ async fn handle_request(
|
||||
|
||||
pub struct BrokenithmJob {
|
||||
state: SliderState,
|
||||
ground_only: bool,
|
||||
spec: BrokenithmSpec,
|
||||
lights_enabled: bool,
|
||||
}
|
||||
|
||||
impl BrokenithmJob {
|
||||
pub fn new(state: &SliderState, ground_only: &bool, lights_enabled: &bool) -> Self {
|
||||
pub fn new(state: &SliderState, spec: &BrokenithmSpec, lights_enabled: &bool) -> Self {
|
||||
Self {
|
||||
state: state.clone(),
|
||||
ground_only: *ground_only,
|
||||
spec: spec.clone(),
|
||||
lights_enabled: *lights_enabled,
|
||||
}
|
||||
}
|
||||
@@ -276,15 +284,17 @@ impl BrokenithmJob {
|
||||
impl AsyncHaltableJob for BrokenithmJob {
|
||||
async fn run<F: Future<Output = ()> + Send>(self, stop_signal: F) {
|
||||
let state = self.state.clone();
|
||||
let ground_only = self.ground_only;
|
||||
let spec = self.spec.clone();
|
||||
let lights_enabled = self.lights_enabled;
|
||||
let make_svc = make_service_fn(|conn: &AddrStream| {
|
||||
let remote_addr = conn.remote_addr();
|
||||
let make_svc_state = state.clone();
|
||||
let make_spec = spec.clone();
|
||||
async move {
|
||||
Ok::<_, Infallible>(service_fn(move |request: Request<Body>| {
|
||||
let svc_state = make_svc_state.clone();
|
||||
handle_request(request, remote_addr, svc_state, ground_only, lights_enabled)
|
||||
let spec = make_spec.clone();
|
||||
handle_request(request, remote_addr, svc_state, spec, lights_enabled)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,14 +7,22 @@ pub enum HardwareSpec {
|
||||
Yuancon,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum BrokenithmSpec {
|
||||
Basic,
|
||||
GroundOnly,
|
||||
Nostalgia,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DeviceMode {
|
||||
None,
|
||||
Hardware {
|
||||
spec: HardwareSpec,
|
||||
disable_air: bool,
|
||||
},
|
||||
Brokenithm {
|
||||
ground_only: bool,
|
||||
spec: BrokenithmSpec,
|
||||
lights_enabled: bool,
|
||||
},
|
||||
DivaSlider {
|
||||
@@ -29,33 +37,38 @@ impl DeviceMode {
|
||||
"none" => DeviceMode::None,
|
||||
"tasoller-one" => DeviceMode::Hardware {
|
||||
spec: HardwareSpec::TasollerOne,
|
||||
disable_air: v["disableAirStrings"].as_bool()?,
|
||||
},
|
||||
"tasoller-two" => DeviceMode::Hardware {
|
||||
spec: HardwareSpec::TasollerTwo,
|
||||
disable_air: v["disableAirStrings"].as_bool()?,
|
||||
},
|
||||
"yuancon" => DeviceMode::Hardware {
|
||||
spec: HardwareSpec::Yuancon,
|
||||
disable_air: v["disableAirStrings"].as_bool()?,
|
||||
},
|
||||
"diva" => DeviceMode::DivaSlider {
|
||||
port: v["divaSerialPort"].as_str()?.to_string(),
|
||||
brightness: u8::try_from(v["divaBrightness"].as_i64()?).ok()?,
|
||||
},
|
||||
"brokenithm" => DeviceMode::Brokenithm {
|
||||
ground_only: false,
|
||||
spec: match v["disableAirStrings"].as_bool()? {
|
||||
false => BrokenithmSpec::Basic,
|
||||
true => BrokenithmSpec::GroundOnly,
|
||||
},
|
||||
lights_enabled: false,
|
||||
},
|
||||
"brokenithm-led" => DeviceMode::Brokenithm {
|
||||
ground_only: false,
|
||||
spec: match v["disableAirStrings"].as_bool()? {
|
||||
false => BrokenithmSpec::Basic,
|
||||
true => BrokenithmSpec::GroundOnly,
|
||||
},
|
||||
lights_enabled: true,
|
||||
},
|
||||
"brokenithm-ground" => DeviceMode::Brokenithm {
|
||||
ground_only: true,
|
||||
"brokenithm-nostalgia" => DeviceMode::Brokenithm {
|
||||
spec: BrokenithmSpec::Nostalgia,
|
||||
lights_enabled: false,
|
||||
},
|
||||
"brokenithm-ground-led" => DeviceMode::Brokenithm {
|
||||
ground_only: true,
|
||||
lights_enabled: true,
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -318,9 +318,9 @@ impl ThreadJob for DivaSliderJob {
|
||||
.take(31)
|
||||
.zip(lights_handle.ground.chunks(3).rev())
|
||||
{
|
||||
buf_chunk[0] = state_chunk[1];
|
||||
buf_chunk[0] = state_chunk[2];
|
||||
buf_chunk[1] = state_chunk[0];
|
||||
buf_chunk[2] = state_chunk[2];
|
||||
buf_chunk[2] = state_chunk[1];
|
||||
}
|
||||
lights_handle.dirty = false;
|
||||
}
|
||||
@@ -342,6 +342,7 @@ impl ThreadJob for DivaSliderJob {
|
||||
if bytes_written == 0 {
|
||||
warn!("Serial write timeout");
|
||||
}
|
||||
serial_port.flush();
|
||||
// debug!("Serial write {}/{}", bytes_written, data.len());
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ pub struct HidJob {
|
||||
pid: u16,
|
||||
read_endpoint: u8,
|
||||
led_endpoint: u8,
|
||||
disable_air: bool,
|
||||
|
||||
read_callback: HidReadCallback,
|
||||
read_buf: Buffer,
|
||||
@@ -51,6 +52,7 @@ impl HidJob {
|
||||
pid: u16,
|
||||
read_endpoint: u8,
|
||||
led_endpoint: u8,
|
||||
disable_air: bool,
|
||||
read_callback: HidReadCallback,
|
||||
led_type: WriteType,
|
||||
led_callback: HidLedCallback,
|
||||
@@ -61,6 +63,7 @@ impl HidJob {
|
||||
pid,
|
||||
read_endpoint,
|
||||
led_endpoint,
|
||||
disable_air,
|
||||
read_callback,
|
||||
read_buf: Buffer::new(),
|
||||
last_read_buf: Buffer::new(),
|
||||
@@ -71,7 +74,7 @@ impl HidJob {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_config(state: &SliderState, spec: &HardwareSpec) -> Self {
|
||||
pub fn from_config(state: &SliderState, spec: &HardwareSpec, disable_air: &bool) -> Self {
|
||||
match spec {
|
||||
HardwareSpec::TasollerOne => Self::new(
|
||||
state.clone(),
|
||||
@@ -79,6 +82,7 @@ impl HidJob {
|
||||
0x2333,
|
||||
0x84,
|
||||
0x03,
|
||||
*disable_air,
|
||||
|buf, input| {
|
||||
if buf.len != 11 {
|
||||
return;
|
||||
@@ -121,6 +125,7 @@ impl HidJob {
|
||||
0x2333,
|
||||
0x84,
|
||||
0x03,
|
||||
*disable_air,
|
||||
|buf, input| {
|
||||
if buf.len != 36 {
|
||||
return;
|
||||
@@ -157,6 +162,7 @@ impl HidJob {
|
||||
0x2001,
|
||||
0x81,
|
||||
0x02,
|
||||
*disable_air,
|
||||
|buf, input| {
|
||||
if buf.len != 34 {
|
||||
return;
|
||||
@@ -246,6 +252,10 @@ impl ThreadJob for HidJob {
|
||||
work = true;
|
||||
let mut input_handle = self.state.input.lock();
|
||||
(self.read_callback)(&self.read_buf, input_handle.deref_mut());
|
||||
|
||||
if self.disable_air {
|
||||
input_handle.air.fill(0);
|
||||
}
|
||||
swap(&mut self.read_buf, &mut self.last_read_buf);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,68 @@ use serde_json::Value;
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ReactiveLayout {
|
||||
Even { splits: usize },
|
||||
Six,
|
||||
Voltex,
|
||||
Rainbow,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ColorScheme {
|
||||
pub active: [u8; 3],
|
||||
pub inactive: [u8; 3],
|
||||
}
|
||||
|
||||
impl ColorScheme {
|
||||
pub fn from_serde_value(v: &Value) -> Option<Self> {
|
||||
Some(Self {
|
||||
active: [
|
||||
u8::from_str_radix(&v["ledColorActive"].as_str()?[1..3], 16).ok()?,
|
||||
u8::from_str_radix(&v["ledColorActive"].as_str()?[3..5], 16).ok()?,
|
||||
u8::from_str_radix(&v["ledColorActive"].as_str()?[5..7], 16).ok()?,
|
||||
],
|
||||
inactive: [
|
||||
u8::from_str_radix(&v["ledColorInactive"].as_str()?[1..3], 16).ok()?,
|
||||
u8::from_str_radix(&v["ledColorInactive"].as_str()?[3..5], 16).ok()?,
|
||||
u8::from_str_radix(&v["ledColorInactive"].as_str()?[5..7], 16).ok()?,
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
active: [255, 0, 255],
|
||||
inactive: [255, 255, 0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_serde_value_or_default(v: &Value) -> Self {
|
||||
Self::from_serde_value(v)
|
||||
.or(Some(Self {
|
||||
active: [255, 0, 255],
|
||||
inactive: [255, 255, 0],
|
||||
}))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LightsMode {
|
||||
None,
|
||||
Reactive {
|
||||
faster: bool,
|
||||
layout: ReactiveLayout,
|
||||
sensitivity: u8,
|
||||
color: ColorScheme,
|
||||
},
|
||||
Attract {
|
||||
faster: bool,
|
||||
},
|
||||
Attract,
|
||||
Test,
|
||||
Websocket {
|
||||
faster: bool,
|
||||
url: String,
|
||||
},
|
||||
Serial {
|
||||
faster: bool,
|
||||
port: String,
|
||||
},
|
||||
}
|
||||
@@ -27,28 +73,51 @@ impl LightsMode {
|
||||
pub fn from_serde_value(v: &Value) -> Option<Self> {
|
||||
Some(match v["ledMode"].as_str()? {
|
||||
"none" => LightsMode::None,
|
||||
"reactive-4" => LightsMode::Reactive {
|
||||
layout: ReactiveLayout::Even { splits: 4 },
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"reactive-8" => LightsMode::Reactive {
|
||||
layout: ReactiveLayout::Even { splits: 8 },
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"reactive-16" => LightsMode::Reactive {
|
||||
faster: v["ledFaster"].as_bool()?,
|
||||
layout: ReactiveLayout::Even { splits: 16 },
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
color: ColorScheme::from_serde_value_or_default(v),
|
||||
},
|
||||
"reactive-8" => LightsMode::Reactive {
|
||||
faster: v["ledFaster"].as_bool()?,
|
||||
layout: ReactiveLayout::Even { splits: 8 },
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
color: ColorScheme::from_serde_value_or_default(v),
|
||||
},
|
||||
"reactive-6" => LightsMode::Reactive {
|
||||
faster: v["ledFaster"].as_bool()?,
|
||||
layout: ReactiveLayout::Six,
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
color: ColorScheme::from_serde_value_or_default(v),
|
||||
},
|
||||
"reactive-4" => LightsMode::Reactive {
|
||||
faster: v["ledFaster"].as_bool()?,
|
||||
layout: ReactiveLayout::Even { splits: 4 },
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
color: ColorScheme::from_serde_value_or_default(v),
|
||||
},
|
||||
"reactive-rainbow" => LightsMode::Reactive {
|
||||
faster: v["ledFaster"].as_bool()?,
|
||||
layout: ReactiveLayout::Rainbow,
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
color: ColorScheme::default(),
|
||||
},
|
||||
"reactive-voltex" => LightsMode::Reactive {
|
||||
faster: v["ledFaster"].as_bool()?,
|
||||
layout: ReactiveLayout::Voltex,
|
||||
sensitivity: u8::try_from(v["ledSensitivity"].as_i64()?).ok()?,
|
||||
color: ColorScheme::default(),
|
||||
},
|
||||
"attract" => LightsMode::Attract {
|
||||
faster: v["ledFaster"].as_bool()?,
|
||||
},
|
||||
"attract" => LightsMode::Attract,
|
||||
"test" => LightsMode::Test,
|
||||
"websocket" => LightsMode::Websocket {
|
||||
faster: v["ledFaster"].as_bool()?,
|
||||
url: v["ledWebsocketUrl"].as_str()?.to_string(),
|
||||
},
|
||||
"serial" => LightsMode::Serial {
|
||||
faster: v["ledFaster"].as_bool()?,
|
||||
port: v["ledSerialPort"].as_str()?.to_string(),
|
||||
},
|
||||
_ => return None,
|
||||
|
||||
@@ -30,7 +30,15 @@ impl LightsJob {
|
||||
mode: mode.clone(),
|
||||
serial_port: None,
|
||||
started: Instant::now(),
|
||||
timer: interval(Duration::from_micros(33333)),
|
||||
timer: match match mode {
|
||||
LightsMode::Attract { faster, .. } => *faster,
|
||||
LightsMode::Websocket { faster, .. } => *faster,
|
||||
LightsMode::Serial { faster, .. } => *faster,
|
||||
_ => false,
|
||||
} {
|
||||
false => interval(Duration::from_micros(66666)),
|
||||
true => interval(Duration::from_micros(33333)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +48,8 @@ impl LightsJob {
|
||||
serial_buffer: Option<&Buffer>,
|
||||
lights: &mut SliderLights,
|
||||
) {
|
||||
match self.mode {
|
||||
LightsMode::Reactive { layout, .. } => {
|
||||
match &self.mode {
|
||||
LightsMode::Reactive { layout, color, .. } => {
|
||||
let flat_input = flat_input.unwrap();
|
||||
|
||||
match layout {
|
||||
@@ -50,23 +58,55 @@ impl LightsJob {
|
||||
|
||||
let banks: Vec<bool> = flat_input
|
||||
.chunks(32 / splits)
|
||||
.take(splits)
|
||||
.take(*splits)
|
||||
.map(|x| x.contains(&true))
|
||||
.collect();
|
||||
|
||||
for idx in 0..31 {
|
||||
lights.paint(
|
||||
idx,
|
||||
match (idx + 1) % buttons_per_split {
|
||||
0 => &[255, 0, 255],
|
||||
match ((idx + 1) % buttons_per_split, (idx + 1) % 2) {
|
||||
(0, _) => &color.active,
|
||||
(_, 0) => &color.inactive,
|
||||
_ => match banks[idx / buttons_per_split] {
|
||||
true => &[255, 0, 255],
|
||||
false => &[255, 255, 0],
|
||||
true => &color.active,
|
||||
false => &color.inactive,
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
ReactiveLayout::Six => {
|
||||
let banks: Vec<bool> = [0..6, 6..10, 10..16, 16..22, 22..26, 26..32]
|
||||
.into_iter()
|
||||
.map(|x| flat_input[x].contains(&true))
|
||||
.collect();
|
||||
|
||||
for idx in (1..31).step_by(2) {
|
||||
lights.paint(
|
||||
idx,
|
||||
match idx {
|
||||
5 | 9 | 15 | 21 | 25 => &color.active,
|
||||
_ => &color.inactive,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
for (bank_idxs, bank_val) in [0..6, 6..10, 10..16, 16..22, 22..26, 26..32]
|
||||
.into_iter()
|
||||
.zip(banks)
|
||||
{
|
||||
for idx in bank_idxs.step_by(2) {
|
||||
lights.paint(
|
||||
idx,
|
||||
match bank_val {
|
||||
true => &color.active,
|
||||
false => &color.inactive,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
ReactiveLayout::Voltex => {
|
||||
lights.ground.fill(0);
|
||||
|
||||
@@ -114,9 +154,38 @@ impl LightsJob {
|
||||
}
|
||||
}
|
||||
}
|
||||
ReactiveLayout::Rainbow => {
|
||||
let banks: Vec<bool> = flat_input
|
||||
.chunks(2)
|
||||
.take(16)
|
||||
.map(|x| x.contains(&true))
|
||||
.collect();
|
||||
let theta = self
|
||||
.started
|
||||
.elapsed()
|
||||
.div_duration_f64(Duration::from_secs(4))
|
||||
% 1.0;
|
||||
for idx in 0..31 {
|
||||
let slice_theta = (&theta + (idx as f64) / 32.0) % 1.0;
|
||||
let color = Srgb::from_color(Hsv::new(
|
||||
slice_theta * 360.0,
|
||||
match idx % 2 {
|
||||
0 => match banks[idx / 2] {
|
||||
true => 0.2,
|
||||
false => 1.0,
|
||||
},
|
||||
1 => 1.0,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
1.0,
|
||||
))
|
||||
.into_format::<u8>();
|
||||
lights.paint(idx, &[color.red, color.green, color.blue]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LightsMode::Attract => {
|
||||
LightsMode::Attract { .. } => {
|
||||
let theta = self
|
||||
.started
|
||||
.elapsed()
|
||||
@@ -158,7 +227,7 @@ impl LightsJob {
|
||||
impl AsyncJob for LightsJob {
|
||||
async fn setup(&mut self) -> bool {
|
||||
match &self.mode {
|
||||
LightsMode::Serial { port } => {
|
||||
LightsMode::Serial { port, .. } => {
|
||||
info!(
|
||||
"Serial port for led opening at {} {:?}",
|
||||
port.as_str(),
|
||||
|
||||
@@ -14,7 +14,9 @@ pub enum KeyboardLayout {
|
||||
Tasoller,
|
||||
Yuancon,
|
||||
TasollerHalf,
|
||||
Deemo,
|
||||
EightK,
|
||||
SixK,
|
||||
FourK,
|
||||
Voltex,
|
||||
Neardayo,
|
||||
}
|
||||
@@ -81,13 +83,23 @@ impl OutputMode {
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"kb-16-tasoller" => OutputMode::Keyboard {
|
||||
"kb-16" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::TasollerHalf,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"kb-8-deemo" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::Deemo,
|
||||
"kb-8" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::EightK,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"kb-6" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::SixK,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
"kb-4" => OutputMode::Keyboard {
|
||||
layout: KeyboardLayout::FourK,
|
||||
polling: PollingRate::from_str(v["outputPolling"].as_str()?)?,
|
||||
sensitivity: u8::try_from(v["keyboardSensitivity"].as_i64()?).ok()?,
|
||||
},
|
||||
|
||||
@@ -43,7 +43,7 @@ const TASOLLER_HALF_KB_MAP: [usize; 41] = [
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
const DEEMO_KB_MAP: [usize; 41] = [
|
||||
const EIGHT_K_MAP: [usize; 41] = [
|
||||
0x41, 0x41, 0x41, 0x41, // A
|
||||
0x53, 0x53, 0x53, 0x53, // S
|
||||
0x44, 0x44, 0x44, 0x44, // D
|
||||
@@ -56,6 +56,28 @@ const DEEMO_KB_MAP: [usize; 41] = [
|
||||
0x00, 0x00, 0x00, // Disabled
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
const SIX_K_MAP: [usize; 41] = [
|
||||
0x53, 0x53, 0x53, 0x53, 0x53, 0x53, // S
|
||||
0x44, 0x44, 0x44, 0x44, // D
|
||||
0x46, 0x46, 0x46, 0x46, 0x46, 0x46, // F
|
||||
0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, // J
|
||||
0x4b, 0x4b, 0x4b, 0x4b, // K
|
||||
0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, // L
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // VK_SPACE
|
||||
0x00, 0x00, 0x00, // Disabled
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
const FOUR_K_MAP: [usize; 41] = [
|
||||
0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, // D
|
||||
0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, // F
|
||||
0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, // J
|
||||
0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, // K
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // VK_SPACE
|
||||
0x00, 0x00, 0x00, // Disabled
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
const VOLTEX_KB_MAP: [usize; 41] = [
|
||||
0x57, 0x57, 0x57, 0x57, // W
|
||||
@@ -109,7 +131,9 @@ impl KeyboardOutput {
|
||||
KeyboardLayout::Tasoller => &TASOLLER_KB_MAP,
|
||||
KeyboardLayout::Yuancon => &YUANCON_KB_MAP,
|
||||
KeyboardLayout::TasollerHalf => &TASOLLER_HALF_KB_MAP,
|
||||
KeyboardLayout::Deemo => &DEEMO_KB_MAP,
|
||||
KeyboardLayout::EightK => &EIGHT_K_MAP,
|
||||
KeyboardLayout::SixK => &SIX_K_MAP,
|
||||
KeyboardLayout::FourK => &FOUR_K_MAP,
|
||||
KeyboardLayout::Voltex => &VOLTEX_KB_MAP,
|
||||
KeyboardLayout::Neardayo => &VOLTEX_KB_MAP_NEARDAYO,
|
||||
};
|
||||
|
||||
@@ -43,6 +43,7 @@ impl Error for ShimError {
|
||||
pub struct LoopTimer {
|
||||
cap: usize,
|
||||
cur: usize,
|
||||
init: usize,
|
||||
buf: Vec<Instant>,
|
||||
freq: Arc<AtomicF64>,
|
||||
}
|
||||
@@ -52,16 +53,22 @@ impl LoopTimer {
|
||||
Self {
|
||||
cap: 100,
|
||||
cur: 0,
|
||||
buf: vec![Instant::now() - Duration::from_secs(10_000); 100],
|
||||
init: 0,
|
||||
buf: vec![Instant::now(); 100],
|
||||
freq: Arc::new(AtomicF64::new(0.0)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tick(&mut self) {
|
||||
let last = self.buf[self.cur];
|
||||
let now = Instant::now();
|
||||
if self.init < 100 {
|
||||
self.init += 1;
|
||||
}
|
||||
|
||||
self.buf[self.cur] = now;
|
||||
|
||||
let delta = (now - last) / 100 + Duration::from_micros(1);
|
||||
let delta = (now - last) / (self.init as u32) + Duration::from_micros(1);
|
||||
let freq = Duration::from_millis(1000)
|
||||
.div_duration_f64(delta)
|
||||
.clamp(0.0, 9999.0);
|
||||
@@ -75,7 +82,8 @@ impl LoopTimer {
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn reset(&mut self) {
|
||||
self.buf = vec![Instant::now() - Duration::from_secs(10); 100];
|
||||
self.init = 0;
|
||||
self.buf = vec![Instant::now(); 100];
|
||||
self.cur = 0;
|
||||
}
|
||||
|
||||
|
||||
Generated
+574
-623
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "slidershim"
|
||||
version = "0.2.0"
|
||||
version = "0.2.5"
|
||||
description = "slidershim"
|
||||
authors = ["4yn"]
|
||||
license = ""
|
||||
@@ -10,7 +10,7 @@ edition = "2018"
|
||||
build = "src/build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.0.0-beta.4" }
|
||||
tauri-build = { version = "1.0.0-rc.4", features = [] }
|
||||
|
||||
[dependencies]
|
||||
log = "0.4.14"
|
||||
@@ -19,7 +19,7 @@ env_logger = "0.9.0"
|
||||
|
||||
parking_lot = "0.12.0"
|
||||
|
||||
tauri = { version = "1.0.0-beta.8", features = ["shell-open", "system-tray"] }
|
||||
tauri = { version = "1.0.0-rc.4", features = ["system-tray", "window-start-dragging"] }
|
||||
open = "2.0.2"
|
||||
|
||||
slider_io = {path = "../src-slider_io" }
|
||||
|
||||
+20
-2
@@ -2,15 +2,33 @@
|
||||
___| (_) __| | ___ _ __ ___| |__ (_)_ __ ___
|
||||
/ __| | |/ _` |/ _ \ '__/ __| '_ \| | '_ ` _ \
|
||||
\__ \ | | (_| | __/ | \__ \ | | | | | | | | |
|
||||
|___/_|_|\__,_|\___|_| |___/_| |_|_|_| |_| |_| v0.2.0
|
||||
|___/_|_|\__,_|\___|_| |___/_| |_|_|_| |_| |_| v0.2.5
|
||||
===============================================
|
||||
|
||||
https://github.com/4yn/slidershim
|
||||
|
||||
# Changelog
|
||||
|
||||
- v0.2.5
|
||||
- Add 6k and 4k keyboard output options
|
||||
- Add settings for reactive LED colors
|
||||
- v0.2.4
|
||||
- Restore support for older tablet devices for brokenithm by compiling brokenithm JS with older babel preset
|
||||
- Fix keys staying pressed even after brokenithm controller is closed
|
||||
- New Brokestalgia 28k web touch controller option
|
||||
- v0.2.3
|
||||
- Fix first and last buttons not working with brokenithm
|
||||
- Add option to disable air strings for controllers
|
||||
- Add option to slow down lighting updates
|
||||
- v0.2.2
|
||||
- Retcon serial slider name
|
||||
- Fix crash on some systems due to time math
|
||||
- Add link to repo
|
||||
- v0.2.1
|
||||
- Reactive + Rainbow mode
|
||||
- Fixed byte order for serial slider lights
|
||||
- v0.2.0
|
||||
- Added support for Hori Project DIVA slider protocol
|
||||
- Added support for serial slider protocol
|
||||
- Added 16 key keyboard layout
|
||||
- v0.1.4
|
||||
- Initial public release
|
||||
@@ -13,7 +13,8 @@ use std::sync::Arc;
|
||||
use log::info;
|
||||
|
||||
use tauri::{
|
||||
AppHandle, CustomMenuItem, Event, Manager, Runtime, SystemTray, SystemTrayEvent, SystemTrayMenu,
|
||||
AppHandle, CustomMenuItem, Manager, RunEvent, Runtime, SystemTray, SystemTrayEvent,
|
||||
SystemTrayMenu,
|
||||
};
|
||||
|
||||
fn show_window<R: Runtime>(handle: &AppHandle<R>) {
|
||||
@@ -116,6 +117,11 @@ fn main() {
|
||||
}
|
||||
});
|
||||
|
||||
// Show repo
|
||||
app.listen_global("openRepo", |_| {
|
||||
open::that("https://github.com/4yn/slidershim").ok();
|
||||
});
|
||||
|
||||
// UI ready event
|
||||
let app_handle = app.handle();
|
||||
let config_clone = Arc::clone(&config);
|
||||
@@ -180,7 +186,7 @@ fn main() {
|
||||
.expect("error while running tauri application")
|
||||
.run(|app_handle, event| match event {
|
||||
// After app starts
|
||||
Event::CloseRequested { label, api, .. } if label.as_str() == "main" => {
|
||||
RunEvent::CloseRequested { label, api, .. } if label.as_str() == "main" => {
|
||||
api.prevent_close();
|
||||
hide_window(app_handle);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "slidershim",
|
||||
"version": "0.2.0"
|
||||
"version": "0.2.5"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../public",
|
||||
@@ -51,13 +51,16 @@
|
||||
"active": false
|
||||
},
|
||||
"allowlist": {
|
||||
"all": false
|
||||
"all": false,
|
||||
"window": {
|
||||
"startDragging": true
|
||||
}
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"title": "slidershim",
|
||||
"width": 500,
|
||||
"height": 550,
|
||||
"height": 600,
|
||||
"resizable": false,
|
||||
"fullscreen": false,
|
||||
"decorations": false,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
Derived from https://github.com/wjwwood/serial
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2012 William Woodall, John Harrison
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+323
-227
@@ -10,11 +10,15 @@
|
||||
let outputMode = "none";
|
||||
let ledMode = "none";
|
||||
|
||||
let disableAirStrings = false;
|
||||
let divaSerialPort = "COM1";
|
||||
let divaBrightness = 63;
|
||||
let keyboardSensitivity = 20;
|
||||
let outputPolling = "100";
|
||||
let outputWebsocketUrl = "http://localhost:3000";
|
||||
let ledFaster = false;
|
||||
let ledColorActive = "#ff00ff";
|
||||
let ledColorInactive = "#ffff00";
|
||||
let ledSensitivity = 20;
|
||||
let ledWebsocketUrl = "http://localhost:3001";
|
||||
let ledSerialPort = "COM5";
|
||||
@@ -58,12 +62,16 @@
|
||||
outputMode = payload.outputMode || "none";
|
||||
ledMode = payload.ledMode || "none";
|
||||
|
||||
disableAirStrings = payload.disableAirStrings || false;
|
||||
divaSerialPort = payload.divaSerialPort || "COM1";
|
||||
divaBrightness = payload.divaBrightness || 63;
|
||||
keyboardSensitivity = payload.keyboardSensitivity || 20;
|
||||
outputPolling = payload.outputPolling || "100";
|
||||
outputWebsocketUrl =
|
||||
payload.outputWebsocketUrl || "http://localhost:3000/";
|
||||
ledFaster = payload.ledFaster || false;
|
||||
ledColorActive = payload.ledColorActive || "#ff00ff";
|
||||
ledColorInactive = payload.ledColorInactive || "#ffff00";
|
||||
ledSensitivity = payload.ledSensitivity || 20;
|
||||
ledWebsocketUrl = payload.ledWebsocketUrl || "http://localhost:3001";
|
||||
ledSerialPort = payload.ledSerialPort || "COM5";
|
||||
@@ -101,17 +109,22 @@
|
||||
|
||||
async function setConfig() {
|
||||
console.log("Updating config");
|
||||
console.log(disableAirStrings);
|
||||
await emit(
|
||||
"setConfig",
|
||||
JSON.stringify({
|
||||
deviceMode,
|
||||
outputMode,
|
||||
ledMode,
|
||||
disableAirStrings,
|
||||
divaSerialPort,
|
||||
divaBrightness,
|
||||
keyboardSensitivity,
|
||||
outputPolling,
|
||||
outputWebsocketUrl,
|
||||
ledFaster,
|
||||
ledColorActive,
|
||||
ledColorInactive,
|
||||
ledSensitivity,
|
||||
ledWebsocketUrl,
|
||||
ledSerialPort,
|
||||
@@ -136,6 +149,10 @@
|
||||
async function brokenithmQr() {
|
||||
await emit("openBrokenithmQr");
|
||||
}
|
||||
|
||||
async function repo() {
|
||||
await emit("openRepo");
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="titlebar">
|
||||
@@ -152,259 +169,337 @@
|
||||
</div>
|
||||
<div data-tauri-drag-region class="titlebar titlebar-front" />
|
||||
<main class="main">
|
||||
<div class="row">
|
||||
<div class="preview-row">
|
||||
<Preview data={previewData} />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">Input Device</div>
|
||||
<div class="input">
|
||||
<select bind:value={deviceMode} on:change={markDirty}>
|
||||
<option value="none">None</option>
|
||||
<option value="tasoller-one">GAMO2 Tasoller, 1.0 HID Firmware</option>
|
||||
<option value="tasoller-two">GAMO2 Tasoller, 2.0 HID Firmware</option>
|
||||
<option value="yuancon">Yuancon Laverita, HID Firmware</option>
|
||||
<option value="diva">Diva Slider over Serial</option>
|
||||
<option value="brokenithm">Brokenithm</option>
|
||||
<option value="brokenithm-led">Brokenithm + Led</option>
|
||||
<option value="brokenithm-ground">Brokenithm, Ground only</option>
|
||||
<option value="brokenithm-ground-led"
|
||||
>Brokenithm + Led, Ground only</option
|
||||
>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{#if deviceMode.slice(0, 10) === "brokenithm"}
|
||||
<div class="options">
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="label">Input Device</div>
|
||||
<div class="input">
|
||||
<div class="serverlist">
|
||||
Brokenithm server running, access at one of:
|
||||
<pre>
|
||||
{ips.map((x) => `http://${x}:1606/`).join("\n")}
|
||||
</pre>
|
||||
<select bind:value={deviceMode} on:change={markDirty}>
|
||||
<option value="none">None</option>
|
||||
<option value="tasoller-one">GAMO2 Tasoller, 1.0 HID Firmware</option>
|
||||
<option value="tasoller-two">GAMO2 Tasoller, 2.0 HID Firmware</option>
|
||||
<option value="yuancon">Yuancon Laverita, HID Firmware</option>
|
||||
<option value="diva">Slider over Serial</option>
|
||||
<option value="brokenithm">Brokenithm</option>
|
||||
<option value="brokenithm-led">Brokenithm + Led</option>
|
||||
<option value="brokenithm-nostalgia">Brokestalgia (28k)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{#if deviceMode.slice(0, 8) === "tasoller" || deviceMode.slice(0, 7) === "yuancon" || (deviceMode.slice(0, 10) === "brokenithm" && deviceMode !== "brokenithm-nostalgia")}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
<span>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="disable-air"
|
||||
style="width: unset;"
|
||||
bind:checked={disableAirStrings}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
<label for="disable-air">Disable Air Strings</label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if deviceMode === "diva"}
|
||||
<div class="row">
|
||||
<div class="label">Diva Serial Port</div>
|
||||
<div class="input">
|
||||
<select bind:value={divaSerialPort} on:change={markDirty}>
|
||||
<option value="COM1">COM1</option>
|
||||
<option value="COM2">COM2</option>
|
||||
<option value="COM3">COM3</option>
|
||||
<option value="COM4">COM4</option>
|
||||
<option value="COM5">COM5</option>
|
||||
<option value="COM6">COM6</option>
|
||||
<option value="COM7">COM7</option>
|
||||
<option value="COM8">COM8</option>
|
||||
<option value="COM9">COM9</option>
|
||||
</select>
|
||||
{/if}
|
||||
{#if deviceMode.slice(0, 10) === "brokenithm"}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
<div class="serverlist">
|
||||
Brokenithm server running, access at one of:
|
||||
<div class="iplist">
|
||||
{ips
|
||||
.map((x) => `http://${x}:1606/`)
|
||||
.join("\n")
|
||||
.trim()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">Brightness</div>
|
||||
<div class="input">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={divaBrightness}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
{/if}
|
||||
{#if deviceMode === "diva"}
|
||||
<div class="row">
|
||||
<div class="label">Slider Serial Port</div>
|
||||
<div class="input">
|
||||
<select bind:value={divaSerialPort} on:change={markDirty}>
|
||||
<option value="COM1">COM1</option>
|
||||
<option value="COM2">COM2</option>
|
||||
<option value="COM3">COM3</option>
|
||||
<option value="COM4">COM4</option>
|
||||
<option value="COM5">COM5</option>
|
||||
<option value="COM6">COM6</option>
|
||||
<option value="COM7">COM7</option>
|
||||
<option value="COM8">COM8</option>
|
||||
<option value="COM9">COM9</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={divaBrightness}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
<div class="row">
|
||||
<div class="label">Brightness</div>
|
||||
<div class="input">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={divaBrightness}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={divaBrightness}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="row">
|
||||
<div class="label">Output Mode</div>
|
||||
<div class="input">
|
||||
<select bind:value={outputMode} on:change={markDirty}>
|
||||
<option value="none">None</option>
|
||||
<option value="kb-32-tasoller">Keyboard 32-zone, Tasoller Layout</option
|
||||
>
|
||||
<option value="kb-32-yuancon">Keyboard 32-zone, Yuancon Layout</option>
|
||||
<option value="kb-16-tasoller"
|
||||
>Keyboard 16-zone, Tasoller (Bottom Half) Layout</option
|
||||
>
|
||||
<option value="kb-8-deemo">Keyboard 8-zone, Deemo Layout</option>
|
||||
<option value="kb-voltex">Keyboard 10-zone, Voltex Layout</option>
|
||||
<option value="kb-neardayo">Keyboard 10-zone, Neardayo Layout</option>
|
||||
<option value="gamepad-voltex">XBOX 360 Gamepad, Voltex Layout</option>
|
||||
<option value="gamepad-neardayo"
|
||||
>XBOX 360 Gamepad, Neardayo Layout</option
|
||||
>
|
||||
<!-- <option value="websocket">Websocket</option> -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{#if outputMode !== "none"}
|
||||
<div class="row">
|
||||
<div class="label">Output Polling</div>
|
||||
<div class="label">Output Mode</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="250">250 Hz</option>
|
||||
<option value="500">500 Hz</option>
|
||||
<option value="1000">1000 Hz</option>
|
||||
<select bind:value={outputMode} on:change={markDirty}>
|
||||
<option value="none">None</option>
|
||||
<option value="kb-32-tasoller"
|
||||
>Keyboard 32-zone, Tasoller Layout</option
|
||||
>
|
||||
<option value="kb-32-yuancon">Keyboard 32-zone, Yuancon Layout</option
|
||||
>
|
||||
<option value="kb-16">Keyboard 16-zone, Linear</option>
|
||||
<option value="kb-8">Keyboard 8-zone, Linear</option>
|
||||
<option value="kb-6">Keyboard 6-zone, Linear</option>
|
||||
<option value="kb-4">Keyboard 4-zone, Linear</option>
|
||||
<option value="kb-voltex">Keyboard 10-zone, Voltex Layout</option>
|
||||
<option value="kb-neardayo">Keyboard 10-zone, Neardayo Layout</option>
|
||||
<option value="gamepad-voltex">XBOX 360 Gamepad, Voltex Layout</option
|
||||
>
|
||||
<option value="gamepad-neardayo"
|
||||
>XBOX 360 Gamepad, Neardayo Layout</option
|
||||
>
|
||||
<!-- <option value="websocket">Websocket</option> -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if outputMode.slice(0, 7) === "gamepad"}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
Gamepad emulation requires <Link
|
||||
href="https://github.com/ViGEm/ViGEmBus/releases">ViGEMBus</Link
|
||||
>
|
||||
{#if deviceMode === "brokenithm-nostalgia" && outputMode !== "none" && outputMode.slice(0, 5) !== "kb-32"}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input comment">
|
||||
32 key layout is recommended for Brokestalgia controllers
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if outputMode.slice(0, 2) === "kb" && deviceMode.slice(0, 10) !== "brokenithm"}
|
||||
<div class="row">
|
||||
<div class="label" title="Larger means harder to trigger">
|
||||
Sensitivity
|
||||
{:else if deviceMode.slice(0, 10) === "brokenithm" && ["kb-voltex", "kb-neardayo", "gamepad-voltex", "gamepad-neardayo"].includes(outputMode)}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input comment">
|
||||
Voltex-like layouts are not recommended for Brokenithm controllers
|
||||
</div>
|
||||
</div>
|
||||
<div class="input">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={keyboardSensitivity}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
{/if}
|
||||
{#if outputMode.slice(0, 7) === "gamepad"}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input comment">
|
||||
Gamepad emulation requires <Link
|
||||
href="https://github.com/ViGEm/ViGEmBus/releases">ViGEMBus</Link
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={keyboardSensitivity}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
{/if}
|
||||
{#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="250">250 Hz</option>
|
||||
<option value="500">500 Hz</option>
|
||||
<option value="1000">1000 Hz</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if outputMode === "websocket"}
|
||||
<div class="row">
|
||||
<div class="label">Output URL</div>
|
||||
<div class="input">
|
||||
<input
|
||||
placeholder="URL"
|
||||
bind:value={outputWebsocketUrl}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
{/if}
|
||||
{#if (outputMode.slice(0, 2) === "kb" || outputMode.slice(0, 7) === "gamepad") && deviceMode.slice(0, 10) !== "brokenithm"}
|
||||
<div class="row">
|
||||
<div class="label" title="Larger means harder to trigger">
|
||||
Sensitivity
|
||||
</div>
|
||||
<div class="input">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={keyboardSensitivity}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={keyboardSensitivity}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if outputMode === "websocket"}
|
||||
<div class="row">
|
||||
<div class="label">Output URL</div>
|
||||
<div class="input">
|
||||
<input
|
||||
placeholder="URL"
|
||||
bind:value={outputWebsocketUrl}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="row">
|
||||
<div class="label">LED Mode</div>
|
||||
<div class="input">
|
||||
<select bind:value={ledMode} on:change={markDirty}>
|
||||
<option value="none">None</option>
|
||||
<option value="reactive-4">Reactive, 4-Zone</option>
|
||||
<option value="reactive-8">Reactive, 8-Zone</option>
|
||||
<option value="reactive-16">Reactive, 16-Zone</option>
|
||||
<option value="reactive-voltex">Reactive, Voltex Layout</option>
|
||||
<option value="attract">Rainbow Attract Mode</option>
|
||||
<!-- <option value="websocket">Websocket</option> -->
|
||||
<option value="serial">Serial</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{#if ledMode.slice(0, 8) === "reactive" && deviceMode.slice(0, 10) !== "brokenithm"}
|
||||
<div class="row">
|
||||
<div class="label" title="Larger means harder to trigger">
|
||||
Sensitivity
|
||||
</div>
|
||||
<div class="label">LED Mode</div>
|
||||
<div class="input">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={ledSensitivity}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={ledSensitivity}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if ledMode === "websocket"}
|
||||
<div class="row">
|
||||
<div class="label">LED URL</div>
|
||||
<div class="input">
|
||||
<input
|
||||
placeholder="URL"
|
||||
bind:value={ledWebsocketUrl}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if ledMode === "serial"}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
Serial LED may require <Link
|
||||
href="https://sourceforge.net/projects/com0com/files/com0com/2.2.2.0/com0com-2.2.2.0-x64-fre-signed.zip/download"
|
||||
>com0com</Link
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">LED Serial Port</div>
|
||||
<div class="input">
|
||||
<select bind:value={ledSerialPort} on:change={markDirty}>
|
||||
<option value="COM1">COM1</option>
|
||||
<option value="COM2">COM2</option>
|
||||
<option value="COM3">COM3</option>
|
||||
<option value="COM4">COM4</option>
|
||||
<option value="COM5">COM5</option>
|
||||
<option value="COM6">COM6</option>
|
||||
<option value="COM7">COM7</option>
|
||||
<option value="COM8">COM8</option>
|
||||
<option value="COM9">COM9</option>
|
||||
<select bind:value={ledMode} on:change={markDirty}>
|
||||
<option value="none">None</option>
|
||||
<option value="reactive-16">Reactive, 16-Zone</option>
|
||||
<option value="reactive-8">Reactive, 8-Zone</option>
|
||||
<option value="reactive-6">Reactive, 6-Zone</option>
|
||||
<option value="reactive-4">Reactive, 4-Zone</option>
|
||||
<option value="reactive-rainbow">Reactive, 16-Zone Rainbow</option>
|
||||
<option value="reactive-voltex">Reactive, Voltex Layout</option>
|
||||
<option value="attract">Rainbow Attract Mode</option>
|
||||
<!-- <option value="websocket">Websocket</option> -->
|
||||
<option value="serial">Serial</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="row">
|
||||
{#if ledMode !== "none"}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
<span>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="led-faster"
|
||||
style="width: unset;"
|
||||
bind:checked={ledFaster}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
<label for="led-faster">Update LED data faster</label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if ledMode.slice(0, 8) === "reactive" && ["16", "8", "6", "4"].includes(ledMode.slice(9))}
|
||||
<div class="row">
|
||||
<div class="label">Active Color</div>
|
||||
<div class="input">
|
||||
<input
|
||||
type="color"
|
||||
bind:value={ledColorActive}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">Base Color</div>
|
||||
<div class="input">
|
||||
<input
|
||||
type="color"
|
||||
bind:value={ledColorInactive}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if ledMode.slice(0, 8) === "reactive" && deviceMode.slice(0, 10) !== "brokenithm"}
|
||||
<div class="row">
|
||||
<div class="label" title="Larger means harder to trigger">
|
||||
Sensitivity
|
||||
</div>
|
||||
<div class="input">
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={ledSensitivity}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
<input
|
||||
type="range"
|
||||
min="1"
|
||||
max="255"
|
||||
step="1"
|
||||
bind:value={ledSensitivity}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if ledMode === "websocket"}
|
||||
<div class="row">
|
||||
<div class="label">LED URL</div>
|
||||
<div class="input">
|
||||
<input
|
||||
placeholder="URL"
|
||||
bind:value={ledWebsocketUrl}
|
||||
on:change={markDirty}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if ledMode === "serial"}
|
||||
<div class="row">
|
||||
<div class="label" />
|
||||
<div class="input">
|
||||
Serial LED may require <Link
|
||||
href="https://sourceforge.net/projects/com0com/files/com0com/2.2.2.0/com0com-2.2.2.0-x64-fre-signed.zip/download"
|
||||
>com0com</Link
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="label">LED Serial Port</div>
|
||||
<div class="input">
|
||||
<select bind:value={ledSerialPort} on:change={markDirty}>
|
||||
<option value="COM1">COM1</option>
|
||||
<option value="COM2">COM2</option>
|
||||
<option value="COM3">COM3</option>
|
||||
<option value="COM4">COM4</option>
|
||||
<option value="COM5">COM5</option>
|
||||
<option value="COM6">COM6</option>
|
||||
<option value="COM7">COM7</option>
|
||||
<option value="COM8">COM8</option>
|
||||
<option value="COM9">COM9</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="buttons-row">
|
||||
<button
|
||||
on:click={async () => await setConfig()}
|
||||
class={`${dirty && "primary"}`}>Apply</button
|
||||
@@ -415,6 +510,7 @@
|
||||
{#if deviceMode.slice(0, 10) === "brokenithm"}
|
||||
<button on:click={async () => await brokenithmQr()}>Brokenithm QR</button>
|
||||
{/if}
|
||||
<button on:click={async () => await repo()}>About</button>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user