Inital commit

This commit is contained in:
2026-02-08 18:52:57 +01:00
commit dd94bfdafa
12 changed files with 1124 additions and 0 deletions

1
.gitignore vendored Normal file
View File

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

520
Cargo.lock generated Normal file
View File

@@ -0,0 +1,520 @@
# 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",
"embedded-graphics-core",
"input-linux",
"nusb",
"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.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
[[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"

20
Cargo.toml Normal file
View File

@@ -0,0 +1,20 @@
[package]
name = "g13-driver"
version = "0.1.0"
edition = "2024"
[dependencies]
embedded-graphics = "0.8.1"
embedded-graphics-core = "0.4.0"
input-linux = "0.7.1"
nusb = { version = "0.2.1", features = ["tokio"] }
time = { version = "0.3.47", features = ["formatting", "macros"] }
tokio = { version = "1.49.0", features = [
"rt",
"rt-multi-thread",
"sync",
"macros",
"net",
"signal",
"time",
] }

70
device-info Normal file
View File

@@ -0,0 +1,70 @@
Bus 001 Device 021: ID 046d:c21c Logitech, Inc. G13 Advanced Gameboard
Negotiated speed: Full Speed (12Mbps)
Device Descriptor:
bLength 18
bDescriptorType 1
bcdUSB 2.00
bDeviceClass 0 [unknown]
bDeviceSubClass 0 [unknown]
bDeviceProtocol 0
bMaxPacketSize0 8
idVendor 0x046d Logitech, Inc.
idProduct 0xc21c G13 Advanced Gameboard
bcdDevice 2.03
iManufacturer 0
iProduct 1 G13
iSerial 0
bNumConfigurations 1
Configuration Descriptor:
bLength 9
bDescriptorType 2
wTotalLength 0x0029
bNumInterfaces 1
bConfigurationValue 1
iConfiguration 0
bmAttributes 0x80
(Bus Powered)
MaxPower 500mA
Interface Descriptor:
bLength 9
bDescriptorType 4
bInterfaceNumber 0
bAlternateSetting 0
bNumEndpoints 2
bInterfaceClass 3 Human Interface Device
bInterfaceSubClass 0 [unknown]
bInterfaceProtocol 0
iInterface 0
HID Device Descriptor:
bLength 9
bDescriptorType 33
bcdHID 1.11
bCountryCode 0 Not supported
bNumDescriptors 1
bDescriptorType 34 Report
wDescriptorLength 61
Report Descriptors:
** UNAVAILABLE **
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x81 EP 1 IN
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0008 1x 8 bytes
bInterval 2
Endpoint Descriptor:
bLength 7
bDescriptorType 5
bEndpointAddress 0x02 EP 2 OUT
bmAttributes 3
Transfer Type Interrupt
Synch Type None
Usage Type Data
wMaxPacketSize 0x0040 1x 64 bytes
bInterval 1
Device Status: 0x0000
(Bus Powered)

1
rustfmt.toml Normal file
View File

@@ -0,0 +1 @@
max_width = 120

23
src/g13/joystick/axis.rs Normal file
View File

@@ -0,0 +1,23 @@
use std::slice;
#[derive(Debug)]
pub enum Axis {
X,
Y,
}
impl Axis {
pub(super) fn to_evdev_axis(&self) -> input_linux::AbsoluteAxis {
use Axis::*;
match &self {
X => input_linux::AbsoluteAxis::RX,
Y => input_linux::AbsoluteAxis::RY,
}
}
pub(super) fn all_axes() -> slice::Iter<'static, Self> {
use Axis::*;
[X, Y].iter()
}
}

View File

@@ -0,0 +1,99 @@
use std::slice;
#[derive(Debug)]
pub enum Button {
Back,
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(super) fn to_evdev_button(&self) -> input_linux::Key {
use input_linux::Key::*;
match &self {
Button::Back => ButtonBack,
Button::Screen1 => Button0,
Button::Screen2 => Button1,
Button::Screen3 => Button2,
Button::Screen4 => Button3,
Button::Light => Button4,
Button::M1 => Button5,
Button::M2 => Button6,
Button::M3 => Button7,
Button::MR => Button8,
Button::G1 => Button9,
Button::G2 => Unknown10A,
Button::G3 => Unknown10B,
Button::G4 => Unknown10C,
Button::G5 => Unknown10D,
Button::G6 => Unknown10E,
Button::G7 => Unknown10F,
Button::G8 => Unknown118,
Button::G9 => Unknown119,
Button::G10 => Unknown11A,
Button::G11 => Unknown11B,
Button::G12 => Unknown11C,
Button::G13 => Unknown11D,
Button::G14 => Unknown11E,
Button::G15 => Unknown11F,
Button::G16 => Unknown12C,
Button::G17 => Unknown12D,
Button::G18 => Unknown12E,
Button::G19 => Unknown13F,
Button::G20 => Unknown152,
Button::G21 => Unknown153,
Button::G22 => Unknown154,
Button::Stick1 => Unknown155,
Button::Stick2 => Unknown156,
Button::Stick3 => Unknown157,
}
}
pub(super) fn all_buttons() -> slice::Iter<'static, Self> {
use Button::*;
[
Back, 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()
}
}

28
src/g13/joystick/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 {}

117
src/g13/joystick/mod.rs Normal file
View File

@@ -0,0 +1,117 @@
mod axis;
mod button;
mod error;
pub use axis::Axis;
pub use button::Button;
pub use error::Error;
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() -> Result<Self, Error> {
let device = create_joystick_device()?;
Ok(Joystick {
device: Arc::new(device),
})
}
// pub fn device_path(&self) -> Result<path::PathBuf, Error> {
// Ok(self.device.evdev_path()?)
// }
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(),
axis.to_evdev_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(),
button.to_evdev_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() -> 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)?; // informs linux that this is a joystick
for button in Button::all_buttons() {
device.set_keybit(button.to_evdev_button())?;
}
device.create(
&input_id,
b"g13-joystick",
0,
&Axis::all_axes()
.map(|axis| input_linux::AbsoluteInfoSetup {
axis: axis.to_evdev_axis(),
info: standard_info,
})
.collect::<Vec<_>>(),
)?;
Ok(device)
}

