kobo/display/
chr_display.rs

1use std::{
2    path::PathBuf,
3    process::{Command, ExitStatus},
4};
5
6use regex::Regex;
7
8use crate::coords::{CharSpaceCoord, PixelSpaceCoord};
9
10use super::{color::EinkColor, error::DisplayError};
11
12const FBINK_VAR_PARSE: &str =
13    r"screenWidth=(\d+).*screenHeight=(\d+).*MAXCOLS=(\d+).*MAXROWS=(\d+)";
14
15/// A simple character-only display controller
16///
17/// # Debugging FBInk
18///
19/// You can set `KOBO_RS_DEBUG_FBINK=1` in your environment to enable debug printing for the FBInk subprocess
20pub struct CharacterDisplay {
21    /// Executable path of FBInk
22    fbink_path: PathBuf,
23    /// The size of the display in pixels
24    display_size: PixelSpaceCoord,
25    /// The size of the display in characters
26    display_char_size: CharSpaceCoord,
27}
28
29impl CharacterDisplay {
30    /// Opens a new character display through FBInk
31    pub fn open() -> Result<Self, DisplayError> {
32        // Check for FBInk
33        let fbink_install_locations: Vec<PathBuf> = vec![
34            "/usr/local/kfmon/bin/fbink".parse().unwrap(),
35            "/usr/bin/fbink".parse().unwrap(),
36        ];
37        let fbink_path = fbink_install_locations
38            .iter()
39            .find(|path| path.exists())
40            .ok_or(DisplayError::FBInkNotFound)?;
41
42        // Call FBInk to get the internal variables
43        let output = Command::new(fbink_path).arg("-e").output().unwrap();
44        let o = String::from_utf8(output.stdout).unwrap();
45        let matched = Regex::new(FBINK_VAR_PARSE)
46            .unwrap()
47            .captures(o.trim())
48            .unwrap();
49
50        Ok(Self {
51            fbink_path: fbink_path.to_owned(),
52            display_size: PixelSpaceCoord::new(
53                matched[1].parse().unwrap(),
54                matched[2].parse().unwrap(),
55            ),
56            display_char_size: CharSpaceCoord::new(
57                matched[3].parse().unwrap(),
58                matched[4].parse().unwrap(),
59            ),
60        })
61    }
62
63    /// Execute a command through FBInk
64    fn fbink_exec(&self, args: Vec<String>) -> std::io::Result<ExitStatus> {
65        let mut cmd = Command::new(&self.fbink_path);
66        cmd.args(args);
67
68        // If the `KOBO_RS_DEBUG_FBINK` environment variable is set, pass the command output to stdout otherwise keep it silent
69        if std::env::var("KOBO_RS_DEBUG_FBINK").is_ok() {
70            cmd.stdout(std::process::Stdio::inherit());
71        } else {
72            cmd.stdout(std::process::Stdio::null());
73        }
74
75        Ok(cmd.output()?.status)
76    }
77
78    /// Get the screen size in pixels
79    pub fn get_screen_size_px(&self) -> PixelSpaceCoord {
80        self.display_size
81    }
82
83    /// Get the screen size in characters
84    pub fn get_screen_size_ch(&self) -> CharSpaceCoord {
85        self.display_char_size
86    }
87
88    /// Clear the display
89    pub fn clear_screen(&self) -> std::io::Result<ExitStatus> {
90        self.fbink_exec(vec!["-c".to_string()])
91    }
92
93    /// Write a string to a specific display position
94    pub fn write_str(
95        &self,
96        s: &str,
97        pos: CharSpaceCoord,
98        invert_colors: bool,
99        color: Option<EinkColor>,
100        background_color: Option<EinkColor>,
101    ) -> std::io::Result<ExitStatus> {
102        let mut args = vec![
103            "-y".to_string(),
104            pos.y.to_string(),
105            "-x".to_string(),
106            pos.x.to_string(),
107            "-C".to_string(),
108            color.unwrap_or(EinkColor::Black).to_string(),
109            "-B".to_string(),
110            background_color.unwrap_or(EinkColor::White).to_string(),
111            s.to_string(),
112        ];
113
114        if invert_colors {
115            args.push("--invert".to_string());
116        }
117
118        self.fbink_exec(args)
119    }
120}