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}