Pre-bevy minigame commit

This commit is contained in:
2026-02-10 12:32:01 +01:00
commit 498d0b1ba8
14 changed files with 1316 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

536
Cargo.lock generated Normal file
View File

@@ -0,0 +1,536 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "az"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7"
[[package]]
name = "bitflags"
version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "deranged"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587"
dependencies = [
"powerfmt",
]
[[package]]
name = "embedded-graphics"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0649998afacf6d575d126d83e68b78c0ab0e00ca2ac7e9b3db11b4cbe8274ef0"
dependencies = [
"az",
"byteorder",
"embedded-graphics-core",
"float-cmp",
"micromath",
]
[[package]]
name = "embedded-graphics-core"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba9ecd261f991856250d2207f6d8376946cd9f412a2165d3b75bc87a0bc7a044"
dependencies = [
"az",
"byteorder",
]
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
dependencies = [
"num-traits",
]
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "g13-driver"
version = "0.1.0"
dependencies = [
"embedded-graphics-core",
"nusb",
]
[[package]]
name = "g13-game"
version = "0.1.0"
dependencies = [
"embedded-graphics",
"g13-driver",
"tokio",
]
[[package]]
name = "g13-joystick"
version = "0.1.0"
dependencies = [
"embedded-graphics",
"g13-driver",
"input-linux",
"time",
"tokio",
]
[[package]]
name = "input-linux"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e8c4821c88b95582ca69234a1d233f87e44182c42e121f740efb0bec1142e0"
dependencies = [
"input-linux-sys",
"nix",
]
[[package]]
name = "input-linux-sys"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b91b2248b0eaf0a576ef5e60b7f2107a749e705a876bc0b9fe952ac8d83a724"
dependencies = [
"libc",
"nix",
]
[[package]]
name = "io-kit-sys"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b"
dependencies = [
"core-foundation-sys",
"mach2",
]
[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "libc"
version = "0.2.180"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "mach2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
dependencies = [
"libc",
]
[[package]]
name = "micromath"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
[[package]]
name = "mio"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "num-conv"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "nusb"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0226f4db3ee78f820747cf713767722877f6449d7a0fcfbf2ec3b840969763f"
dependencies = [
"core-foundation",
"core-foundation-sys",
"futures-core",
"io-kit-sys",
"linux-raw-sys 0.9.4",
"log",
"once_cell",
"rustix",
"slab",
"tokio",
"windows-sys 0.60.2",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "powerfmt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustix"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.11.0",
"windows-sys 0.61.2",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "socket2"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0"
dependencies = [
"libc",
"windows-sys 0.60.2",
]
[[package]]
name = "syn"
version = "2.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "time"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
"num-conv",
"powerfmt",
"serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
]
[[package]]
name = "tokio"
version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
"libc",
"mio",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e"
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"

11
Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[workspace]
resolver = "3"
members = ["driver", "joystick", "mini-game"]
default-members = ["joystick", "mini-game"]
[workspace.package]
version = "0.1.0"
edition = "2024"
[workspace.dependencies]
g13-driver.path = "./driver"

8
driver/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "g13-driver"
version.workspace = true
edition.workspace = true
[dependencies]
embedded-graphics-core = "0.4.0"
nusb = { version = "0.2.1", features = ["tokio"] }

View File

@@ -0,0 +1,14 @@
use std::slice;
#[derive(Debug, Clone, Copy)]
pub enum Axis {
X,
Y,
}
impl Axis {
pub fn all_axes() -> slice::Iter<'static, Self> {
use Axis::*;
[X, Y].iter()
}
}

View File

@@ -0,0 +1,58 @@
use std::slice;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Button {
Action,
Screen1,
Screen2,
Screen3,
Screen4,
Light,
M1,
M2,
M3,
MR,
G1,
G2,
G3,
G4,
G5,
G6,
G7,
G8,
G9,
G10,
G11,
G12,
G13,
G14,
G15,
G16,
G17,
G18,
G19,
G20,
G21,
G22,
Stick1,
Stick2,
Stick3,
}
impl Button {
pub fn all_buttons() -> slice::Iter<'static, Self> {
use Button::*;
[
Action, Screen1, Screen2, Screen3, Screen4, Light, M1, M2, M3, MR, G1, G2, G3, G4, G5,
G6, G7, G8, G9, G10, G11, G12, G13, G14, G15, G16, G17, G18, G19, G20, G21, G22,
Stick1, Stick2, Stick3,
]
.iter()
}
}

