From 498d0b1ba8e52de62e816e03e405108bd1b9b3ad Mon Sep 17 00:00:00 2001 From: Avii Date: Tue, 10 Feb 2026 12:32:01 +0100 Subject: [PATCH] Pre-bevy minigame commit --- .gitignore | 1 + Cargo.lock | 536 ++++++++++++++++++++++++++++++++++ Cargo.toml | 11 + driver/Cargo.toml | 8 + driver/src/joystick/axis.rs | 14 + driver/src/joystick/button.rs | 58 ++++ driver/src/joystick/mod.rs | 7 + driver/src/lib.rs | 241 +++++++++++++++ joystick/Cargo.toml | 19 ++ joystick/src/error.rs | 28 ++ joystick/src/joystick.rs | 157 ++++++++++ joystick/src/main.rs | 120 ++++++++ mini-game/Cargo.toml | 22 ++ mini-game/src/main.rs | 94 ++++++ 14 files changed, 1316 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 driver/Cargo.toml create mode 100644 driver/src/joystick/axis.rs create mode 100644 driver/src/joystick/button.rs create mode 100644 driver/src/joystick/mod.rs create mode 100644 driver/src/lib.rs create mode 100644 joystick/Cargo.toml create mode 100644 joystick/src/error.rs create mode 100644 joystick/src/joystick.rs create mode 100644 joystick/src/main.rs create mode 100644 mini-game/Cargo.toml create mode 100644 mini-game/src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2d39a20 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..553f0d1 --- /dev/null +++ b/Cargo.toml @@ -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" diff --git a/driver/Cargo.toml b/driver/Cargo.toml new file mode 100644 index 0000000..8a50fbb --- /dev/null +++ b/driver/Cargo.toml @@ -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"] } diff --git a/driver/src/joystick/axis.rs b/driver/src/joystick/axis.rs new file mode 100644 index 0000000..e1c6a4c --- /dev/null +++ b/driver/src/joystick/axis.rs @@ -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() + } +} diff --git a/driver/src/joystick/button.rs b/driver/src/joystick/button.rs new file mode 100644 index 0000000..92a895e --- /dev/null +++ b/driver/src/joystick/button.rs @@ -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() + } +} diff --git a/driver/src/joystick/mod.rs b/driver/src/joystick/mod.rs new file mode 100644 index 0000000..8f44429 --- /dev/null +++ b/driver/src/joystick/mod.rs @@ -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; diff --git a/driver/src/lib.rs b/driver/src/lib.rs new file mode 100644 index 0000000..a380f27 --- /dev/null +++ b/driver/src/lib.rs @@ -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>, +} + +#[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> { + 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::(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> { + let mut state = self.state(); + + state.previous_buttons = state.buttons; + let mut reader = self.interface.endpoint::(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> { + 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> { + 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::(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; + + fn draw_iter(&mut self, pixels: I) -> Result<(), Self::Error> + where + I: IntoIterator>, + { + 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(()) + } +} diff --git a/joystick/Cargo.toml b/joystick/Cargo.toml new file mode 100644 index 0000000..12ca9d4 --- /dev/null +++ b/joystick/Cargo.toml @@ -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", +] } diff --git a/joystick/src/error.rs b/joystick/src/error.rs new file mode 100644 index 0000000..afdf231 --- /dev/null +++ b/joystick/src/error.rs @@ -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 for Error { + fn from(e: io::Error) -> Self { + Error::IoError(e) + } +} + +impl error::Error for Error {} diff --git a/joystick/src/joystick.rs b/joystick/src/joystick.rs new file mode 100644 index 0000000..4f8b3a0 --- /dev/null +++ b/joystick/src/joystick.rs @@ -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>, +} + +impl Joystick { + pub fn new(name: &str) -> Result { + 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) -> 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, 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::>(), + )?; + + 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, + } +} diff --git a/joystick/src/main.rs b/joystick/src/main.rs new file mode 100644 index 0000000..cbed6bd --- /dev/null +++ b/joystick/src/main.rs @@ -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> { + 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()?; + } +} diff --git a/mini-game/Cargo.toml b/mini-game/Cargo.toml new file mode 100644 index 0000000..fb11bac --- /dev/null +++ b/mini-game/Cargo.toml @@ -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 } diff --git a/mini-game/src/main.rs b/mini-game/src/main.rs new file mode 100644 index 0000000..0e4a1f7 --- /dev/null +++ b/mini-game/src/main.rs @@ -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> { + 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::::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, +];