mirror of https://github.com/4yn/slidershim
Compare commits
12 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
4f0db8e66e | |
![]() |
7cc6e46afe | |
![]() |
7b63ff3a1a | |
![]() |
6124dee2db | |
![]() |
cbbfcdc99e | |
![]() |
b77f5f105f | |
![]() |
743424dc01 | |
![]() |
1b131d7ea7 | |
![]() |
df9ee3b57f | |
![]() |
3bd7ee8586 | |
![]() |
828f480424 | |
![]() |
e285bdb999 |
15
README.md
15
README.md
|
@ -12,6 +12,14 @@ Software adapter for various Chunithm slider controllers with a built-in Brokeni
|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
- v0.6.0
|
||||||
|
- Support Laverita v3. Thanks @sr1canskhsia
|
||||||
|
- v0.5.1
|
||||||
|
- Support SSL/wss if brokenithm used with tunnelling. Thanks @kokarare1212
|
||||||
|
- v0.5.0
|
||||||
|
- Support for Yubideck 3.0 firmware
|
||||||
|
- v0.4.3
|
||||||
|
- Make Yubideck USB reports more flexible when reading data
|
||||||
- v0.4.2
|
- v0.4.2
|
||||||
- Fix HORI Project Diva gamepad slider output order
|
- Fix HORI Project Diva gamepad slider output order
|
||||||
- v0.4.1
|
- v0.4.1
|
||||||
|
@ -20,6 +28,11 @@ Software adapter for various Chunithm slider controllers with a built-in Brokeni
|
||||||
- v0.4.0
|
- v0.4.0
|
||||||
- Add DirectInput keyboard emulation using [oblitum/Interception](https://github.com/oblitum/Interception)
|
- Add DirectInput keyboard emulation using [oblitum/Interception](https://github.com/oblitum/Interception)
|
||||||
- Add slide LED support for some controllers
|
- Add slide LED support for some controllers
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>Older changelog</summary>
|
||||||
|
|
||||||
- v0.3.1
|
- v0.3.1
|
||||||
- Fix wrong key in UMIGURI keyboard layout
|
- Fix wrong key in UMIGURI keyboard layout
|
||||||
- v0.3.0
|
- v0.3.0
|
||||||
|
@ -57,6 +70,8 @@ Software adapter for various Chunithm slider controllers with a built-in Brokeni
|
||||||
- v0.1.4
|
- v0.1.4
|
||||||
- Initial public release
|
- Initial public release
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Setup Instructions
|
## Setup Instructions
|
||||||
|
|
||||||
1. [Download here](https://github.com/4yn/slidershim/releases/latest)
|
1. [Download here](https://github.com/4yn/slidershim/releases/latest)
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -223,7 +223,7 @@ var ws = null;
|
||||||
var wsTimeout = 0;
|
var wsTimeout = 0;
|
||||||
var wsConnected = false;
|
var wsConnected = false;
|
||||||
const wsConnect = () => {
|
const wsConnect = () => {
|
||||||
ws = new WebSocket("ws://" + location.host + "/ws");
|
ws = new WebSocket((location.protocol == "https:" ? "wss://" : "ws://") + location.host + "/ws");
|
||||||
ws.binaryType = "arraybuffer";
|
ws.binaryType = "arraybuffer";
|
||||||
ws.onopen = () => {
|
ws.onopen = () => {
|
||||||
ws.send("alive?");
|
ws.send("alive?");
|
||||||
|
|
|
@ -5,7 +5,9 @@ pub enum HardwareSpec {
|
||||||
TasollerOne,
|
TasollerOne,
|
||||||
TasollerTwo,
|
TasollerTwo,
|
||||||
Yuancon,
|
Yuancon,
|
||||||
|
YuanconThree,
|
||||||
Yubideck,
|
Yubideck,
|
||||||
|
YubideckThree,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -49,10 +51,18 @@ impl DeviceMode {
|
||||||
spec: HardwareSpec::Yuancon,
|
spec: HardwareSpec::Yuancon,
|
||||||
disable_air: v["disableAirStrings"].as_bool()?,
|
disable_air: v["disableAirStrings"].as_bool()?,
|
||||||
},
|
},
|
||||||
|
"yuancon-three" => DeviceMode::Hardware {
|
||||||
|
spec: HardwareSpec::YuanconThree,
|
||||||
|
disable_air: v["disableAirStrings"].as_bool()?,
|
||||||
|
},
|
||||||
"yubideck" => DeviceMode::Hardware {
|
"yubideck" => DeviceMode::Hardware {
|
||||||
spec: HardwareSpec::Yubideck,
|
spec: HardwareSpec::Yubideck,
|
||||||
disable_air: v["disableAirStrings"].as_bool()?,
|
disable_air: v["disableAirStrings"].as_bool()?,
|
||||||
},
|
},
|
||||||
|
"yubideck-three" => DeviceMode::Hardware {
|
||||||
|
spec: HardwareSpec::YubideckThree,
|
||||||
|
disable_air: v["disableAirStrings"].as_bool()?,
|
||||||
|
},
|
||||||
"diva" => DeviceMode::DivaSlider {
|
"diva" => DeviceMode::DivaSlider {
|
||||||
port: v["divaSerialPort"].as_str()?.to_string(),
|
port: v["divaSerialPort"].as_str()?.to_string(),
|
||||||
brightness: u8::try_from(v["divaBrightness"].as_i64()?).ok()?,
|
brightness: u8::try_from(v["divaBrightness"].as_i64()?).ok()?,
|
||||||
|
|
|
@ -18,7 +18,7 @@ use crate::{
|
||||||
use super::config::HardwareSpec;
|
use super::config::HardwareSpec;
|
||||||
|
|
||||||
type HidReadCallback = fn(&Buffer, &mut SliderInput) -> ();
|
type HidReadCallback = fn(&Buffer, &mut SliderInput) -> ();
|
||||||
type HidLedCallback = fn(&mut Buffer, &SliderLights) -> ();
|
type HidLedCallback = fn(&mut Buffer, &mut Buffer, &SliderLights) -> ();
|
||||||
|
|
||||||
enum WriteType {
|
enum WriteType {
|
||||||
Bulk,
|
Bulk,
|
||||||
|
@ -41,6 +41,7 @@ pub struct HidJob {
|
||||||
led_write_type: WriteType,
|
led_write_type: WriteType,
|
||||||
led_callback: HidLedCallback,
|
led_callback: HidLedCallback,
|
||||||
led_buf: Buffer,
|
led_buf: Buffer,
|
||||||
|
led_buf_two: Buffer,
|
||||||
|
|
||||||
handle: Option<DeviceHandle<GlobalContext>>,
|
handle: Option<DeviceHandle<GlobalContext>>,
|
||||||
}
|
}
|
||||||
|
@ -70,6 +71,7 @@ impl HidJob {
|
||||||
led_write_type: led_type,
|
led_write_type: led_type,
|
||||||
led_callback,
|
led_callback,
|
||||||
led_buf: Buffer::new(),
|
led_buf: Buffer::new(),
|
||||||
|
led_buf_two: Buffer::new(),
|
||||||
handle: None,
|
handle: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +104,7 @@ impl HidJob {
|
||||||
input.extra[0..2].copy_from_slice(&bits[26..28]);
|
input.extra[0..2].copy_from_slice(&bits[26..28]);
|
||||||
},
|
},
|
||||||
WriteType::Bulk,
|
WriteType::Bulk,
|
||||||
|buf, lights| {
|
|buf, _, lights| {
|
||||||
buf.len = 240;
|
buf.len = 240;
|
||||||
buf.data[0] = 'B' as u8;
|
buf.data[0] = 'B' as u8;
|
||||||
buf.data[1] = 'L' as u8;
|
buf.data[1] = 'L' as u8;
|
||||||
|
@ -139,7 +141,7 @@ impl HidJob {
|
||||||
input.extra[0..2].copy_from_slice(&bits[6..8]);
|
input.extra[0..2].copy_from_slice(&bits[6..8]);
|
||||||
},
|
},
|
||||||
WriteType::Bulk,
|
WriteType::Bulk,
|
||||||
|buf, lights| {
|
|buf, _, lights| {
|
||||||
buf.len = 240;
|
buf.len = 240;
|
||||||
buf.data[0] = 'B' as u8;
|
buf.data[0] = 'B' as u8;
|
||||||
buf.data[1] = 'L' as u8;
|
buf.data[1] = 'L' as u8;
|
||||||
|
@ -190,7 +192,7 @@ impl HidJob {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
WriteType::Interrupt,
|
WriteType::Interrupt,
|
||||||
|buf, lights| {
|
|buf, _, lights| {
|
||||||
buf.len = 31 * 2;
|
buf.len = 31 * 2;
|
||||||
for (buf_chunk, state_chunk) in buf
|
for (buf_chunk, state_chunk) in buf
|
||||||
.data
|
.data
|
||||||
|
@ -203,6 +205,53 @@ impl HidJob {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
HardwareSpec::YuanconThree => Self::new(
|
||||||
|
state.clone(),
|
||||||
|
0x0518,
|
||||||
|
0x2022,
|
||||||
|
0x83,
|
||||||
|
0x03,
|
||||||
|
*disable_air,
|
||||||
|
|buf, input| {
|
||||||
|
if buf.len != 46 { // real length is 46 but last 12 bytes are unused
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.ground.copy_from_slice(&buf.data[2..34]);
|
||||||
|
input.flip_vert();
|
||||||
|
|
||||||
|
let bits: Vec<u8> = (0..8).map(|x| (buf.data[0] >> x) & 1).collect();
|
||||||
|
for i in 0..6 {
|
||||||
|
input.air[i ^ 1] = bits[i];
|
||||||
|
}
|
||||||
|
input.extra[0..2].copy_from_slice(&bits[6..8]);
|
||||||
|
},
|
||||||
|
WriteType::Interrupt,
|
||||||
|
|buf, buf_two, lights| {
|
||||||
|
buf.len = 61;
|
||||||
|
buf.data[0] = 0;
|
||||||
|
buf_two.len = 61;
|
||||||
|
buf_two.data[0] = 1;
|
||||||
|
|
||||||
|
for (buf_chunk, state_chunk) in buf.data[1..61]
|
||||||
|
.chunks_mut(3)
|
||||||
|
.zip(lights.ground.chunks(3).skip(11).take(20).rev())
|
||||||
|
{
|
||||||
|
buf_chunk[0] = state_chunk[0];
|
||||||
|
buf_chunk[1] = state_chunk[1];
|
||||||
|
buf_chunk[2] = state_chunk[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (buf_chunk, state_chunk) in buf_two.data[1..34]
|
||||||
|
.chunks_mut(3)
|
||||||
|
.zip(lights.ground.chunks(3).take(11).rev())
|
||||||
|
{
|
||||||
|
buf_chunk[0] = state_chunk[0];
|
||||||
|
buf_chunk[1] = state_chunk[1];
|
||||||
|
buf_chunk[2] = state_chunk[2];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
HardwareSpec::Yubideck => Self::new(
|
HardwareSpec::Yubideck => Self::new(
|
||||||
state.clone(),
|
state.clone(),
|
||||||
0x1973,
|
0x1973,
|
||||||
|
@ -211,11 +260,12 @@ impl HidJob {
|
||||||
0x02, // Need to confirm
|
0x02, // Need to confirm
|
||||||
*disable_air,
|
*disable_air,
|
||||||
|buf, input| {
|
|buf, input| {
|
||||||
if buf.len != 45 {
|
if buf.len != 45 && buf.len != 46 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
input.ground.copy_from_slice(&buf.data[2..34]);
|
input.ground.copy_from_slice(&buf.data[2..34]);
|
||||||
|
input.flip_vert();
|
||||||
for i in 0..6 {
|
for i in 0..6 {
|
||||||
input.air[i ^ 1] = (buf.data[0] >> i) & 1;
|
input.air[i ^ 1] = (buf.data[0] >> i) & 1;
|
||||||
}
|
}
|
||||||
|
@ -224,7 +274,7 @@ impl HidJob {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
WriteType::Interrupt,
|
WriteType::Interrupt,
|
||||||
|buf, lights| {
|
|buf, _, lights| {
|
||||||
buf.len = 62;
|
buf.len = 62;
|
||||||
|
|
||||||
let lights_nibbles: Vec<u8> = lights
|
let lights_nibbles: Vec<u8> = lights
|
||||||
|
@ -251,6 +301,56 @@ impl HidJob {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
HardwareSpec::YubideckThree => Self::new(
|
||||||
|
state.clone(),
|
||||||
|
0x1973,
|
||||||
|
0x2001,
|
||||||
|
0x81, // Need to confirm
|
||||||
|
0x02, // Need to confirm
|
||||||
|
*disable_air,
|
||||||
|
|buf, input| {
|
||||||
|
if buf.len != 45 && buf.len != 46 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
input.ground.copy_from_slice(&buf.data[2..34]);
|
||||||
|
input.flip_vert();
|
||||||
|
for i in 0..6 {
|
||||||
|
input.air[i ^ 1] = (buf.data[0] >> i) & 1;
|
||||||
|
}
|
||||||
|
for i in 0..3 {
|
||||||
|
input.extra[2 - i] = (buf.data[1] >> i) & 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
WriteType::Interrupt,
|
||||||
|
|buf, buf_two, lights| {
|
||||||
|
buf.len = 61;
|
||||||
|
buf.data[0] = 0;
|
||||||
|
buf_two.len = 61;
|
||||||
|
buf_two.data[0] = 1;
|
||||||
|
|
||||||
|
for (buf_chunk, state_chunk) in buf.data[1..61]
|
||||||
|
.chunks_mut(3)
|
||||||
|
.zip(lights.ground.chunks(3).skip(11).take(20).rev())
|
||||||
|
{
|
||||||
|
buf_chunk[0] = state_chunk[0];
|
||||||
|
buf_chunk[1] = state_chunk[1];
|
||||||
|
buf_chunk[2] = state_chunk[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (buf_chunk, state_chunk) in buf_two.data[1..34]
|
||||||
|
.chunks_mut(3)
|
||||||
|
.zip(lights.ground.chunks(3).take(11).rev())
|
||||||
|
{
|
||||||
|
buf_chunk[0] = state_chunk[0];
|
||||||
|
buf_chunk[1] = state_chunk[1];
|
||||||
|
buf_chunk[2] = state_chunk[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
buf_two.data[34..37].copy_from_slice(&lights.air_left[3..6]);
|
||||||
|
buf_two.data[37..40].copy_from_slice(&lights.air_right[3..6]);
|
||||||
|
},
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,8 +370,15 @@ impl HidJob {
|
||||||
}
|
}
|
||||||
info!("Device setting configuration");
|
info!("Device setting configuration");
|
||||||
handle.set_active_configuration(1)?;
|
handle.set_active_configuration(1)?;
|
||||||
|
|
||||||
|
// A bit janky but Laverita v3 seems to require interface 3
|
||||||
info!("Device claiming interface");
|
info!("Device claiming interface");
|
||||||
handle.claim_interface(0)?;
|
if self.vid == 0x0518 && self.pid == 0x2022 {
|
||||||
|
handle.claim_interface(3)?;
|
||||||
|
} else {
|
||||||
|
handle.claim_interface(0)?;
|
||||||
|
}
|
||||||
|
|
||||||
self.handle = Some(handle);
|
self.handle = Some(handle);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -326,7 +433,11 @@ impl ThreadJob for HidJob {
|
||||||
{
|
{
|
||||||
let mut lights_handle = self.state.lights.lock();
|
let mut lights_handle = self.state.lights.lock();
|
||||||
if lights_handle.dirty {
|
if lights_handle.dirty {
|
||||||
(self.led_callback)(&mut self.led_buf, lights_handle.deref());
|
(self.led_callback)(
|
||||||
|
&mut self.led_buf,
|
||||||
|
&mut self.led_buf_two,
|
||||||
|
lights_handle.deref(),
|
||||||
|
);
|
||||||
lights_handle.dirty = false;
|
lights_handle.dirty = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -348,6 +459,26 @@ impl ThreadJob for HidJob {
|
||||||
self.led_buf.len = 0;
|
self.led_buf.len = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.led_buf_two.len != 0 {
|
||||||
|
let res = (match self.led_write_type {
|
||||||
|
WriteType::Bulk => {
|
||||||
|
handle.write_bulk(self.led_endpoint, self.led_buf_two.slice(), TIMEOUT)
|
||||||
|
}
|
||||||
|
WriteType::Interrupt => {
|
||||||
|
handle.write_interrupt(self.led_endpoint, &self.led_buf_two.slice(), TIMEOUT)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
// debug!("Device write error {}", e);
|
||||||
|
e
|
||||||
|
})
|
||||||
|
.unwrap_or(0);
|
||||||
|
if res == self.led_buf_two.len + 1 {
|
||||||
|
// work = true;
|
||||||
|
self.led_buf_two.len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
work
|
work
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "CoreFoundation-sys"
|
name = "CoreFoundation-sys"
|
||||||
|
@ -2462,9 +2462,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.40"
|
version = "1.0.67"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
|
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
@ -2996,7 +2996,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slidershim"
|
name = "slidershim"
|
||||||
version = "0.4.2"
|
version = "0.6.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "slidershim"
|
name = "slidershim"
|
||||||
version = "0.4.2"
|
version = "0.6.0"
|
||||||
description = "slidershim"
|
description = "slidershim"
|
||||||
authors = ["4yn"]
|
authors = ["4yn"]
|
||||||
license = ""
|
license = ""
|
||||||
|
|
|
@ -2,13 +2,21 @@
|
||||||
___| (_) __| | ___ _ __ ___| |__ (_)_ __ ___
|
___| (_) __| | ___ _ __ ___| |__ (_)_ __ ___
|
||||||
/ __| | |/ _` |/ _ \ '__/ __| '_ \| | '_ ` _ \
|
/ __| | |/ _` |/ _ \ '__/ __| '_ \| | '_ ` _ \
|
||||||
\__ \ | | (_| | __/ | \__ \ | | | | | | | | |
|
\__ \ | | (_| | __/ | \__ \ | | | | | | | | |
|
||||||
|___/_|_|\__,_|\___|_| |___/_| |_|_|_| |_| |_| v0.4.2
|
|___/_|_|\__,_|\___|_| |___/_| |_|_|_| |_| |_| v0.6.1
|
||||||
===============================================
|
===============================================
|
||||||
|
|
||||||
https://github.com/4yn/slidershim
|
https://github.com/4yn/slidershim
|
||||||
|
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
- v0.6.0
|
||||||
|
- Support Laverita v3. Thanks @sr1canskhsia
|
||||||
|
- v0.5.1
|
||||||
|
- Support SSL/wss if brokenithm used with tunnelling. Thanks @kokarare1212
|
||||||
|
- v0.5.0
|
||||||
|
- Support for Yubideck 3.0 firmware
|
||||||
|
- v0.4.3
|
||||||
|
- Make Yubideck USB reports more flexible when reading data
|
||||||
- v0.4.2
|
- v0.4.2
|
||||||
- Fix HORI Project Diva gamepad slider output order
|
- Fix HORI Project Diva gamepad slider output order
|
||||||
- v0.4.1
|
- v0.4.1
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "slidershim",
|
"productName": "slidershim",
|
||||||
"version": "0.4.2"
|
"version": "0.6.0"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"distDir": "../public",
|
"distDir": "../public",
|
||||||
|
@ -21,7 +21,9 @@
|
||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
"icons/icon.ico"
|
"icons/icon.ico"
|
||||||
],
|
],
|
||||||
"resources": ["./README.txt"],
|
"resources": [
|
||||||
|
"./README.txt"
|
||||||
|
],
|
||||||
"externalBin": [],
|
"externalBin": [],
|
||||||
"copyright": "© 4yn 2022",
|
"copyright": "© 4yn 2022",
|
||||||
"category": "DeveloperTool",
|
"category": "DeveloperTool",
|
||||||
|
|
|
@ -196,7 +196,9 @@
|
||||||
<option value="tasoller-one">GAMO2 Tasoller, 1.0 HID Firmware</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="tasoller-two">GAMO2 Tasoller, 2.0 HID Firmware</option>
|
||||||
<option value="yuancon">Yuancon Laverita, HID Firmware</option>
|
<option value="yuancon">Yuancon Laverita, HID Firmware</option>
|
||||||
<option value="yubideck">大四 / Yubideck, HID Firmware</option>
|
<option value="yuancon-three">Yuancon Laverita v3, HID Firmware</option>
|
||||||
|
<option value="yubideck">大四 / Yubideck, HID Firmware 1.0</option>
|
||||||
|
<option value="yubideck-three">大四 / Yubideck, HID Firmware 3.0</option>
|
||||||
<option value="diva">Slider over Serial</option>
|
<option value="diva">Slider over Serial</option>
|
||||||
<option value="brokenithm">Brokenithm</option>
|
<option value="brokenithm">Brokenithm</option>
|
||||||
<option value="brokenithm-led">Brokenithm + Led</option>
|
<option value="brokenithm-led">Brokenithm + Led</option>
|
||||||
|
|
Loading…
Reference in New Issue