View File

@@ -0,0 +1,7 @@
// gracefully stolen from https://github.com/gwilymk/arduino-joystick/tree/master/joystick-daemon/src/joystick <3
mod axis;
mod button;
pub use axis::Axis;
pub use button::Button;

241
driver/src/lib.rs Normal file
View File

@@ -0,0 +1,241 @@
pub mod joystick;
use std::{
io::{Read, Write},
sync::{Arc, RwLock},
time::Duration,
};
use embedded_graphics_core::{
pixelcolor::BinaryColor,
prelude::{Dimensions, DrawTarget, Point, Size},
primitives::Rectangle,
};
use nusb::{
Interface, MaybeFuture,
transfer::{ControlOut, ControlType, In, Interrupt, Out, Recipient},
};
pub const G13_LCD_COLUMNS: i32 = 160;
pub const G13_LCD_ROWS: i32 = 43;
pub const G13_LCD_BYTES_PER_ROW: i32 = G13_LCD_COLUMNS / 8;
pub const G13_LCD_BUF_SIZE: i32 = (G13_LCD_ROWS + 5) * G13_LCD_BYTES_PER_ROW;
#[derive(Clone)]
pub struct G13 {
interface: Interface,
img_buffer: [u8; G13_LCD_BUF_SIZE as usize + 8],
state: Arc<RwLock<State>>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct State {
pub x: i32,
pub y: i32,
pub buttons: Buttons,
pub previous_buttons: Buttons,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Buttons {
pub action: bool,
pub screen1: bool,
pub screen2: bool,
pub screen3: bool,
pub screen4: bool,
pub light: bool,
pub m1: bool,
pub m2: bool,
pub m3: bool,
pub mr: bool,
pub g1: bool,
pub g2: bool,
pub g3: bool,
pub g4: bool,
pub g5: bool,
pub g6: bool,
pub g7: bool,
pub g8: bool,
pub g9: bool,
pub g10: bool,
pub g11: bool,
pub g12: bool,
pub g13: bool,
pub g14: bool,
pub g15: bool,
pub g16: bool,
pub g17: bool,
pub g18: bool,
pub g19: bool,
pub g20: bool,
pub g21: bool,
pub g22: bool,
pub stick1: bool,
pub stick2: bool,
pub stick3: bool,
}
impl G13 {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
let Ok(mut devices) = nusb::list_devices().wait() else {
return Err("No devices found".into());
};
let Some(device) = devices.find(|d| d.vendor_id() == 0x046d && d.product_id() == 0xc21c)
else {
return Err("No device found".into());
};
let Ok(device) = device.open().wait() else {
return Err("Unable to open device".into());
};
let _ = device.detach_kernel_driver(0);
let interface = match device.claim_interface(0).wait() {
Ok(i) => i,
Err(e) => return Err(format!("Unable to claim interface: {:#?}", e).into()),
};
let img_buffer = [0u8; G13_LCD_BUF_SIZE as usize + 8];
let mut buffer = [0u8; G13_LCD_BUF_SIZE as usize + 32 + 8];
buffer[0] = 0x03;
buffer[32..G13_LCD_BUF_SIZE as usize + 32 + 8].copy_from_slice(&img_buffer);
let mut w = interface.endpoint::<Interrupt, Out>(0x02)?.writer(64);
w.write_all(&buffer)?;
w.flush()?;
Ok(Self {
interface,
img_buffer,
state: Arc::new(RwLock::new(Default::default())),
})
}
pub fn state(&self) -> State {
*self.state.read().expect("Poisoned")
}
pub fn read(&self) -> Result<State, Box<dyn std::error::Error>> {
let mut state = self.state();
state.previous_buttons = state.buttons;
let mut reader = self.interface.endpoint::<Interrupt, In>(0x81)?.reader(8);
let mut buf = [0; 8];
reader.read_exact(&mut buf)?;
state.x = (((buf[1] as f64) / 256.0 * 1024.0) - 512.0) as i32;
state.y = (((buf[2] as f64) / 256.0 * 1024.0) - 512.0) as i32;
state.buttons.action = buf[6] & 0b00000001 != 0;
state.buttons.screen1 = buf[6] & 0b00000010 != 0;
state.buttons.screen2 = buf[6] & 0b00000100 != 0;
state.buttons.screen3 = buf[6] & 0b00001000 != 0;
state.buttons.screen4 = buf[6] & 0b00010000 != 0;
state.buttons.light = buf[7] & 0b0100000 != 0;
state.buttons.m1 = buf[6] & 0b00100000 != 0;
state.buttons.m2 = buf[6] & 0b01000000 != 0;
state.buttons.m3 = buf[6] & 0b10000000 != 0;
state.buttons.mr = buf[7] & 0b00000001 != 0;
state.buttons.g1 = buf[3] & 0b00000001 != 0;
state.buttons.g2 = buf[3] & 0b00000010 != 0;
state.buttons.g3 = buf[3] & 0b00000100 != 0;
state.buttons.g4 = buf[3] & 0b00001000 != 0;
state.buttons.g5 = buf[3] & 0b00010000 != 0;
state.buttons.g6 = buf[3] & 0b00100000 != 0;
state.buttons.g7 = buf[3] & 0b01000000 != 0;
state.buttons.g8 = buf[3] & 0b10000000 != 0;
state.buttons.g9 = buf[4] & 0b00000001 != 0;
state.buttons.g10 = buf[4] & 0b00000010 != 0;
state.buttons.g11 = buf[4] & 0b00000100 != 0;
state.buttons.g12 = buf[4] & 0b00001000 != 0;
state.buttons.g13 = buf[4] & 0b00010000 != 0;
state.buttons.g14 = buf[4] & 0b00100000 != 0;
state.buttons.g15 = buf[4] & 0b01000000 != 0;
state.buttons.g16 = buf[4] & 0b10000000 != 0;
state.buttons.g17 = buf[5] & 0b00000001 != 0;
state.buttons.g18 = buf[5] & 0b00000010 != 0;
state.buttons.g19 = buf[5] & 0b00000100 != 0;
state.buttons.g20 = buf[5] & 0b00001000 != 0;
state.buttons.g21 = buf[5] & 0b00010000 != 0;
state.buttons.g22 = buf[5] & 0b00100000 != 0;
state.buttons.stick1 = buf[7] & 0b00000010 != 0;
state.buttons.stick2 = buf[7] & 0b00000100 != 0;
state.buttons.stick3 = buf[7] & 0b00001000 != 0;
*self.state.write().expect("Poisoned") = state;
Ok(state)
}
pub fn set_lcd_color(&self, r: u8, g: u8, b: u8) -> Result<(), Box<dyn std::error::Error>> {
self.interface
.control_out(
ControlOut {
control_type: ControlType::Class,
recipient: Recipient::Interface,
request: 9,
value: 0x307,
index: 0,
data: &[5, r, g, b, 0],
},
Duration::from_secs(2),
)
.wait()?;
Ok(())
}
pub fn render(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let mut buffer = [0u8; G13_LCD_BUF_SIZE as usize + 32 + 8];
buffer[0] = 0x03;
buffer[32..G13_LCD_BUF_SIZE as usize + 32 + 8].copy_from_slice(&self.img_buffer);
let mut w = self.interface.endpoint::<Interrupt, Out>(0x02)?.writer(64);
w.write_all(&buffer)?;
w.flush()?;
Ok(())
}
}
impl Dimensions for G13 {
fn bounding_box(&self) -> Rectangle {
Rectangle::new(
Point::new(0, 0),
Size::new(G13_LCD_COLUMNS as u32, G13_LCD_ROWS as u32),
)
}
}
impl DrawTarget for G13 {
type Color = BinaryColor;
type Error = Box<dyn std::error::Error>;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = embedded_graphics_core::Pixel<Self::Color>>,
{
for p in pixels {
if p.0.x < 0 || p.0.x > G13_LCD_COLUMNS - 1 || p.0.y < 0 || p.0.y > G13_LCD_ROWS - 1 {
continue;
}
let offset = p.0.x + (p.0.y / 8) * (G13_LCD_BYTES_PER_ROW) * 8;
let mask = 1 << (p.0.y.rem_euclid(8));
if p.1.is_on() {
self.img_buffer[offset as usize] |= mask;
} else {
self.img_buffer[offset as usize] &= !mask;
}
}
Ok(())
}
}

19
joystick/Cargo.toml Normal file
View File

@@ -0,0 +1,19 @@
[package]
name = "g13-joystick"
version.workspace = true
edition.workspace = true
[dependencies]
g13-driver.workspace = true
input-linux = "0.7.1"
embedded-graphics = "0.8.1"
time = { version = "0.3.47", features = ["formatting", "macros"] }
tokio = { version = "1.49.0", features = [
"rt",
"rt-multi-thread",
"sync",
"macros",
"net",
"signal",
"time",
] }

28
joystick/src/error.rs Normal file
View File

@@ -0,0 +1,28 @@
use std::{error, fmt, io};
#[derive(Debug)]
pub enum Error {
IoError(io::Error),
OutOfRangeError { min: i32, max: i32, actual: i32 },
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self {
Error::IoError(io_error) => write!(f, "IoError: {}", io_error),
Error::OutOfRangeError { min, max, actual } => write!(
f,
"OutOfRangeError: min: {}, max: {}, actual: {}",
min, max, actual
),
}
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::IoError(e)
}
}
impl error::Error for Error {}

