Compare commits

..

10 Commits

Author SHA1 Message Date
c866d1dfd7 nicer bound check and renamed render to flush 2026-02-17 17:04:37 +01:00
e9949a7526 Joystick fix 2026-02-15 00:12:58 +01:00
f6ec4492f1 I can haz Label? 2026-02-14 20:13:16 +01:00
81971410ce @benjamin.brienen held 2026-02-14 18:31:46 +01:00
87490152bd This fundamentally doesn't work..? 2026-02-14 16:17:30 +01:00
112820bf1f Can i haz Structure? 2026-02-14 03:03:26 +01:00
85f1154c03 input as events 2026-02-13 19:02:40 +01:00
37d834d80b mouse stuff 2026-02-12 22:59:27 +01:00
3db5b5e094 input and text and stuff 2026-02-12 17:14:50 +01:00
5b27529cf8 x and y is not local, dumbass 2026-02-12 15:24:34 +01:00
21 changed files with 1038 additions and 430 deletions

169
Cargo.lock generated
View File

@@ -103,6 +103,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "aliasable"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
[[package]] [[package]]
name = "aligned" name = "aligned"
version = "0.4.3" version = "0.4.3"
@@ -2376,6 +2382,19 @@ dependencies = [
"byteorder", "byteorder",
] ]
[[package]]
name = "embedded-graphics-simulator"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31606a4fb7d9d3a79a38d27bc2954cfa98682c8fea4b22c09a442785a80424e"
dependencies = [
"base64",
"embedded-graphics",
"image",
"ouroboros",
"sdl2",
]
[[package]] [[package]]
name = "encase" name = "encase"
version = "0.12.0" version = "0.12.0"
@@ -2748,6 +2767,7 @@ dependencies = [
name = "g13-driver" name = "g13-driver"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"crossbeam-channel",
"embedded-graphics-core", "embedded-graphics-core",
"nusb", "nusb",
] ]
@@ -2776,16 +2796,20 @@ dependencies = [
"g13-driver", "g13-driver",
"input-linux", "input-linux",
"time", "time",
"tokio",
] ]
[[package]] [[package]]
name = "g13-os" name = "g13-os"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"crossbeam-channel",
"ctrlc",
"embedded-graphics", "embedded-graphics",
"embedded-graphics-simulator",
"g13-driver", "g13-driver",
"mlua", "mlua",
"time",
"winit",
] ]
[[package]] [[package]]
@@ -3074,6 +3098,12 @@ dependencies = [
"stable_deref_trait", "stable_deref_trait",
] ]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.5.2" version = "0.5.2"
@@ -3447,15 +3477,6 @@ dependencies = [
"imgref", "imgref",
] ]
[[package]]
name = "luau0-src"
version = "0.18.2+luau708"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eed3214ab526e7e7b76c3f324a965d363db94a99ae67f65e67ec6fc499eb5e6d"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "mach2" name = "mach2"
version = "0.4.3" version = "0.4.3"
@@ -3545,17 +3566,6 @@ dependencies = [
"simd-adler32", "simd-adler32",
] ]
[[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]] [[package]]
name = "mlua" name = "mlua"
version = "0.11.6" version = "0.11.6"
@@ -3586,7 +3596,6 @@ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
"libc", "libc",
"luau0-src",
"pkg-config", "pkg-config",
] ]
@@ -4204,6 +4213,30 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "ouroboros"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0f050db9c44b97a94723127e6be766ac5c340c48f2c4bb3ffa11713744be59"
dependencies = [
"aliasable",
"ouroboros_macro",
"static_assertions",
]
[[package]]
name = "ouroboros_macro"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
dependencies = [
"heck",
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
]
[[package]] [[package]]
name = "owned_ttf_parser" name = "owned_ttf_parser"
version = "0.25.1" version = "0.25.1"
@@ -4434,6 +4467,19 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "proc-macro2-diagnostics"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"version_check",
"yansi",
]
[[package]] [[package]]
name = "profiling" name = "profiling"
version = "1.0.17" version = "1.0.17"
@@ -4855,6 +4901,29 @@ dependencies = [
"tiny-skia", "tiny-skia",
] ]
[[package]]
name = "sdl2"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b498da7d14d1ad6c839729bd4ad6fc11d90a57583605f3b4df2cd709a9cd380"
dependencies = [
"bitflags 1.3.2",
"lazy_static",
"libc",
"sdl2-sys",
]
[[package]]
name = "sdl2-sys"
version = "0.37.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "951deab27af08ed9c6068b7b0d05a93c91f0a8eb16b6b816a5e73452a43521d3"
dependencies = [
"cfg-if",
"libc",
"version-compare",
]
[[package]] [[package]]
name = "self_cell" name = "self_cell"
version = "1.2.2" version = "1.2.2"
@@ -4941,16 +5010,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[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]] [[package]]
name = "simd-adler32" name = "simd-adler32"
version = "0.3.8" version = "0.3.8"
@@ -5041,16 +5100,6 @@ dependencies = [
"serde", "serde",
] ]
[[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]] [[package]]
name = "spin" name = "spin"
version = "0.10.0" version = "0.10.0"
@@ -5308,24 +5357,7 @@ version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [ dependencies = [
"libc",
"mio",
"pin-project-lite", "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]] [[package]]
@@ -5557,6 +5589,12 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version-compare"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
@@ -5573,12 +5611,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasip2" name = "wasip2"
version = "1.0.2+wasi-0.2.9" version = "1.0.2+wasi-0.2.9"
@@ -5752,6 +5784,7 @@ checksum = "1e6dbfc3ac5ef974c92a2235805cc0114033018ae1290a72e474aa8b28cbbdfd"
dependencies = [ dependencies = [
"dlib", "dlib",
"log", "log",
"once_cell",
"pkg-config", "pkg-config",
] ]
@@ -6633,6 +6666,12 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448" checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
[[package]]
name = "yansi"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
[[package]] [[package]]
name = "yazi" name = "yazi"
version = "0.2.1" version = "0.2.1"

View File

@@ -9,3 +9,4 @@ edition = "2024"
[workspace.dependencies] [workspace.dependencies]
g13-driver.path = "./driver" g13-driver.path = "./driver"
crossbeam-channel = "0.5.15"

54
apps/main/main.lua Normal file
View File

@@ -0,0 +1,54 @@
name = "DVD"
authors = { "AviiNL" }
description = "Bouncing 'dvd' Logo"
local width = g13.display_width
local height = g13.display_height
local x = 22.0
local y = 6.0
local x_direction = 1
local y_direction = 1
local speed = 20
local size = 3
function setup()
joy.deadzone = 40
end
function update(delta)
x = x + speed * delta * joy.x * 1.2
y = y + speed * delta * joy.y
local color_x = (x / width) * 255
local color_y = (y / height) * 255
local color_z = 255 - (color_x + color_y) / 2
g13.clear()
g13.set_color(color_x / 2, color_y / 2, color_z / 2)
g13.text("Hello World!", 1, 1, true, 0, 0, 1)
for sy = -size, size do
for sx = -size, size do
g13.set_pixel(x + sx, y + sy, true)
end
end
end
function dump(o)
if type(o) == 'table' then
local s = '{ '
for k, v in pairs(o) do
if type(k) ~= 'number' then k = '"' .. k .. '"' end
s = s .. '[' .. k .. '] = ' .. dump(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end

View File

@@ -6,3 +6,4 @@ edition.workspace = true
[dependencies] [dependencies]
embedded-graphics-core = "0.4.0" embedded-graphics-core = "0.4.0"
nusb = { version = "0.2.1", features = ["tokio"] } nusb = { version = "0.2.1", features = ["tokio"] }
crossbeam-channel.workspace = true

View File

@@ -43,6 +43,11 @@ pub enum Button {
Stick1, Stick1,
Stick2, Stick2,
Stick3, Stick3,
StickUp,
StickDown,
StickLeft,
StickRight,
} }
impl Button { impl Button {
@@ -51,7 +56,7 @@ impl Button {
[ [
Action, Screen1, Screen2, Screen3, Screen4, Light, M1, M2, M3, MR, G1, G2, G3, G4, G5, 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, G6, G7, G8, G9, G10, G11, G12, G13, G14, G15, G16, G17, G18, G19, G20, G21, G22,
Stick1, Stick2, Stick3, Stick1, Stick2, Stick3, StickUp, StickDown, StickLeft, StickRight,
] ]
.iter() .iter()
} }

View File

@@ -1,12 +1,15 @@
pub mod joystick; pub mod joystick;
use std::{ use std::{
collections::{HashMap, hash_map::Entry},
io::{Read, Write}, io::{Read, Write},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
time::Duration, time::Duration,
}; };
use crossbeam_channel::{Receiver, bounded};
use embedded_graphics_core::{ use embedded_graphics_core::{
Pixel,
pixelcolor::BinaryColor, pixelcolor::BinaryColor,
prelude::{Dimensions, DrawTarget, Point, Size}, prelude::{Dimensions, DrawTarget, Point, Size},
primitives::Rectangle, primitives::Rectangle,
@@ -16,66 +19,88 @@ use nusb::{
transfer::{ControlOut, ControlType, In, Interrupt, Out, Recipient}, transfer::{ControlOut, ControlType, In, Interrupt, Out, Recipient},
}; };
use crate::joystick::Button;
pub const G13_LCD_COLUMNS: i32 = 160; pub const G13_LCD_COLUMNS: i32 = 160;
pub const G13_LCD_ROWS: i32 = 43; pub const G13_LCD_ROWS: i32 = 43;
pub const G13_LCD_BYTES_PER_ROW: i32 = G13_LCD_COLUMNS / 8; 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; pub const G13_LCD_BUF_SIZE: i32 = (G13_LCD_ROWS + 5) * G13_LCD_BYTES_PER_ROW;
#[derive(Clone)] const DEADZONE: f32 = 10.0;
const CENTER: f32 = 127.5;
const RANGE: f32 = 117.5;
const BUTTON_MAP: &[(Button, usize, u8)] = &[
// buf[6]
(Button::Action, 6, 0b00000001),
(Button::Screen1, 6, 0b00000010),
(Button::Screen2, 6, 0b00000100),
(Button::Screen3, 6, 0b00001000),
(Button::Screen4, 6, 0b00010000),
(Button::M1, 6, 0b00100000),
(Button::M2, 6, 0b01000000),
(Button::M3, 6, 0b10000000),
// buf[7]
(Button::Light, 7, 0b01000000),
(Button::MR, 7, 0b00000001),
(Button::Stick1, 7, 0b00000010),
(Button::Stick2, 7, 0b00000100),
(Button::Stick3, 7, 0b00001000),
// buf[3]
(Button::G1, 3, 0b00000001),
(Button::G2, 3, 0b00000010),
(Button::G3, 3, 0b00000100),
(Button::G4, 3, 0b00001000),
(Button::G5, 3, 0b00010000),
(Button::G6, 3, 0b00100000),
(Button::G7, 3, 0b01000000),
(Button::G8, 3, 0b10000000),
// buf[4]
(Button::G9, 4, 0b00000001),
(Button::G10, 4, 0b00000010),
(Button::G11, 4, 0b00000100),
(Button::G12, 4, 0b00001000),
(Button::G13, 4, 0b00010000),
(Button::G14, 4, 0b00100000),
(Button::G15, 4, 0b01000000),
(Button::G16, 4, 0b10000000),
// buf[5]
(Button::G17, 5, 0b00000001),
(Button::G18, 5, 0b00000010),
(Button::G19, 5, 0b00000100),
(Button::G20, 5, 0b00001000),
(Button::G21, 5, 0b00010000),
(Button::G22, 5, 0b00100000),
];
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Vec2<T>
where
T: Copy,
{
pub x: T,
pub y: T,
}
impl<T> Vec2<T>
where
T: Copy,
{
pub fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
pub struct G13 { pub struct G13 {
rx: Option<Receiver<G13Event>>,
interface: Interface, interface: Interface,
img_buffer: Arc<RwLock<[u8; G13_LCD_BUF_SIZE as usize + 8]>>, img_buffer: Arc<RwLock<[u8; G13_LCD_BUF_SIZE as usize + 8]>>,
state: Arc<RwLock<State>>,
}
#[derive(Debug, Clone, Copy, Default)]
pub struct State {
pub x: i32,
pub y: i32,
pub buttons: Buttons,
}
#[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 { impl G13 {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> { pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
let img_buffer = [0u8; G13_LCD_BUF_SIZE as usize + 8];
let Ok(mut devices) = nusb::list_devices().wait() else { let Ok(mut devices) = nusb::list_devices().wait() else {
return Err("No devices found".into()); return Err("No devices found".into());
}; };
@@ -96,82 +121,118 @@ impl G13 {
Err(e) => return Err(format!("Unable to claim interface: {:#?}", e).into()), Err(e) => return Err(format!("Unable to claim interface: {:#?}", e).into()),
}; };
let img_buffer = [0u8; G13_LCD_BUF_SIZE as usize + 8]; let input = G13Input::new(interface.clone());
let mut buffer = [0u8; G13_LCD_BUF_SIZE as usize + 32 + 8]; let (tx, rx) = bounded(0);
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); let _tx = tx.clone();
w.write_all(&buffer)?; std::thread::spawn(move || {
w.flush()?; for event in input {
let Some(event) = event else {
continue;
};
if _tx.send(event).is_err() {
break;
}
}
});
Ok(Self { Ok(Self {
interface, interface,
img_buffer: Arc::new(RwLock::new(img_buffer)), img_buffer: Arc::new(RwLock::new(img_buffer)),
state: Arc::new(RwLock::new(Default::default())), rx: Some(rx),
}) })
} }
pub fn state(&self) -> State { pub fn events(&mut self) -> Receiver<G13Event> {
*self.state.read().expect("Poisoned") self.rx.take().expect("events() can only be called once")
}
} }
pub fn read(&self) -> Result<State, Box<dyn std::error::Error>> { #[derive(Debug, Copy, Clone)]
let mut state = self.state(); pub enum G13Event {
Axis(f32, f32),
Button(Button, (bool, bool)), // (old, new)
}
let mut reader = self.interface.endpoint::<Interrupt, In>(0x81)?.reader(8); pub struct G13Input {
interface: Interface,
buttons: HashMap<Button, bool>,
axis: Vec2<f32>,
}
impl G13Input {
pub fn new(interface: Interface) -> Self {
Self {
interface,
buttons: HashMap::new(),
axis: Default::default(),
}
}
fn handle_button(&mut self, button: Button, value: bool) -> Option<G13Event> {
if let Entry::Vacant(e) = self.buttons.entry(button) {
e.insert(value);
if value {
return Some(G13Event::Button(button, (!value, value)));
}
}
let prev = self.buttons[&button];
if self.buttons[&button] == value {
return None;
}
self.buttons.insert(button, value);
Some(G13Event::Button(button, (prev, value)))
}
}
impl Iterator for G13Input {
type Item = Option<G13Event>;
fn next(&mut self) -> Option<Self::Item> {
let mut reader = self
.interface
.endpoint::<Interrupt, In>(0x81)
.ok()?
.reader(8);
let mut buf = [0; 8]; let mut buf = [0; 8];
reader.read_exact(&mut buf)?; reader.read_exact(&mut buf).ok()?;
state.x = (((buf[1] as f64) / 256.0 * 1024.0) - 512.0) as i32; for &(button, index, mask) in BUTTON_MAP {
state.y = (((buf[2] as f64) / 256.0 * 1024.0) - 512.0) as i32; if let Some(event) = self.handle_button(button, buf[index] & mask != 0) {
return Some(Some(event));
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)
} }
let axis = Vec2::new(buf[1], buf[2]);
let mut input = Vec2::new(axis.x as f32 - CENTER, axis.y as f32 - CENTER);
input.x = apply_deadzone(input.x);
input.y = apply_deadzone(input.y);
if self.axis == input {
return Some(None);
}
self.axis = input;
Some(Some(G13Event::Axis(input.x / RANGE, input.y / RANGE)))
}
}
fn apply_deadzone(v: f32) -> f32 {
if v.abs() <= DEADZONE {
0.0
} else if v > 0.0 {
v - DEADZONE
} else {
v + DEADZONE
}
}
impl G13 {
pub fn set_lcd_color(&self, r: u8, g: u8, b: u8) -> Result<(), Box<dyn std::error::Error>> { pub fn set_lcd_color(&self, r: u8, g: u8, b: u8) -> Result<(), Box<dyn std::error::Error>> {
self.interface self.interface
.control_out( .control_out(
@@ -190,7 +251,7 @@ impl G13 {
Ok(()) Ok(())
} }
pub fn render(&mut self) -> Result<(), Box<dyn std::error::Error>> { pub fn flush(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let img_buffer = self.img_buffer.read().expect("Poisoned"); let img_buffer = self.img_buffer.read().expect("Poisoned");
let mut buffer = [0u8; G13_LCD_BUF_SIZE as usize + 32 + 8]; let mut buffer = [0u8; G13_LCD_BUF_SIZE as usize + 32 + 8];
@@ -202,6 +263,10 @@ impl G13 {
w.flush()?; w.flush()?;
Ok(()) Ok(())
} }
pub fn size(&self) -> Size {
Size::new(G13_LCD_COLUMNS as u32, G13_LCD_ROWS as u32)
}
} }
impl Dimensions for G13 { impl Dimensions for G13 {
@@ -221,16 +286,17 @@ impl DrawTarget for G13 {
where where
I: IntoIterator<Item = embedded_graphics_core::Pixel<Self::Color>>, I: IntoIterator<Item = embedded_graphics_core::Pixel<Self::Color>>,
{ {
let bb = self.bounding_box();
let mut img_buffer = self.img_buffer.write().expect("Poisoned"); let mut img_buffer = self.img_buffer.write().expect("Poisoned");
for p in pixels { for Pixel(pos, color) 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 { if !bb.contains(pos) {
continue; continue;
} }
let offset = p.0.x + (p.0.y / 8) * (G13_LCD_BYTES_PER_ROW) * 8; let offset = pos.x + (pos.y / 8) * (G13_LCD_BYTES_PER_ROW) * 8;
let mask = 1 << (p.0.y.rem_euclid(8)); let mask = 1 << (pos.y.rem_euclid(8));
if p.1.is_on() { if color.is_on() {
img_buffer[offset as usize] |= mask; img_buffer[offset as usize] |= mask;
} else { } else {
img_buffer[offset as usize] &= !mask; img_buffer[offset as usize] &= !mask;

View File

@@ -4,6 +4,12 @@ version.workspace = true
edition.workspace = true edition.workspace = true
[dependencies] [dependencies]
g13-driver.workspace = true crossbeam-channel.workspace = true
ctrlc = "3.5"
embedded-graphics = "0.8" embedded-graphics = "0.8"
mlua = { version = "0.11.6", features = ["luau-jit", "async", "macros", "serde"] } g13-driver.workspace = true
mlua = { version = "0.11.6", features = ["lua54", "async", "macros", "serde"] }
time = { version = "0.3.47", features = ["formatting", "macros"] }
embedded-graphics-simulator = "0.7.0"
winit = "0.30.12"

36
g13-os/src/app/mod.rs Normal file
View File

@@ -0,0 +1,36 @@
pub(super) mod pages;
pub(super) mod widgets;
use std::collections::VecDeque;
use g13_driver::{G13, G13Event};
use crate::app::pages::StartPage;
use super::app::pages::Page;
pub struct App {
pages: VecDeque<Box<dyn Page<Display = G13>>>,
}
impl App {
pub fn new() -> Self {
let mut pages: VecDeque<Box<dyn Page<Display = G13>>> = VecDeque::new();
pages.push_back(Box::new(StartPage::new()));
Self { pages }
}
pub fn update(&mut self, event: G13Event) {
if let Some(last_page) = self.pages.iter_mut().last() {
last_page.update(event);
}
}
pub fn draw(&mut self, display: &mut G13) {
if let Some(last_page) = self.pages.iter_mut().last() {
last_page.draw(display);
}
}
}

View File

@@ -0,0 +1,7 @@
use crate::app::widgets::Widget;
mod start;
pub trait Page: Widget {}
pub use start::*;

View File

@@ -0,0 +1,73 @@
use embedded_graphics::{
mono_font::ascii::FONT_6X10,
pixelcolor::BinaryColor,
prelude::Point,
text::{Alignment, Baseline},
};
use g13_driver::{G13, G13Event, Vec2};
use crate::app::widgets::{Widget, label::Label};
pub struct StartPage {
joy: Vec2<f32>,
children: Vec<Box<dyn Widget<Display = G13>>>,
}
impl StartPage {
pub fn new() -> Self {
let children: Vec<Box<dyn Widget<Display = G13>>> = vec![Box::new(Label::new(
"Hello World!",
&FONT_6X10,
BinaryColor::On,
Point::new(10, 0),
Alignment::Left,
Baseline::Top,
))];
Self {
joy: Vec2::default(),
children,
}
}
}
impl Widget for StartPage {
type Display = G13;
fn update(&mut self, event: g13_driver::G13Event) {
if let G13Event::Axis(x, y) = event {
self.joy.x = x;
self.joy.y = y;
}
// This is stupid.
// Need a way to keep some reference to widgets to manipulate them later
// preferably as their original type so we don't need this downcast bs
self.children[0]
.as_any_mut()
.downcast_mut::<Label>()
.unwrap()
.text(&format!(
"{} {}",
(self.joy.x * 512.0) as i32,
(self.joy.y * 512.0) as i32
));
for child in self.children.iter_mut() {
child.update(event);
}
}
fn draw(&mut self, display: &mut Self::Display) {
for child in self.children.iter_mut() {
child.draw(display);
}
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}
// Marker to indicate page
impl super::Page for StartPage {}

View File

@@ -0,0 +1,73 @@
use embedded_graphics::{
mono_font::{MonoFont, MonoTextStyle},
pixelcolor::BinaryColor,
prelude::*,
text::{Alignment, Baseline, Text, TextStyle, TextStyleBuilder},
};
use g13_driver::G13;
// const CHARACTER_STYLE: MonoTextStyle<'static, BinaryColor> =
// MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
// const TEXT_STYLE: TextStyle = TextStyleBuilder::new()
// .baseline(Baseline::Top)
// .alignment(Alignment::Left)
// .build();
pub struct Label {
text: String,
position: Point,
character_style: MonoTextStyle<'static, BinaryColor>,
text_style: TextStyle,
}
impl Label {
pub fn new(
text: &str,
font: &'static MonoFont<'static>,
color: BinaryColor,
position: Point,
alignment: Alignment,
baseline: Baseline,
) -> Self {
let character_style: MonoTextStyle<'static, BinaryColor> = MonoTextStyle::new(font, color);
let text_style: TextStyle = TextStyleBuilder::new()
.baseline(baseline)
.alignment(alignment)
.build();
Self {
text: text.to_owned(),
position,
character_style,
text_style,
}
}
pub fn text(&mut self, text: &str) {
self.text = text.to_owned();
}
}
impl super::Widget for Label {
type Display = G13;
fn update(&mut self, _: g13_driver::G13Event) {
// noop
}
fn draw(&mut self, display: &mut Self::Display) {
let _ = Text::with_text_style(
&self.text,
self.position,
self.character_style,
self.text_style,
)
.draw(display);
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}

View File

@@ -0,0 +1,16 @@
pub mod label;
use std::any::Any;
use embedded_graphics::draw_target::DrawTarget;
use g13_driver::G13Event;
pub trait Widget {
type Display: DrawTarget;
fn update(&mut self, event: G13Event);
fn draw(&mut self, display: &mut Self::Display);
fn as_any_mut(&mut self) -> &mut dyn Any;
}

View File

@@ -1,87 +1,32 @@
use std::{ mod app;
fs,
thread::sleep,
time::{Duration, Instant},
};
use g13_driver::{G13, G13_LCD_COLUMNS, G13_LCD_ROWS}; use std::time::{Duration, Instant};
use mlua::{ExternalResult, Lua};
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::*;
use g13_driver::G13;
use crate::app::App;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let g13 = G13::new()?; let mut g13 = G13::new()?;
let lua = Lua::new();
let globals = lua.globals();
let g13_table = lua.create_table()?; let mut app = App::new();
let _g13 = g13.clone(); let events = g13.events();
g13_table.set(
"set_color",
lua.create_function(move |_, (r, g, b)| _g13.set_lcd_color(r, g, b).into_lua_err())?,
)?;
let _g13 = g13.clone(); let mut start = Instant::now();
let fn_clear = lua.create_function(move |_, on| {
let mut _g13 = _g13.clone();
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::*;
let color = if on {
BinaryColor::On
} else {
BinaryColor::Off
};
_g13.clear(color).into_lua_err()
});
g13_table.set("clear", fn_clear?)?;
let mut _g13 = g13.clone();
let fn_set_pixel = lua.create_function_mut(move |_, (x, y, on)| {
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::*;
let color = if on {
BinaryColor::On
} else {
BinaryColor::Off
};
Pixel(Point::new(x, y), color)
.draw(&mut _g13)
.into_lua_err()
});
g13_table.set("set_pixel", fn_set_pixel?)?;
g13_table.set("display_width", G13_LCD_COLUMNS)?;
g13_table.set("display_height", G13_LCD_ROWS)?;
globals.set("g13", g13_table)?;
// load all files from dir `./scripts` for now, user configurable later or ~/.config/g13-os/*.<lua[u]>
// for now, just main.luau
let main = fs::read_to_string("./scripts/main.luau")?;
lua.load(main).set_name("main.luau").exec()?;
if lua.load("setup ~= nil").eval()? {
lua.load("setup()").exec()?;
}
if lua.load("update ~= nil").eval()? {
let mut _g13 = g13.clone();
let mut delta: f32 = 0.0;
loop { loop {
let start = Instant::now(); if let Ok(event) = events.try_recv() {
app.update(event); // update can happen A LOT more often
lua.load(format!("update({})", delta)).exec()?;
_g13.render()?;
let duration = Instant::now() - start;
// 30 fps lock
if duration < Duration::from_millis(33) {
sleep(Duration::from_millis(33) - duration);
}
delta = (Instant::now() - start).as_secs_f32();
}
} }
Ok(()) if (Instant::now() - start) >= Duration::from_millis(33) {
start = Instant::now();
g13.clear(BinaryColor::Off)?;
app.draw(&mut g13);
g13.flush()?; // 30 fps rendering
}
}
} }

92
g13-os/src/mouse.rs Normal file
View File

@@ -0,0 +1,92 @@
use std::cell::RefCell;
use embedded_graphics::{Drawable, Pixel, pixelcolor::BinaryColor, prelude::Point};
use g13_driver::{G13, G13_LCD_COLUMNS, G13_LCD_ROWS, State};
#[rustfmt::skip]
const MOUSE_CURSOR: &[u8] = &[
2, 0, 0, 0, 0,
2, 2, 0, 0, 0,
2, 1, 2, 0, 0,
2, 1, 1, 2, 0,
2, 1, 2, 2, 2,
2, 2, 0, 0, 0,
];
pub struct Mouse {
deadzone: i32,
speed: f32,
x: RefCell<f32>,
y: RefCell<f32>,
}
impl Default for Mouse {
fn default() -> Self {
Self::new()
}
}
impl Mouse {
pub fn new() -> Self {
Self {
deadzone: 40,
speed: 20.0,
x: RefCell::new(G13_LCD_COLUMNS as f32 / 2.0 - 2.0),
y: RefCell::new(G13_LCD_ROWS as f32 / 2.0 - 3.0),
}
}
pub fn update(&self, state: State, delta: f32) {
let offset: i32 = 512 - self.deadzone;
let mut x = state.x;
let mut y = state.y;
x = if x.abs() < self.deadzone {
0
} else {
x - self.deadzone * x.signum()
};
y = if y.abs() < self.deadzone {
0
} else {
y - self.deadzone * y.signum()
};
let x = x as f32 / offset as f32;
let y = y as f32 / offset as f32;
let mut sx = self.x.borrow_mut();
let mut sy = self.y.borrow_mut();
*sx = (*sx + x * delta * self.speed * 1.2).clamp(0., (G13_LCD_COLUMNS - 1) as f32);
*sy = (*sy + y * delta * self.speed).clamp(0., (G13_LCD_ROWS - 1) as f32);
}
pub fn get_x(&self) -> i32 {
*self.x.borrow() as i32
}
pub fn get_y(&self) -> i32 {
*self.y.borrow() as i32
}
pub fn render(&self, g13: &mut G13) {
for (i, value) in MOUSE_CURSOR.iter().enumerate() {
let x = i as i32 % 5;
let y = i as i32 / 5;
let color = if *value == 1 {
BinaryColor::Off
} else if *value == 2 {
BinaryColor::On
} else {
continue;
};
Pixel(Point::new(self.get_x() + x, self.get_y() + y), color)
.draw(g13)
.expect("G13 to be Connected");
}
}
}

231
g13-os/src/os.rs Normal file
View File

@@ -0,0 +1,231 @@
use std::{
fs::read_dir,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::Instant,
};
use embedded_graphics::{
Drawable,
mono_font::ascii::*,
pixelcolor::BinaryColor,
prelude::{DrawTarget, Point, Primitive, Size},
primitives::{Line, PrimitiveStyle, PrimitiveStyleBuilder, Rectangle},
text::{Alignment, Baseline, Text, TextStyleBuilder},
};
use g13_driver::{G13, G13_LCD_COLUMNS, G13_LCD_ROWS};
use mlua::Lua;
use time::{OffsetDateTime, macros::offset};
use crate::mouse::Mouse;
pub struct App {
name: String,
}
pub struct Button {
label: String,
action: Option<fn()>,
}
pub struct G13Os {
g13: G13,
lua: Lua,
running: Arc<AtomicBool>,
apps: Vec<App>,
running_app: Option<App>,
buttons: [Option<Button>; 4],
mouse: Mouse,
}
impl G13Os {
pub fn new() -> Self {
let g13 = G13::new().expect("G13 to be connected");
let lua = Lua::new();
let running = Arc::new(AtomicBool::new(false));
let mut apps = Vec::new();
let buttons = [
Some(Button {
label: "Settings".to_owned(),
action: None,
}),
// Some(Button {
// label: "Down".to_owned(),
// action: None,
// }),
None,
// Some(Button {
// label: "Up".to_owned(),
// action: None,
// }),
None,
Some(Button {
label: "00:00".to_owned(),
action: None,
}),
];
for entry in read_dir("./apps").unwrap() {
apps.push(App {
name: entry.unwrap().file_name().into_string().unwrap(),
});
}
Self {
g13,
lua,
running,
apps,
running_app: None,
buttons,
mouse: Mouse::new(),
}
}
pub fn run(&self) {
let running = self.running.clone();
running.store(true, Ordering::SeqCst);
ctrlc::set_handler(move || running.store(false, Ordering::SeqCst))
.expect("Error setting Ctrl-C handler");
self.start_input_thread();
let mut g13 = self.g13.clone();
let mut delta = 0.0;
while self.running.load(Ordering::SeqCst) {
let start = Instant::now();
g13.clear(BinaryColor::Off).expect("G13 to be connected");
let mut show_mouse = false;
if self.running_app.is_none() {
self.render_menu();
show_mouse = true;
} else {
// render app
// if app.is_init == false
// app.is_init = true
// app.init()
// ..
// app.update();
}
if show_mouse {
self.mouse.update(g13.state(), delta);
self.mouse.render(&mut g13);
}
g13.render().expect("G13 to be connected");
delta = (Instant::now() - start).as_secs_f32();
}
}
fn render_menu(&self) {
let mut g13 = self.g13.clone();
// todo: const
let button_count = 4;
let button_width = G13_LCD_COLUMNS / button_count;
let fill = PrimitiveStyleBuilder::new()
.fill_color(BinaryColor::On)
.build();
let stroke = PrimitiveStyleBuilder::new()
.stroke_color(BinaryColor::On)
.stroke_width(1)
.build();
let character_style =
embedded_graphics::mono_font::MonoTextStyle::new(&FONT_4X6, BinaryColor::On);
let character_style_inv =
embedded_graphics::mono_font::MonoTextStyle::new(&FONT_4X6, BinaryColor::Off);
let textstyle = TextStyleBuilder::new()
.alignment(Alignment::Center)
.baseline(Baseline::Top)
.build();
for (i, btn) in self.buttons.iter().enumerate() {
let Some(btn) = btn else {
continue;
};
let line_x = i as i32 * button_width;
Line::new(Point::new(line_x, 8), Point::new(line_x + button_width, 8))
.into_styled(stroke)
.draw(&mut g13)
.expect("G13 to be connected");
if i > 0 && i < 4 {
Line::new(Point::new(line_x, 0), Point::new(line_x, 8))
.into_styled(stroke)
.draw(&mut g13)
.expect("G13 to be connected");
}
Line::new(
Point::new(line_x + button_width, 0),
Point::new(line_x + button_width, 8),
)
.into_styled(stroke)
.draw(&mut g13)
.expect("G13 to be connected");
let i = i as i32;
let text_x = ((i + 1) * button_width) - (button_width / 2);
let mut label = btn.label.clone();
if label.len() > 8 {
let _ = label.split_off(8);
}
if label == "00:00" {
let now = OffsetDateTime::now_utc().to_offset(offset!(+1)); // GMT+1
// Render
label = format!(
"{:0>2}:{:0>2}:{:0>2}",
now.hour(),
now.minute(),
now.second()
);
}
let style = if self.mouse.get_x() > i * button_width
&& self.mouse.get_x() < (i + 1) * button_width
&& self.mouse.get_y() < 8
{
Rectangle::new(
Point::new(i * button_width, 0),
Size::new(button_width as u32, 8),
)
.into_styled(fill)
.draw(&mut g13)
.expect("G13 to be Connected");
character_style_inv
} else {
character_style
};
Text::with_text_style(&label, Point::new(text_x, 1), style, textstyle)
.draw(&mut g13)
.expect("G13 to be connected");
}
}
fn start_input_thread(&self) {
let g13 = self.g13.clone();
let running = self.running.clone();
std::thread::spawn(move || {
while running.load(Ordering::SeqCst) {
let _ = g13.read();
}
});
}
}

View File

@@ -8,12 +8,3 @@ g13-driver.workspace = true
input-linux = "0.7.1" input-linux = "0.7.1"
embedded-graphics = "0.8.1" embedded-graphics = "0.8.1"
time = { version = "0.3.47", features = ["formatting", "macros"] } time = { version = "0.3.47", features = ["formatting", "macros"] }
tokio = { version = "1.49.0", features = [
"rt",
"rt-multi-thread",
"sync",
"macros",
"net",
"signal",
"time",
] }

112
joystick/src/app.rs Normal file
View File

@@ -0,0 +1,112 @@
use embedded_graphics::Drawable;
use embedded_graphics::mono_font::MonoTextStyle;
use embedded_graphics::mono_font::ascii::*;
use embedded_graphics::pixelcolor::BinaryColor;
use embedded_graphics::prelude::Point;
use embedded_graphics::text::*;
use g13_driver::G13;
use g13_driver::G13_LCD_COLUMNS;
use g13_driver::G13_LCD_ROWS;
use g13_driver::G13Event;
use g13_driver::joystick::Axis;
use g13_driver::joystick::Button;
use time::OffsetDateTime;
use time::macros::offset;
use crate::joystick::Joystick;
const CHARACTER_STYLE_TIME: MonoTextStyle<'static, BinaryColor> =
MonoTextStyle::new(&FONT_10X20, BinaryColor::On);
const CHARACTER_STYLE_DATE: MonoTextStyle<'static, BinaryColor> =
MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
const TEXTSTYLE: TextStyle = TextStyleBuilder::new()
.alignment(Alignment::Center)
.baseline(embedded_graphics::text::Baseline::Middle)
.build();
pub struct App {
joystick: Joystick,
}
impl App {
pub fn new() -> Self {
let joystick = Joystick::new("g13-joystick").expect("Joystick to be available");
Self { joystick }
}
pub fn update(&mut self, events: G13Event) {
match events {
G13Event::Axis(x, y) => {
let x = (x * 512.0) as i32;
let y = (y * 512.0) as i32;
self.joystick.move_axis(Axis::X, x).ok();
self.joystick.move_axis(Axis::Y, y).ok();
if x <= -500 {
self.joystick.button_press(Button::StickLeft, true).ok();
} else {
self.joystick.button_press(Button::StickLeft, false).ok();
}
if x >= 500 {
self.joystick.button_press(Button::StickRight, true).ok();
} else {
self.joystick.button_press(Button::StickRight, false).ok();
}
if y <= -500 {
self.joystick.button_press(Button::StickUp, true).ok();
} else {
self.joystick.button_press(Button::StickUp, false).ok();
}
if y >= 500 {
self.joystick.button_press(Button::StickDown, true).ok();
} else {
self.joystick.button_press(Button::StickDown, false).ok();
}
}
G13Event::Button(button, (_, value)) => {
self.joystick.button_press(button, value).ok();
}
}
self.joystick.synchronise().ok();
}
pub fn draw(&mut self, display: &mut G13) {
let now = OffsetDateTime::now_utc().to_offset(offset!(+1)); // GMT+1
let time = format!(
"{:0>2}:{:0>2}:{:0>2}",
now.hour(),
now.minute(),
now.second()
);
let date = format!("{}, {} {}", now.weekday(), now.day(), now.month());
Text::with_text_style(
&time,
Point::new(
(G13_LCD_COLUMNS as f64 / 2.0) as i32,
(G13_LCD_ROWS as f64 / 2.0) as i32 - 8,
),
CHARACTER_STYLE_TIME,
TEXTSTYLE,
)
.draw(display)
.ok();
Text::with_text_style(
&date,
Point::new(
(G13_LCD_COLUMNS as f64 / 2.0) as i32,
(G13_LCD_ROWS as f64 / 2.0) as i32 + 8,
),
CHARACTER_STYLE_DATE,
TEXTSTYLE,
)
.draw(display)
.ok();
}
}

View File

@@ -153,5 +153,9 @@ fn to_evdev_button(button: Button) -> input_linux::Key {
Button::Stick1 => input_linux::Key::Unknown155, Button::Stick1 => input_linux::Key::Unknown155,
Button::Stick2 => input_linux::Key::Unknown156, Button::Stick2 => input_linux::Key::Unknown156,
Button::Stick3 => input_linux::Key::Unknown157, Button::Stick3 => input_linux::Key::Unknown157,
Button::StickUp => input_linux::Key::Unknown158,
Button::StickDown => input_linux::Key::Unknown159,
Button::StickLeft => input_linux::Key::Unknown15A,
Button::StickRight => input_linux::Key::Unknown15B,
} }
} }

View File

@@ -1,135 +1,34 @@
mod error; pub(crate) mod app;
mod joystick; pub(crate) mod error;
pub(crate) mod joystick;
use std::time::Duration; use std::time::{Duration, Instant};
use embedded_graphics::{ use embedded_graphics::pixelcolor::BinaryColor;
mono_font::{MonoTextStyle, ascii::*}, use embedded_graphics::prelude::*;
pixelcolor::BinaryColor, use g13_driver::G13;
prelude::*,
text::{Alignment, Text, TextStyleBuilder},
};
use time::{OffsetDateTime, macros::offset};
use tokio::time::Instant;
use g13_driver::{ use crate::app::App;
G13, G13_LCD_COLUMNS, G13_LCD_ROWS,
joystick::{Axis, Button},
};
use crate::joystick::Joystick; fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut g13 = G13::new()?;
#[tokio::main] let mut app = App::new();
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let g13 = G13::new()?;
let joystick = Joystick::new("g13-joystick")?;
g13.set_lcd_color(4, 8, 96)?; g13.set_lcd_color(4, 8, 96)?;
let mut _g13 = g13.clone(); let events = g13.events();
tokio::spawn(async move {
let character_style_time = MonoTextStyle::new(&FONT_10X20, BinaryColor::On);
let character_style_date = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
let textstyle = TextStyleBuilder::new()
.alignment(Alignment::Center)
.baseline(embedded_graphics::text::Baseline::Middle)
.build();
let mut start = Instant::now();
loop { loop {
let start = Instant::now(); if let Ok(event) = events.try_recv() {
let now = OffsetDateTime::now_utc().to_offset(offset!(+1)); // GMT+1 app.update(event);
// Render
let time = format!(
"{:0>2}:{:0>2}:{:0>2}",
now.hour(),
now.minute(),
now.second()
);
let date = format!("{}, {} {}", now.weekday(), now.day(), now.month());
_g13.clear(BinaryColor::Off).unwrap();
Text::with_text_style(
&time,
Point::new(
(G13_LCD_COLUMNS as f64 / 2.0) as i32,
(G13_LCD_ROWS as f64 / 2.0) as i32 - 8,
),
character_style_time,
textstyle,
)
.draw(&mut _g13)
.unwrap();
Text::with_text_style(
&date,
Point::new(
(G13_LCD_COLUMNS as f64 / 2.0) as i32,
(G13_LCD_ROWS as f64 / 2.0) as i32 + 8,
),
character_style_date,
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 { if (Instant::now() - start) >= Duration::from_millis(33) {
let state = g13.read()?; start = Instant::now();
g13.clear(BinaryColor::Off)?;
joystick.move_axis(Axis::X, state.x)?; app.draw(&mut g13);
joystick.move_axis(Axis::Y, state.y)?; g13.flush()?;
}
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()?;
} }
} }

View File

@@ -159,7 +159,7 @@ fn save_buffer_to_disk(
.draw(&mut g13.g13) .draw(&mut g13.g13)
.expect("G13 to be connected"); .expect("G13 to be connected");
let _ = g13.g13.render(); let _ = g13.g13.flush();
} }
} }
} }

View File

@@ -1,43 +0,0 @@
name = "DVD"
authors = {"AviiNL"}
description = "Bouncing 'dvd' Logo"
local width = g13.display_width
local height = g13.display_height
local x = 22.0
local y = 6.0
local x_direction = 1
local y_direction = 1
local speed = 20
local size = 3
function update(delta: number)
local x = x + speed * delta * x_direction
local y = y + speed * delta * y_direction
if x <= size or x >= width - size then
x_direction = -x_direction
end
if y <= size or y >= height - size then
y_direction = -y_direction
end
local color_x = (x / width) * 255
local color_y = (y / height) * 255
local color_z = 255 - (color_x + color_y) / 2
g13.set_color(color_x/2, color_y/2, color_z/2)
g13.clear()
for sy = -size,size do
for sx = -size,size do
g13.set_pixel(x + sx, y + sy, true)
end
end
end