commit dd94bfdafad17c4fa2959e83a4c01020abff3574 Author: Avii Date: Sun Feb 8 18:52:57 2026 +0100 Inital commit 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..4d4ea1c --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0a6decd --- /dev/null +++ b/Cargo.toml @@ -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", +] } diff --git a/device-info b/device-info new file mode 100644 index 0000000..ca5bcf1 --- /dev/null +++ b/device-info @@ -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) diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..7530651 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 120 diff --git a/src/g13/joystick/axis.rs b/src/g13/joystick/axis.rs new file mode 100644 index 0000000..379f55f --- /dev/null +++ b/src/g13/joystick/axis.rs @@ -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() + } +} diff --git a/src/g13/joystick/button.rs b/src/g13/joystick/button.rs new file mode 100644 index 0000000..034a1f2 --- /dev/null +++ b/src/g13/joystick/button.rs @@ -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() + } +} diff --git a/src/g13/joystick/error.rs b/src/g13/joystick/error.rs new file mode 100644 index 0000000..afdf231 --- /dev/null +++ b/src/g13/joystick/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/src/g13/joystick/mod.rs b/src/g13/joystick/mod.rs new file mode 100644 index 0000000..f9bf91f --- /dev/null +++ b/src/g13/joystick/mod.rs @@ -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>, +} + +impl Joystick { + pub fn new() -> Result { + let device = create_joystick_device()?; + + Ok(Joystick { + device: Arc::new(device), + }) + } + + // pub fn device_path(&self) -> Result { + // 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) -> 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, 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::>(), + )?; + + Ok(device) +} diff --git a/src/g13/mod.rs b/src/g13/mod.rs new file mode 100644 index 0000000..8a1d581 --- /dev/null +++ b/src/g13/mod.rs @@ -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> { + 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::(0x02)?.writer(64); + w.write_all(&buffer)?; + w.flush()?; + + Ok(Self { + interface, + joystick, + img_buffer, + }) + } + + pub async fn read(&mut self) -> Result<(), Box> { + let mut reader = self.interface.endpoint::(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> { + 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> { + 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(1, 1), Size::new(160, 42)) + } +} + +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 { + 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 +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2e09906 --- /dev/null +++ b/src/main.rs @@ -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> { + 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?; + } +} diff --git a/udev b/udev new file mode 100644 index 0000000..05310d9 --- /dev/null +++ b/udev @@ -0,0 +1 @@ +KERNEL=="usb", ATTRS{idVendor}=="", ATTRS{idProduct}=="", "TAG+="systemd", ENV{SYSTEMD_WANTS}="serialdaemon.service" \ No newline at end of file