157
joystick/src/joystick.rs Normal file
View File

@@ -0,0 +1,157 @@
// gracefully stolen from https://github.com/gwilymk/arduino-joystick/tree/master/joystick-daemon/src/joystick <3
pub use crate::error::Error;
use g13_driver::joystick::{Axis, Button};
use input_linux::sys;
use std::{fs, sync::Arc};
#[derive(Clone)]
pub struct Joystick {
device: Arc<input_linux::UInputHandle<fs::File>>,
}
impl Joystick {
pub fn new(name: &str) -> Result<Self, Error> {
let device = create_joystick_device(name)?;
Ok(Joystick {
device: Arc::new(device),
})
}
pub fn move_axis(&self, axis: Axis, position: i32) -> Result<(), Error> {
if !(-512..=512).contains(&position) {
return Err(Error::OutOfRangeError {
min: -512,
max: 512,
actual: position,
});
}
self.write_event(input_linux::AbsoluteEvent::new(
empty_event_time(),
to_evdev_axis(axis),
position,
))
}
pub fn button_press(&self, button: Button, is_pressed: bool) -> Result<(), Error> {
let value = if is_pressed {
input_linux::KeyState::PRESSED
} else {
input_linux::KeyState::RELEASED
};
self.write_event(input_linux::KeyEvent::new(
empty_event_time(),
to_evdev_button(button),
value,
))
}
pub fn synchronise(&self) -> Result<(), Error> {
self.write_event(input_linux::SynchronizeEvent::report(empty_event_time()))
}
fn write_event(&self, event: impl std::convert::AsRef<sys::input_event>) -> Result<(), Error> {
self.device.write(&[*event.as_ref()])?;
Ok(())
}
}
fn empty_event_time() -> input_linux::EventTime {
input_linux::EventTime::new(0, 0)
}
fn create_joystick_device(name: &str) -> Result<input_linux::UInputHandle<fs::File>, Error> {
let uinput_file = fs::File::create("/dev/uinput")?;
let device = input_linux::UInputHandle::new(uinput_file);
let input_id = input_linux::InputId {
bustype: sys::BUS_VIRTUAL,
vendor: 34,
product: 10,
version: 1,
};
let standard_info = input_linux::AbsoluteInfo {
value: 0,
minimum: -512,
maximum: 512,
fuzz: 0,
flat: 0,
resolution: 50,
};
device.set_evbit(input_linux::EventKind::Absolute)?;
device.set_evbit(input_linux::EventKind::Key)?;
device.set_keybit(input_linux::Key::ButtonTrigger)?;
for button in Button::all_buttons() {
device.set_keybit(to_evdev_button(*button))?;
}
device.create(
&input_id,
name.as_bytes(),
0,
&Axis::all_axes()
.map(|axis| input_linux::AbsoluteInfoSetup {
axis: to_evdev_axis(*axis),
info: standard_info,
})
.collect::<Vec<_>>(),
)?;
Ok(device)
}
fn to_evdev_axis(axis: Axis) -> input_linux::AbsoluteAxis {
match axis {
Axis::X => input_linux::AbsoluteAxis::RX,
Axis::Y => input_linux::AbsoluteAxis::RY,
}
}
fn to_evdev_button(button: Button) -> input_linux::Key {
match button {
Button::Action => input_linux::Key::ButtonBack,
Button::Screen1 => input_linux::Key::Button0,
Button::Screen2 => input_linux::Key::Button1,
Button::Screen3 => input_linux::Key::Button2,
Button::Screen4 => input_linux::Key::Button3,
Button::Light => input_linux::Key::Button4,
Button::M1 => input_linux::Key::Button5,
Button::M2 => input_linux::Key::Button6,
Button::M3 => input_linux::Key::Button7,
Button::MR => input_linux::Key::Button8,
Button::G1 => input_linux::Key::Button9,
Button::G2 => input_linux::Key::Unknown10A,
Button::G3 => input_linux::Key::Unknown10B,
Button::G4 => input_linux::Key::Unknown10C,
Button::G5 => input_linux::Key::Unknown10D,
Button::G6 => input_linux::Key::Unknown10E,
Button::G7 => input_linux::Key::Unknown10F,
Button::G8 => input_linux::Key::Unknown118,
Button::G9 => input_linux::Key::Unknown119,
Button::G10 => input_linux::Key::Unknown11A,
Button::G11 => input_linux::Key::Unknown11B,
Button::G12 => input_linux::Key::Unknown11C,
Button::G13 => input_linux::Key::Unknown11D,
Button::G14 => input_linux::Key::Unknown11E,
Button::G15 => input_linux::Key::Unknown11F,
Button::G16 => input_linux::Key::Unknown12C,
Button::G17 => input_linux::Key::Unknown12D,
Button::G18 => input_linux::Key::Unknown12E,
Button::G19 => input_linux::Key::Unknown13F,
Button::G20 => input_linux::Key::Unknown152,
Button::G21 => input_linux::Key::Unknown153,
Button::G22 => input_linux::Key::Unknown154,
Button::Stick1 => input_linux::Key::Unknown155,
Button::Stick2 => input_linux::Key::Unknown156,
Button::Stick3 => input_linux::Key::Unknown157,
}
}

