//! Interface to the Linux Framebuffer API
//!
//! This crate provides high-level access to a linux framebuffer device (`/dev/fb*`).
//!
//! Check out the [`Framebuffer`] documentation for a simple example.
//!
//! Once you are familiar with the basic interface, check out the [`double::Buffer`]
//! documentation, for some more examples.
extern crate libc;
extern crate memmap;
mod proc;
mod fbio;
pub mod double;
use std::fs::{OpenOptions, File};
use memmap::{MmapMut, MmapOptions};
use std::path::{Path, PathBuf};
pub use self::fbio::{PixelLayout, PixelLayoutChannel, BlankingLevel, ErrnoError, TerminalMode, set_terminal_mode};
#[derive(Debug)]
pub enum ErrorKind {
Io,
Fb,
}
/// Errors returned by `Framebuffer` methods
#[derive(Debug)]
pub struct Error {
pub kind: ErrorKind,
/// Available when `kind == ErrorKind::Io`.
pub io: Option<std::io::Error>,
/// Available when `kind == ErrorKind::Fb`.
pub fb: Option<fbio::ErrnoError>,
}
impl From<std::io::Error> for Error {
fn from(io: std::io::Error) -> Error {
Error { kind: ErrorKind::Io, io: Some(io), fb: None }
}
}
impl From<fbio::ErrnoError> for Error {
fn from(fb: fbio::ErrnoError) -> Error {
Error { kind: ErrorKind::Fb, io: None, fb: Some(fb) }
}
}
/// Represents a single framebuffer device
///
/// Example usage:
///
/// ```no_run
/// // Instead of hardcoding the path, you could also use `Framebuffer::list()`
/// // to find paths to available devices.
/// let fb = linuxfb::Framebuffer::new("/dev/fb0").unwrap();
///
/// println!("Size in pixels: {:?}", fb.get_size());
///
/// println!("Bytes per pixel: {:?}", fb.get_bytes_per_pixel());
///
/// println!("Physical size in mm: {:?}", fb.get_physical_size());
///
/// // Map the framebuffer into memory, so we can write to it:
/// let mut data = fb.map().unwrap();
///
/// // Make everything black:
/// for i in 0..data.len() {
/// data[i] = 0;
/// }
///
/// // Make everything white:
/// for i in 0..data.len() {
/// data[i] = 0xFF;
/// }
/// ```
pub struct Framebuffer {
file: File,
finfo: fbio::FixScreeninfo,
vinfo: fbio::VarScreeninfo,
}
impl Framebuffer {
/// Returns a list of paths to device nodes, which are handled by the "fb" driver.
///
/// Relies on `/proc/devices` to discover the major number of the device,
/// and on the device nodes to exist in `/dev`.
///
/// Example, assuming there is one framebuffer named `fb0`:
///
/// let devices = linuxfb::Framebuffer::list().unwrap();
/// println!("Devices: {:?}", devices);
/// // prints:
/// // Devices: ["/dev/fb0"]
///
pub fn list() -> std::io::Result<Vec<PathBuf>> {
match proc::devices()?.find(|device| device.driver == "fb") {
None => Ok(vec![]),
Some(device) =>
Ok(std::fs::read_dir("/dev")?.flat_map(|result| {
match result {
Err(_) => None,
Ok(entry) => {
let mut statbuf: libc::stat = unsafe { std::mem::zeroed() };
let path = entry.path();
let cpath = std::ffi::CString::new(path.to_str().unwrap()).unwrap();
match unsafe { libc::stat(cpath.as_ptr(), &mut statbuf) } {
-1 => {
None
},
_ => {
let major = unsafe { libc::major(statbuf.st_rdev) } as u32;
if major == device.major {
Some(path)
} else {
None
}
}
}
}
}
}).collect())
}
}
/// Attempts to open the framebuffer device at the given `path`, and query it's properties.
///
/// This operation can fail for one of these reasons:
/// * The device cannot be opened. In this case the error has `ErrorKind::Io`, and `error.io` is set.
/// This can happen for example when given a `path` to a file that does not exist, or when the user
/// lacks permission to open the device.
/// * Any of the `ioctl` calls querying device properties fails. In this case the error has `ErrorKind::Fb`,
/// and `error.fb` is set. Use `error.fb.errno` and `error.fb.message` to find out what failed.
/// This can happen for example when the given `path` does not refer to a framebuffer device, or when
/// the driver encounters an error.
pub fn new(path: impl AsRef<Path>) -> Result<Framebuffer, Error> {
let file = OpenOptions::new().read(true).write(true).open(path)?;
let finfo = fbio::get_fscreeninfo(&file)?;
let vinfo = fbio::get_vscreeninfo(&file)?;
Ok(Framebuffer { file, finfo, vinfo })
}
/// Maps the framebuffer device into memory.
///
/// Returns a memory mapped region, which can be used to modify screen contents.
///
/// The size of the region is chosen based on the current virtual size of the display,
/// and the bytes per pixel. Therefore this method should be called *after* configuring
/// the device.
///
/// Since the returned `memmap::MmapMut` object implements the `Drop` trait, the region is
/// automatically unmapped, when the returned map goes out of scope.
///
/// Note that changes to the data directly appear on screen, so you will most likely
/// see flicker, if you write to a visible region.
///
/// To avoid this, you can set the virtual size to be twice as large as the actual size
/// of the display, then only draw to the part of that region that's currently not shown.
/// Once done drawing, use `set_offset` to make the drawn region appear on screen.
///
/// See the [`double`] module for a convenient wrapper that does exactly that.
pub fn map(&self) -> Result<MmapMut, Error> {
let (width, height) = self.get_virtual_size();
let size = width * height * self.get_bytes_per_pixel();
let mmap = unsafe { MmapOptions::new().len(size as usize).map_mut(&self.file) }?;
Ok(mmap)
}
/// Returns the number of bytes used to represent one pixel.
///
/// This can be used to narrow down the format.
pub fn get_bytes_per_pixel(&self) -> u32 {
self.vinfo.bytes_per_pixel()
}
/// Sets the number of bytes per pixel.
///
/// This modifies the `bits_per_pixel` attribute of the underlying
/// device.
/// The actual consequence of this action depends on the driver.
///
/// For at least some drivers, setting a different number of pixels
/// changes the color mode.
///
/// Make sure to use [`get_bytes_per_pixel`](Framebuffer::get_bytes_per_pixel) afterwards to check if
/// the value was changed.
///
/// Also use [`get_pixel_layout`](Framebuffer::get_pixel_layout), to find out more about the format
/// being used.
///
/// This operation fails, when any of the underlying `ioctl` calls fail.
/// After a failure, the device may be in an undefined state.
pub fn set_bytes_per_pixel(&mut self, value: u32) -> Result<(), Error> {
let mut vinfo = self.vinfo.clone();
vinfo.set_bytes_per_pixel(value);
vinfo.activate_now();
fbio::put_vscreeninfo(&self.file, &mut vinfo)?;
self.vinfo = fbio::get_vscreeninfo(&self.file)?;
Ok(())
}
/// Returns the pixel layout, as reported by the driver.
///
/// This value may change, after calling [`set_bytes_per_pixel`](Framebuffer::set_bytes_per_pixel).
///
/// Some examples:
///
/// **16-bit, RGB565**, meaning `rrrrrggggggrrrrr`, with LSB right, aka HighColor:
/// ```
/// # use linuxfb::*;
/// PixelLayout {
/// red: PixelLayoutChannel { offset: 11, length: 5, msb_right: false },
/// green: PixelLayoutChannel { offset: 5, length: 6, msb_right: false },
/// blue: PixelLayoutChannel { offset: 0, length: 5, msb_right: false },
/// alpha: PixelLayoutChannel { offset: 0, length: 0, msb_right: false },
/// };
/// ```
///
/// **32-bit, ABGR**, meaning `aaaaaaaabbbbbbbbbggggggggrrrrrrrr`, with LSB right:
/// ```
/// # use linuxfb::*;
/// PixelLayout {
/// red: PixelLayoutChannel { offset: 0, length: 8, msb_right: false },
/// green: PixelLayoutChannel { offset: 8, length: 8, msb_right: false },
/// blue: PixelLayoutChannel { offset: 16, length: 8, msb_right: false },
/// alpha: PixelLayoutChannel { offset: 24, length: 8, msb_right: false },
/// };
/// ```
///
/// **32-bit, RGBA**, meaning: `rrrrrrrrggggggggbbbbbbbbaaaaaaaa`, with LSB right:
/// ```
/// # use linuxfb::*;
/// PixelLayout {
/// red: PixelLayoutChannel { offset: 24, length: 8, msb_right: false },
/// green: PixelLayoutChannel { offset: 16, length: 8, msb_right: false },
/// blue: PixelLayoutChannel { offset: 8, length: 8, msb_right: false },
/// alpha: PixelLayoutChannel { offset: 0, length: 8, msb_right: false },
/// };
/// ```
///
/// Note that on most devices, setting alpha data does not have any effect, even
/// when an alpha channel is specified in the layout.
pub fn get_pixel_layout(&self) -> fbio::PixelLayout {
self.vinfo.pixel_layout()
}
/// Returns the size of the display, in pixels.
pub fn get_size(&self) -> (u32, u32) {
self.vinfo.size_in_pixels()
}
/// Returns the virtual size of the display, in pixels.
///
/// See `set_virtual_size` for details.
pub fn get_virtual_size(&self) -> (u32, u32) {
self.vinfo.virtual_size()
}
/// Sets the virtual size of the display.
///
/// The virtual size defines the area where pixel data can be written to.
/// It should always be equal to or larger than the values returned from
/// [`get_size`](Framebuffer::get_size).
///
/// After setting the virtual size, you can use [`set_offset`](Framebuffer::set_offset)
/// to control what region of the "virtual display" is actually shown on screen.
///
/// This operation fails, when any of the underlying `ioctl` calls fail.
/// After a failure, the device may be in an undefined state.
pub fn set_virtual_size(&mut self, w: u32, h: u32) -> Result<(), Error> {
let mut vinfo = self.vinfo.clone();
vinfo.set_virtual_size(w, h);
vinfo.activate_now();
fbio::put_vscreeninfo(&self.file, &mut vinfo)?;
self.vinfo = fbio::get_vscreeninfo(&self.file)?;
Ok(())
}
/// Returns the current `xoffset` and `yoffset` of the underlying device.
pub fn get_offset(&self) -> (u32, u32) {
self.vinfo.offset()
}
/// Sets the `xoffset` and `yoffset` of the underlying device.
///
/// This can be used to pan the display.
///
/// This operation fails, when any of the underlying `ioctl` calls fail.
/// After a failure, the device may be in an undefined state.
pub fn set_offset(&mut self, x: u32, y: u32) -> Result<(), Error> {
let mut vinfo = self.vinfo.clone();
vinfo.set_offset(x, y);
vinfo.activate_now();
fbio::put_vscreeninfo(&self.file, &mut vinfo)?;
self.vinfo = fbio::get_vscreeninfo(&self.file)?;
Ok(())
}
/// Returns the physical size of the device
/// in millimeters, as reported by the driver.
pub fn get_physical_size(&self) -> (u32, u32) {
self.vinfo.size_in_mm()
}
/// Get identifier string of the device, as reported by the driver.
pub fn get_id(&self) -> String {
self.finfo.id()
}
/// Sets the blanking level. This can be used to turn off the screen.
///
/// See [`BlankingLevel`] for a list of available options, and their
/// meaning.
///
/// Brief example:
/// ```no_run
/// use linuxfb::{Framebuffer, BlankingLevel};
///
/// let mut fb = Framebuffer::new("/dev/fb0").unwrap();
///
/// // Turn off the screen:
/// fb.blank(BlankingLevel::Powerdown).unwrap();
///
/// // Turn the screen back on:
/// fb.blank(BlankingLevel::Unblank).unwrap();
/// ```
///
/// This operation fails, when the underlying `ioctl` call fails.
///
/// At least some drivers produce an error, when the new blanking
/// mode does not actually change the state of the device.
///
/// For example:
/// ```no_run
/// # use linuxfb::{Framebuffer, BlankingLevel};
/// # let mut fb = Framebuffer::new("/dev/fb0").unwrap();
/// fb.blank(BlankingLevel::Powerdown).unwrap(); // this call goes through fine
/// fb.blank(BlankingLevel::Powerdown).unwrap(); // this call fails with EBUSY
/// ```
///
/// Since there is no way to determine beforehand what the current
/// state of the screen is, you should always expect that these calls
/// may fail, and continue normally (possibly with a warning).
pub fn blank(&self, level: BlankingLevel) -> Result<(), Error> {
fbio::blank(&self.file, level)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
println!("Framebuffer devices: {:?}", crate::Framebuffer::list());
}
}