linfb/
lib.rs

1//! linfb is a drawing library that uses Linux' `/dev/fb0` device as it's backend. For most
2//! tasks you probably want to use OpenGL or Vulkan backed library. `/dev/fb0` is deprecated but
3//! still useful for some specific cases. This library supports framebuffers that use 32 bits per
4//! pixel, so (theoretically) most modern systems.
5//!
6//! Before drawing on framebuffer you should allocate a virtual terminal and switch to it. I
7//! recommend using [vt](https://crates.io/crates/vt) crates for this task. You should never draw
8//! on virtual terminal used by X.org/Wayland server, this is unsafe and can lead to panics.
9//!
10//! By default linfb includes text and images drawing capabilities, which brings additional
11//! dependencies. You can disable these features if you only need low-level framebuffer
12//! interactions and [`Shape`] trait.
13//!
14//! Basic usage can look like this:
15//! ```ignore
16//! use linfb::Framebuffer;
17//! use linfb::shape::{Color, Shape, Rectangle, Caption, Image, FontBuilder, Alignment};
18//! let mut framebuffer = Framebuffer::open()
19//!     .expect("Failed to open framebuffer");
20//! let mut compositor = framebuffer.compositor((255, 255, 255).into());
21//! compositor
22//!     .add("rect1", Rectangle::builder()
23//!         .width(100)
24//!         .height(100)
25//!         .fill_color(Color::hex("#ff000099").unwrap())
26//!         .build()
27//!         .unwrap()
28//!         .at(100, 100))
29//!     .add("rect2", Rectangle::builder()
30//!         .width(100)
31//!         .height(100)
32//!         .fill_color(Color::hex("#00ff0099").unwrap())
33//!         .build()
34//!         .unwrap()
35//!         .at(150, 150))
36//!     .add("image", Image::from_path("image.png")
37//!         .unwrap()
38//!         .at(500, 500))
39//!     .add("wrapped_text", Caption::builder()
40//!         .text("Some centered text\nwith newlines".into())
41//!         .size(56)
42//!         .color(Color::hex("#4066b877").unwrap())
43//!         .font(FontBuilder::default()
44//!               .family("monospace")
45//!               .build()
46//!               .unwrap()
47//!         )
48//!         .alignment(Alignment::Center)
49//!         .max_width(650)
50//!         .build()
51//!         .unwrap()
52//!         .at(1000, 300));
53//! // Compositor is shape, so we can just draw it at the top left angle
54//! framebuffer.draw(0, 0, &compositor);
55//! // Really changing screen contents
56//! framebuffer.flush();
57//! ```
58
59use std::fs::OpenOptions;
60use std::io;
61use std::os::unix::io::AsRawFd;
62
63use memmap::{MmapMut, MmapOptions};
64
65pub mod sys;
66use sys::fb_var_screeninfo;
67use sys::get_var_screeninfo;
68
69mod error;
70pub use error::{Error, Result};
71
72pub mod shape;
73use shape::{Shape, Color};
74
75mod compositor;
76pub use compositor::{Compositor, CompositorBuilder};
77
78#[cfg(feature = "text")]
79mod text;
80
81#[cfg(feature = "images")]
82mod image;
83
84/// Basic object used to manipulate framebuffer.
85/// You should normally use [Shape] and [Compositor] to draw on it
86pub struct Framebuffer {
87    screen: Vec<u8>,
88    /// Information about framebuffer
89    pub screen_info: fb_var_screeninfo,
90    framebuffer: MmapMut,
91}
92
93impl Framebuffer {
94    /// Try to open `/dev/fb0` and create Framebuffer object.
95    /// It requires root privileges on most systems.
96    /// This method will panic if `/dev/fb0` is not a framebuffer or it's pixel size is not 32 bits
97    pub fn open() -> io::Result<Self> {
98        let file = OpenOptions::new()
99            .read(true)
100            .write(true)
101            .create(false)
102            .open("/dev/fb0")?;
103        let mut screen_info: fb_var_screeninfo = Default::default();
104        unsafe {
105            get_var_screeninfo(file.as_raw_fd(), &mut screen_info)
106                .expect("Failed to get var_screeninfo")
107        };
108
109        if screen_info.bits_per_pixel != 32 {
110            panic!("Size of one pixel must be 32 bits for linfb to work");
111        }
112
113        let framebuffer = unsafe {
114            MmapOptions::new()
115                .len(screen_info.overall_size())
116                .map_mut(&file)?
117        };
118        let screen = vec![0u8; framebuffer.len()];
119
120        Ok(Self {
121            screen,
122            framebuffer,
123            screen_info,
124        })
125    }
126
127    /// Flush internal buffer contents to the real framebuffer device
128    pub fn flush(&mut self) {
129        self.framebuffer.copy_from_slice(self.screen.as_slice());
130    }
131
132    /// Set pixel at x, y to color.
133    /// Alpha value of color is probably will be ignored, as it doesn't makes sense in this context
134    pub fn set_pixel<C: Into<Color>>(&mut self, x: u32, y: u32, color: C) {
135        let color: Color = color.into();
136        let pixel_pos = ((y * self.screen_info.xres + x) * 4) as usize;
137
138        let mut pixel = 0u32;
139        pixel |= (color.red as u32) >> (8 - self.screen_info.red.length) << self.screen_info.red.offset;
140        pixel |= (color.green as u32) >> (8 - self.screen_info.green.length) << self.screen_info.green.offset;
141        pixel |= (color.blue as u32) >> (8 - self.screen_info.blue.length) << self.screen_info.blue.offset;
142        pixel |= (color.alpha as u32) >> (8 - self.screen_info.transp.length) << self.screen_info.transp.offset;
143        self.screen[pixel_pos..pixel_pos + 4].copy_from_slice(&pixel.to_ne_bytes());
144    }
145
146    /// Draw shape on internal buffer
147    pub fn draw<T: Shape>(&mut self, x: u32, y: u32, shape: &T) {
148        for (inner_y, row) in shape.render().iter().enumerate() {
149            for (inner_x, color) in row.iter().enumerate() {
150                if let Some(color) = color {
151                    self.set_pixel(x + (inner_x as u32), y + (inner_y as u32), *color);
152                }
153            }
154        }
155    }
156
157    /// Create [Compositor] object with size of a screen and given background color
158    pub fn compositor(&self, background: Color) -> Compositor {
159        Compositor::new(self.screen_info.xres as usize, self.screen_info.yres as usize, background)
160    }
161}