120
joystick/src/main.rs Normal file
View File

@@ -0,0 +1,120 @@
mod error;
mod joystick;
use std::time::Duration;
use embedded_graphics::{
mono_font::{MonoTextStyle, ascii::*},
pixelcolor::BinaryColor,
prelude::*,
text::{Alignment, Text, TextStyleBuilder},
};
use time::{OffsetDateTime, macros::offset};
use tokio::time::Instant;
use g13_driver::{
G13, G13_LCD_COLUMNS, G13_LCD_ROWS,
joystick::{Axis, Button},
};
use crate::joystick::Joystick;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let g13 = G13::new()?;
let joystick = Joystick::new("g13-joystick")?;
g13.set_lcd_color(4, 8, 96)?;
let mut _g13 = g13.clone();
tokio::spawn(async move {
let character_style = MonoTextStyle::new(&FONT_10X20, BinaryColor::On);
let textstyle = TextStyleBuilder::new()
.alignment(Alignment::Center)
.baseline(embedded_graphics::text::Baseline::Middle)
.build();
loop {
let start = Instant::now();
let now = OffsetDateTime::now_utc().to_offset(offset!(+1)); // GMT+1
// Render
let string = format!(
"{:0>2}:{:0>2}:{:0>2}",
now.hour(),
now.minute(),
now.second()
);
_g13.clear(BinaryColor::Off).unwrap();
Text::with_text_style(
&string,
Point::new(
(G13_LCD_COLUMNS as f64 / 2.0) as i32,
(G13_LCD_ROWS as f64 / 2.0) as i32,
),
character_style,
textstyle,
)
.draw(&mut _g13)
.unwrap();
_g13.render().unwrap();
// Calculate delta time
let delta = Instant::now() - start;
tokio::time::sleep(Duration::from_millis(33) - delta).await;
}
});
loop {
let state = g13.read()?;
joystick.move_axis(Axis::X, state.x)?;
joystick.move_axis(Axis::Y, state.y)?;
joystick.button_press(Button::Action, state.buttons.action)?;
joystick.button_press(Button::Screen1, state.buttons.screen1)?;
joystick.button_press(Button::Screen2, state.buttons.screen2)?;
joystick.button_press(Button::Screen3, state.buttons.screen3)?;
joystick.button_press(Button::Screen4, state.buttons.screen4)?;
joystick.button_press(Button::Light, state.buttons.light)?;
joystick.button_press(Button::M1, state.buttons.m1)?;
joystick.button_press(Button::M2, state.buttons.m2)?;
joystick.button_press(Button::M3, state.buttons.m3)?;
joystick.button_press(Button::MR, state.buttons.mr)?;
joystick.button_press(Button::G1, state.buttons.g1)?;
joystick.button_press(Button::G2, state.buttons.g2)?;
joystick.button_press(Button::G3, state.buttons.g3)?;
joystick.button_press(Button::G4, state.buttons.g4)?;
joystick.button_press(Button::G5, state.buttons.g5)?;
joystick.button_press(Button::G6, state.buttons.g6)?;
joystick.button_press(Button::G7, state.buttons.g7)?;
joystick.button_press(Button::G8, state.buttons.g8)?;
joystick.button_press(Button::G9, state.buttons.g9)?;
joystick.button_press(Button::G10, state.buttons.g10)?;
joystick.button_press(Button::G11, state.buttons.g11)?;
joystick.button_press(Button::G12, state.buttons.g12)?;
joystick.button_press(Button::G13, state.buttons.g13)?;
joystick.button_press(Button::G14, state.buttons.g14)?;
joystick.button_press(Button::G15, state.buttons.g15)?;
joystick.button_press(Button::G16, state.buttons.g16)?;
joystick.button_press(Button::G17, state.buttons.g17)?;
joystick.button_press(Button::G18, state.buttons.g18)?;
joystick.button_press(Button::G19, state.buttons.g19)?;
joystick.button_press(Button::G20, state.buttons.g20)?;
joystick.button_press(Button::G21, state.buttons.g21)?;
joystick.button_press(Button::G22, state.buttons.g22)?;
joystick.button_press(Button::Stick1, state.buttons.stick1)?;
joystick.button_press(Button::Stick2, state.buttons.stick2)?;
joystick.button_press(Button::Stick3, state.buttons.stick3)?;
joystick.synchronise()?;
}
}

