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());
    }
}