191
src/g13/mod.rs Normal file
View File

@@ -0,0 +1,191 @@
pub mod joystick;
use std::{
io::{Read, Write},
time::Duration,
};
use embedded_graphics_core::{
pixelcolor::BinaryColor,
prelude::{Dimensions, DrawTarget, Point, Size},
primitives::Rectangle,
};
use nusb::{
Interface,
transfer::{ControlOut, ControlType, In, Interrupt, Out, Recipient},
};
use crate::g13::joystick::{Axis, Button, Joystick};
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,
joystick: Joystick,
img_buffer: [u8; G13_LCD_BUF_SIZE as usize + 8],
}
impl G13 {
pub async fn new() -> Result<Self, Box<dyn std::error::Error>> {
let Ok(mut devices) = nusb::list_devices().await 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().await else {
return Err("Unable to open device".into());
};
let _ = device.detach_kernel_driver(0);
let interface = match device.claim_interface(0).await {
Ok(i) => i,
Err(e) => return Err(format!("Unable to claim interface: {:#?}", e).into()),
};
let joystick = Joystick::new()?;
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,
joystick,
img_buffer,
})
}
pub async fn read(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let mut reader = self.interface.endpoint::<Interrupt, In>(0x81)?.reader(8);
let mut buf = [0; 8];
reader.read_exact(&mut buf)?;
self.joystick
.move_axis(Axis::X, (((buf[1] as f64) / 256.0 * 1024.0) - 512.0) as i32)?;
self.joystick
.move_axis(Axis::Y, (((buf[2] as f64) / 256.0 * 1024.0) - 512.0) as i32)?;
// the right most bit of the 7th byte is the back button
self.joystick.button_press(Button::Back, buf[6] & 0b00000001 != 0)?;
self.joystick.button_press(Button::Screen1, buf[6] & 0b00000010 != 0)?;
self.joystick.button_press(Button::Screen2, buf[6] & 0b00000100 != 0)?;
self.joystick.button_press(Button::Screen3, buf[6] & 0b00001000 != 0)?;
self.joystick.button_press(Button::Screen4, buf[6] & 0b00010000 != 0)?;
self.joystick.button_press(Button::Light, buf[7] & 0b0100000 != 0)?;
self.joystick.button_press(Button::M1, buf[6] & 0b00100000 != 0)?;
self.joystick.button_press(Button::M2, buf[6] & 0b01000000 != 0)?;
self.joystick.button_press(Button::M3, buf[6] & 0b10000000 != 0)?;
self.joystick.button_press(Button::MR, buf[7] & 0b00000001 != 0)?;
self.joystick.button_press(Button::G1, buf[3] & 0b00000001 != 0)?;
self.joystick.button_press(Button::G2, buf[3] & 0b00000010 != 0)?;
self.joystick.button_press(Button::G3, buf[3] & 0b00000100 != 0)?;
self.joystick.button_press(Button::G4, buf[3] & 0b00001000 != 0)?;
self.joystick.button_press(Button::G5, buf[3] & 0b00010000 != 0)?;
self.joystick.button_press(Button::G6, buf[3] & 0b00100000 != 0)?;
self.joystick.button_press(Button::G7, buf[3] & 0b01000000 != 0)?;
self.joystick.button_press(Button::G8, buf[3] & 0b10000000 != 0)?;
self.joystick.button_press(Button::G9, buf[4] & 0b00000001 != 0)?;
self.joystick.button_press(Button::G10, buf[4] & 0b00000010 != 0)?;
self.joystick.button_press(Button::G11, buf[4] & 0b00000100 != 0)?;
self.joystick.button_press(Button::G12, buf[4] & 0b00001000 != 0)?;
self.joystick.button_press(Button::G13, buf[4] & 0b00010000 != 0)?;
self.joystick.button_press(Button::G14, buf[4] & 0b00100000 != 0)?;
self.joystick.button_press(Button::G15, buf[4] & 0b01000000 != 0)?;
self.joystick.button_press(Button::G16, buf[4] & 0b10000000 != 0)?;
self.joystick.button_press(Button::G17, buf[5] & 0b00000001 != 0)?;
self.joystick.button_press(Button::G18, buf[5] & 0b00000010 != 0)?;
self.joystick.button_press(Button::G19, buf[5] & 0b00000100 != 0)?;
self.joystick.button_press(Button::G20, buf[5] & 0b00001000 != 0)?;
self.joystick.button_press(Button::G21, buf[5] & 0b00010000 != 0)?;
self.joystick.button_press(Button::G22, buf[5] & 0b00100000 != 0)?;
self.joystick.button_press(Button::Stick1, buf[7] & 0b00000010 != 0)?;
self.joystick.button_press(Button::Stick2, buf[7] & 0b00000100 != 0)?;
self.joystick.button_press(Button::Stick3, buf[7] & 0b00001000 != 0)?;
self.joystick.synchronise()?;
Ok(())
}
pub async 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),
)
.await?;
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(1, 1), Size::new(160, 42))
}
}
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 {
let offset = image_byte_offset(p.0.y, p.0.x);
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(())
}
}
#[inline]
fn image_byte_offset(row: i32, col: i32) -> i32 {
col + (row / 8) * (G13_LCD_BYTES_PER_ROW) * 8
}

53
src/main.rs Normal file
View File

@@ -0,0 +1,53 @@
use std::time::Duration;
use embedded_graphics::{
mono_font::{MonoTextStyle, iso_8859_14::FONT_10X20},
pixelcolor::BinaryColor,
prelude::*,
text::{Alignment, Text, TextStyleBuilder},
};
use time::OffsetDateTime;
use crate::g13::{G13, G13_LCD_COLUMNS, G13_LCD_ROWS};
mod g13;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut g13 = G13::new().await?;
g13.set_lcd_color(64, 0, 64).await?;
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 now = OffsetDateTime::now_utc();
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 / 2, G13_LCD_ROWS / 2),
character_style,
textstyle,
)
.draw(&mut _g13)
.unwrap();
_g13.render().unwrap();
tokio::time::sleep(Duration::from_secs(1)).await;
}
});
loop {
g13.read().await?;
}
}

1
udev Normal file
View File

@@ -0,0 +1 @@
KERNEL=="usb", ATTRS{idVendor}=="", ATTRS{idProduct}=="", "TAG+="systemd", ENV{SYSTEMD_WANTS}="serialdaemon.service"