use core::ffi::c_int;
use std::ffi::CString;
use std::io;
use libc::{O_RDWR, close, ioctl, open};
use super::ioctl::*;
use crate::draw::texture::ColorFormat;
pub(super) struct FbDevice {
fd: c_int,
pub fbmem: *mut u8,
pub fblen: usize,
pub xres: u16,
pub yres: u16,
pub stride: usize,
pub format: ColorFormat,
pub supports_update: bool,
pub supports_vsync: bool,
pub use_paninfo: bool,
pan_template: FbPlaneInfo,
}
unsafe impl Send for FbDevice {}
impl FbDevice {
pub(super) fn open(path: &str, use_paninfo: Option<bool>) -> io::Result<Self> {
let cpath = CString::new(path)
.map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "fb_path contains NUL"))?;
let fd = unsafe { open(cpath.as_ptr(), O_RDWR) };
if fd < 0 {
return Err(io::Error::last_os_error());
}
let mut vi = FbVideoInfo {
fmt: 0,
xres: 0,
yres: 0,
nplanes: 0,
};
let r = unsafe { ioctl(fd, FBIOGET_VIDEOINFO as _, &mut vi as *mut _) };
if r < 0 {
let err = io::Error::last_os_error();
unsafe { close(fd) };
return Err(err);
}
let mut pi = FbPlaneInfo {
fbmem: core::ptr::null_mut(),
fblen: 0,
stride: 0,
display: 0,
bpp: 0,
xres_virtual: 0,
yres_virtual: 0,
xoffset: 0,
yoffset: 0,
};
let r = unsafe { ioctl(fd, FBIOGET_PLANEINFO as _, &mut pi as *mut _) };
if r < 0 {
let err = io::Error::last_os_error();
unsafe { close(fd) };
return Err(err);
}
if pi.fbmem.is_null() || pi.fblen == 0 {
unsafe { close(fd) };
return Err(io::Error::other("FBIOGET_PLANEINFO returned null fbmem"));
}
let format = format_from_videoinfo(&vi).ok_or_else(|| {
unsafe { close(fd) };
io::Error::new(
io::ErrorKind::Unsupported,
alloc::format!("unsupported FB_FMT_{}", vi.fmt),
)
})?;
let supports_update = probe_update(fd);
let supports_vsync = probe_vsync(fd);
let use_paninfo = use_paninfo.unwrap_or(format.bytes_per_pixel() >= 4);
Ok(Self {
fd,
fbmem: pi.fbmem as *mut u8,
fblen: pi.fblen,
xres: vi.xres,
yres: vi.yres,
stride: pi.stride as usize,
format,
supports_update,
supports_vsync,
use_paninfo,
pan_template: pi,
})
}
pub(super) fn flush(&mut self, x: u16, y: u16, w: u16, h: u16) {
if self.supports_update {
let area = FbArea { x, y, w, h };
unsafe {
ioctl(self.fd, FBIO_UPDATE as _, &area as *const _);
}
}
}
pub(super) fn pan(&mut self) {
if !self.use_paninfo {
return;
}
self.pan_template.yoffset = 0;
unsafe {
ioctl(
self.fd,
FBIOPAN_DISPLAY as _,
&self.pan_template as *const _,
);
}
}
pub(super) fn wait_vsync(&self) {
if !self.supports_vsync {
return;
}
unsafe {
ioctl(self.fd, FBIO_WAITFORVSYNC as _);
}
}
}
impl Drop for FbDevice {
fn drop(&mut self) {
unsafe { close(self.fd) };
}
}
fn format_from_videoinfo(vi: &FbVideoInfo) -> Option<ColorFormat> {
match vi.fmt {
FB_FMT_RGB16_565 => Some(ColorFormat::RGB565Swapped),
FB_FMT_RGB24 => Some(ColorFormat::RGB888),
FB_FMT_RGB32 => Some(ColorFormat::BGRA8888),
FB_FMT_RGBA32 => Some(ColorFormat::RGBA8888),
_ => None,
}
}
fn probe_update(fd: c_int) -> bool {
let area = FbArea {
x: 0,
y: 0,
w: 1,
h: 1,
};
let r = unsafe { ioctl(fd, FBIO_UPDATE as _, &area as *const _) };
r >= 0
}
fn probe_vsync(fd: c_int) -> bool {
let r = unsafe { ioctl(fd, FBIO_WAITFORVSYNC as _) };
r >= 0
}