Created
July 18, 2025 07:30
-
-
Save jmahmood/f342e87a8df408d7bb82997e54b9fd5e to your computer and use it in GitHub Desktop.
Rocknix: Ambernic RG35XX Plus Input w/ Rust
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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