1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358
//! 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());
}
}