kdash/kdash_client/src/fb/mod.rs
2024-11-22 12:38:00 +01:00

201 lines
6.3 KiB
Rust

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<image::Luma<u8>, Vec<u8>>,
) -> 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<i32>,
range_y: Range<i32>,
fb_idx_fn: FbIdxFn,
}
impl FramebufferOrientation {
pub fn new(fb: &framebuffer::Framebuffer, orientation: Orientation) -> Result<Self> {
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<u8>,
}
impl FramebufferDisplay {
pub fn new(fb_path: &str, orientation: Orientation) -> Result<Self> {
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<I>(&mut self, pixels: I) -> std::result::Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
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!()
}
}