22
mini-game/Cargo.toml Normal file
View File

@@ -0,0 +1,22 @@
[package]
name = "g13-game"
version.workspace = true
edition.workspace = true
[dependencies]
g13-driver.workspace = true
embedded-graphics = "0.8.1"
# time = { version = "0.3.47", features = ["formatting", "macros"] }
tokio = { version = "1.49.0", features = [
"rt",
"rt-multi-thread",
"sync",
"macros",
"net",
"signal",
"time",
] }
# bevy = { version = "0.18", default-features = false }
# bevy_ecs = { version = "0.18", default-features = false }
# bevy_platform = { version = "0.18", default-features = false }

94
mini-game/src/main.rs Normal file
View File

@@ -0,0 +1,94 @@
use std::time::Duration;
use embedded_graphics::{
image::{Image, ImageRaw},
mono_font::{MonoTextStyle, ascii::*},
pixelcolor::BinaryColor,
prelude::*,
text::{Alignment, Text, TextStyleBuilder},
};
use tokio::time::Instant;
use g13_driver::{
G13, G13_LCD_COLUMNS, G13_LCD_ROWS,
joystick::{Axis, Button},
};
const DEADZONE: i32 = 30;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut g13 = G13::new()?;
let _g13 = g13.clone();
tokio::spawn(async move {
// background thread to update button state
loop {
if let Err(e) = _g13.read() {
eprintln!("{:?}", e);
break; // probably due to unplug
}
}
});
g13.set_lcd_color(4, 8, 96)?;
// let character_style = MonoTextStyle::new(&FONT_5X7, BinaryColor::On);
// let textstyle = TextStyleBuilder::new()
// .baseline(embedded_graphics::text::Baseline::Top)
// .alignment(Alignment::Left)
// .build();
let mut dt = 0.0;
let mut x = 20.0;
let mut y = (G13_LCD_ROWS as f64 / 2.0) - 3.0;
let speed = 20.0;
let player_sprite = ImageRaw::<BinaryColor>::new(DATA, 16);
loop {
let start = Instant::now();
let mut jx = g13.state().x;
if (-DEADZONE..=DEADZONE).contains(&jx) {
jx = 0;
}
let mut jy: i32 = g13.state().y;
if (-DEADZONE..=DEADZONE).contains(&jy) {
jy = 0;
}
let joyx = jx as f64 / 502.0;
let joyy = jy as f64 / 502.0;
x += joyx * speed * dt;
y += joyy * speed * dt;
// Render
g13.clear(BinaryColor::Off).unwrap();
// Player
let image = Image::new(&player_sprite, Point::new(x as i32, y as i32));
image.draw(&mut g13)?;
// Terrain Generation
g13.render().unwrap();
// Calculate delta time
let delta = Instant::now() - start;
tokio::time::sleep(Duration::from_millis(33) - delta).await;
dt = (Instant::now() - start).as_secs_f64();
}
}
#[rustfmt::skip]
const DATA: &[u8] = &[
0b11100000, 0b00000000,
0b11111111, 0b00000000,
0b10000000, 0b11000000,
0b10011111, 0b00111000,
0b10000000, 0b00000110,
0b01111111, 0b11111111,
];