Skip to content

Instantly share code, notes, and snippets.

@jmahmood
Created July 18, 2025 07:30
Show Gist options
  • Select an option

  • Save jmahmood/f342e87a8df408d7bb82997e54b9fd5e to your computer and use it in GitHub Desktop.

Select an option

Save jmahmood/f342e87a8df408d7bb82997e54b9fd5e to your computer and use it in GitHub Desktop.
Rocknix: Ambernic RG35XX Plus Input w/ Rust
// I've been trying to figure out how to get RUST to build something - anything - for
// small handheld devices. I've managed to do so when using lib sdl2, but that is a poisoned chalice
// with lots of annoyances.
// Under Rocknix, this executes successfully, as long as you shut off weston.service first
// IE: $ systemctl stop weston.service
use anyhow::{Context, Result};
use evdev::{Device as EvdevDevice, EventType, KeyCode};
use framebuffer::Framebuffer;
use embedded_graphics::{
draw_target::DrawTarget,
mono_font::{ascii::FONT_8X13, MonoTextStyle},
pixelcolor::Rgb888,
prelude::*,
primitives::{PrimitiveStyle, Rectangle},
text::Text,
};
use std::{env, thread::sleep, time::Duration};
fn find_gamepad() -> Result<EvdevDevice> {
let mut fallback: Option<EvdevDevice> = None;
for i in 0..32 {
let path = format!("/dev/input/event{}", i);
if let Ok(dev) = EvdevDevice::open(&path) {
let name = dev.name().unwrap_or_default().to_lowercase();
println!("Checking {}: {}", path, name); // Print all for debug
// This is for the RG35XX Plus
if name.contains("h700 gamepad") {
println!("Success! Opening correct gamepad device: {}", path);
return Ok(dev);
}
// Broaden match: "joy", "game", "key", "btn"
// if (name.contains("joy") || name.contains("game") || name.contains("key") || name.contains("btn"))
// && dev.supported_events().contains(EventType::KEY)
// {
// println!("Opening input: {}", path);
// return Ok(dev);
// }
// Fallback: any device supporting keys
if dev.supported_events().contains(EventType::KEY) && fallback.is_none() {
fallback = Some(dev);
}
}
}
if let Some(dev) = fallback {
println!("Falling back to first device with KEY support.");
Ok(dev)
} else {
anyhow::bail!("No suitable /dev/input/eventX found");
}
}
struct RawDisplay<'a> {
buf: &'a mut [u8],
width: u32,
height: u32,
stride: usize,
}
impl<'a> DrawTarget for RawDisplay<'a> {
type Color = Rgb888;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
for Pixel(pt, color) in pixels {
if let (Ok(x), Ok(y)) = (usize::try_from(pt.x), usize::try_from(pt.y)) {
if x < self.width as usize && y < self.height as usize {
let off = y * self.stride + x * 4;
if off + 3 < self.buf.len() {
let r = color.r();
let g = color.g();
let b = color.b();
self.buf[off + 0] = b;
self.buf[off + 1] = g;
self.buf[off + 2] = r;
self.buf[off + 3] = 0;
}
}
}
}
Ok(())
}
}
impl<'a> OriginDimensions for RawDisplay<'a> {
fn size(&self) -> Size {
Size::new(self.width, self.height)
}
}
fn draw_frame<T>(disp: &mut T, text: &str) -> Result<()>
where
T: DrawTarget<Color = Rgb888, Error = core::convert::Infallible> + OriginDimensions,
{
Rectangle::new(Point::zero(), disp.size())
.into_styled(PrimitiveStyle::with_fill(Rgb888::new(0, 0, 48)))
.draw(disp)?;
let style = MonoTextStyle::new(&FONT_8X13, Rgb888::WHITE);
Text::new(text, Point::new(20, 30), style).draw(disp)?;
Ok(())
}
fn main() -> Result<()> {
let fb_path = env::args().nth(1).unwrap_or_else(|| "/dev/fb0".into());
let mut fb = Framebuffer::new(&fb_path)
.with_context(|| format!("Failed to open framebuffer at {}", fb_path))?;
let var = fb.var_screen_info; // Field, not method
let fix = fb.fix_screen_info; // Field, not method
let width = var.xres as u32;
let height = var.yres as u32;
let stride = fix.line_length as usize;
println!("Framebuffer: {}×{}, stride={} bytes", width, height, stride);
let mut gamepad = find_gamepad()?;
let mut last_event = "Press a button…".to_string();
loop {
let buffer = &mut fb.frame; // FIELD, not method, returns &mut [u8]
{
let mut disp = RawDisplay {
buf: buffer,
width,
height,
stride,
};
draw_frame(&mut disp, &last_event)?;
}
for ev in gamepad.fetch_events()? {
println!("{:?}", ev);
if ev.event_type() == EventType::KEY {
// Use KeyCode, not Key, and check with from_code
if let Some(code) = KeyCode::new(ev.code()).into() {
last_event = format!("{:?} => {}", code, ev.value());
println!("{:?}", last_event);
if code == KeyCode::KEY_ESC && ev.value() == 1 {
return Ok(());
}
}
}
}
sleep(Duration::from_millis(16));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment