27 Commits
Author SHA1 Message Date
4yn 856d2ea49a bump version and update readme 2022-03-23 01:56:41 +08:00
4yn d24bd93084 add complete default for config json 2022-03-23 01:55:20 +08:00
4yn 1802ca12fd prevent horizontal scollbar with padding 2022-03-23 01:52:18 +08:00
4yn 87f6c7db95 update layout image 2022-03-23 01:46:58 +08:00
4yn ec0cc7d2e4 add warnings and adjust button layout 2022-03-23 01:42:14 +08:00
4yn 169d981e6f fix styling 2022-03-23 01:15:38 +08:00
4yn 2fb8995e68 add 4k/6k layouts and reactive color customization 2022-03-23 01:06:16 +08:00
4yn 3a3e339aca update packages and deps 2022-03-18 21:43:10 +08:00
4yn cd2c1d73c0 update readme and bump version 2022-03-18 20:59:41 +08:00
4yn d89de5e6fc add brokestalgia controller 2022-03-18 20:58:34 +08:00
4yn 8fbb9e4d9f reset brokenithm input on close controller 2022-03-18 20:57:53 +08:00
4yn 61505c2375 fix brokenithm on older devices using wider babel preset 2022-03-18 19:32:18 +08:00
4yn 8c96763a8a update readme and bump version 2022-03-16 20:33:36 +08:00
4yn 748a76cf06 add slower led update option 2022-03-16 20:23:50 +08:00
4yn ff6e971f62 add disable air strings option 2022-03-16 20:05:04 +08:00
4yn bcff45da56 brokenithm off by one 2022-03-16 19:11:40 +08:00
4yn ec1a146449 bump version and update readme 2022-03-14 22:48:27 +08:00
4yn 63d2724f35 fix display name for serial slider 2022-03-14 22:46:52 +08:00
4yn 7c0d5af821 cleanup 2022-03-14 22:40:04 +08:00
4yn ca06dc025f update readme 2022-02-25 10:27:07 +08:00
4yn 60098bba46 add link to repo 2022-02-24 10:36:19 +08:00
4yn 358b53c0be flush diva slider after send 2022-02-24 10:36:03 +08:00
4yn 47b90603e2 fix time overflow 2022-02-24 10:35:35 +08:00
4yn af16cd43c8 wwserial license 2022-02-24 10:34:57 +08:00
4yn e9510bcfa0 bump version 2022-02-23 00:37:07 +08:00
4yn dedc8cace4 fix diva slider lights byte order 2022-02-23 00:34:34 +08:00
4yn 35da7d4874 rainbow reactive 2022-02-23 00:33:56 +08:00
29 changed files with 1896 additions and 15280 deletions
+54 -8
View File
@@ -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
-11683
View File
File diff suppressed because it is too large Load Diff
+31 -4
View File
@@ -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
View File
@@ -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

+7 -2
View File
@@ -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 -4
View File
@@ -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
View File
@@ -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<()> {
+4 -4
View File
@@ -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>
+40 -14
View File
@@ -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];
+22 -12
View File
@@ -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)
}))
}
});
+22 -9
View File
@@ -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,
})
}
+3 -2
View File
@@ -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());
}
+11 -1
View File
@@ -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);
}
}
+81 -12
View File
@@ -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,
+79 -10
View File
@@ -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(),
+16 -4
View File
@@ -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()?,
},
+26 -2
View File
@@ -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,
};
+11 -3
View File
@@ -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;
}
+574 -623
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -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
View File
@@ -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
+8 -2
View File
@@ -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);
}
+6 -3
View File
@@ -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,
+11
View File
@@ -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
View File
@@ -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>
+341 -2633
View File
File diff suppressed because it is too large Load Diff