use anyhow::Result; use embedded_graphics::{ image::{ImageRaw, ImageRawBE}, pixelcolor::Gray8, prelude::*, primitives::Rectangle, }; use embedded_layout::View; use std::{fs, io::Write, ops::Range}; use kdash_protocol::Orientation; use crate::utils; pub mod widgets; pub fn eips_clear() -> Result<()> { utils::exec_command_discard("eips", &["-c"]) } pub fn image_buf_to_raw( buf: &image::ImageBuffer, Vec>, ) -> ImageRawBE<'_, Gray8> { ImageRaw::new(buf.as_raw(), buf.dimensions().0) } pub const DEFAULT_FB: &str = "/dev/fb0"; const EINK_FB_UPDATE_DISPLAY: &str = "/proc/eink_fb/update_display"; type FbIdxFn = fn(width: usize, height: usize, x: usize, y: usize) -> usize; const fn fb_idx_portrait_up(width: usize, _height: usize, x: usize, y: usize) -> usize { (y * width) + x } const fn fb_idx_portrait_down(width: usize, height: usize, x: usize, y: usize) -> usize { ((height - 1 - y) * width) + (width - 1 - x) } const fn fb_idx_landscape_left(width: usize, height: usize, x: usize, y: usize) -> usize { ((height - 1 - x) * width) + y } const fn fb_idx_landscape_right(width: usize, _height: usize, x: usize, y: usize) -> usize { ((x + 1) * width) - 1 - y } const FB_IDX_PORTRAIT_UP_FN: FbIdxFn = fb_idx_portrait_up; const FB_IDX_PORTRAIT_DOWN_FN: FbIdxFn = fb_idx_portrait_down; const FB_IDX_LANDSCAPE_LEFT_FN: FbIdxFn = fb_idx_landscape_left; const FB_IDX_LANDSCAPE_RIGHT_FN: FbIdxFn = fb_idx_landscape_right; pub struct FramebufferOrientation { pub orientation: Orientation, pub virtual_x: u32, pub virtual_y: u32, range_x: Range, range_y: Range, fb_idx_fn: FbIdxFn, } impl FramebufferOrientation { pub fn new(fb: &framebuffer::Framebuffer, orientation: Orientation) -> Result { Ok(match orientation { Orientation::PortraitUp => Self { orientation, virtual_x: fb.var_screen_info.xres_virtual, virtual_y: fb.var_screen_info.yres_virtual, range_x: 0..i32::try_from(fb.var_screen_info.xres_virtual)?, range_y: 0..i32::try_from(fb.var_screen_info.yres_virtual)?, fb_idx_fn: FB_IDX_PORTRAIT_UP_FN, }, Orientation::PortraitDown => Self { orientation, virtual_x: fb.var_screen_info.xres_virtual, virtual_y: fb.var_screen_info.yres_virtual, range_x: 0..i32::try_from(fb.var_screen_info.xres_virtual)?, range_y: 0..i32::try_from(fb.var_screen_info.yres_virtual)?, fb_idx_fn: FB_IDX_PORTRAIT_DOWN_FN, }, Orientation::LandscapeLeft => Self { orientation, virtual_x: fb.var_screen_info.yres_virtual, virtual_y: fb.var_screen_info.xres_virtual, range_x: 0..i32::try_from(fb.var_screen_info.yres_virtual)?, range_y: 0..i32::try_from(fb.var_screen_info.xres_virtual)?, fb_idx_fn: FB_IDX_LANDSCAPE_LEFT_FN, }, Orientation::LandscapeRight => Self { orientation, virtual_x: fb.var_screen_info.yres_virtual, virtual_y: fb.var_screen_info.xres_virtual, range_x: 0..i32::try_from(fb.var_screen_info.yres_virtual)?, range_y: 0..i32::try_from(fb.var_screen_info.xres_virtual)?, fb_idx_fn: FB_IDX_LANDSCAPE_RIGHT_FN, }, }) } } pub struct FramebufferDisplay { pub fb_orientation: FramebufferOrientation, pub fb: framebuffer::Framebuffer, update_display_file: fs::File, buf: Vec, } impl FramebufferDisplay { pub fn new(fb_path: &str, orientation: Orientation) -> Result { let fb = framebuffer::Framebuffer::new(fb_path)?; let update_display_file = fs::OpenOptions::new() .write(true) .open(EINK_FB_UPDATE_DISPLAY)?; let fb_orientation = FramebufferOrientation::new(&fb, orientation)?; Ok(Self { buf: vec![ 0u8; (fb.var_screen_info.xres_virtual as usize) * (fb.var_screen_info.yres_virtual as usize) ], fb, fb_orientation, update_display_file, }) } pub fn set_orientation(&mut self, orientation: Orientation) -> Result<()> { self.fb_orientation = FramebufferOrientation::new(&self.fb, orientation)?; Ok(()) } fn luma_to_epd_color(color: Gray8) -> u8 { 0xff - color.luma() } pub fn clear(&mut self) { self.buf.fill(0); } pub fn flush(&mut self, update_display_buf: &[u8]) -> Result<()> { self.fb.write_frame(&self.buf); self.update_display_file.write_all(update_display_buf)?; Ok(()) } pub fn flush_partial_update(&mut self) -> Result<()> { self.flush(b"1") } pub fn flush_full_update(&mut self) -> Result<()> { self.flush(b"2") } } impl DrawTarget for FramebufferDisplay { type Color = Gray8; type Error = core::convert::Infallible; fn draw_iter(&mut self, pixels: I) -> std::result::Result<(), Self::Error> where I: IntoIterator>, { for Pixel(coord, color) in pixels.into_iter() { if self.fb_orientation.range_x.contains(&coord.x) && self.fb_orientation.range_y.contains(&coord.y) { let idx = (self.fb_orientation.fb_idx_fn)( self.fb.var_screen_info.xres_virtual as usize, self.fb.var_screen_info.yres_virtual as usize, coord.x as usize, coord.y as usize, ); self.buf[idx] = Self::luma_to_epd_color(color); } } Ok(()) } fn clear(&mut self, color: Self::Color) -> std::result::Result<(), Self::Error> { self.buf.fill(Self::luma_to_epd_color(color)); Ok(()) } } impl OriginDimensions for FramebufferDisplay { fn size(&self) -> Size { Size::new(self.fb_orientation.virtual_x, self.fb_orientation.virtual_y) } } impl View for FramebufferDisplay { fn bounds(&self) -> Rectangle { Rectangle::new(Point::zero(), OriginDimensions::size(self)) } fn translate_impl(&mut self, _by: Point) { unimplemented!() } }