kernel/io/console/
vga_graphics.rs

1use embedded_graphics::{
2    geometry::Point,
3    mono_font::{
4        ascii::{FONT_9X15, FONT_9X15_BOLD},
5        MonoTextStyle,
6    },
7    pixelcolor::{Rgb888, RgbColor},
8    text::{
9        renderer::{CharacterStyle, TextRenderer},
10        Baseline,
11    },
12};
13
14use crate::graphics::{self, vga, Pixel};
15
16use super::{VideoConsole, VideoConsoleAttribute};
17
18pub(super) struct VgaGraphics {
19    pos: Point,
20    text_style: MonoTextStyle<'static, Rgb888>,
21    vga: &'static vga::VgaDisplayController,
22}
23
24impl VgaGraphics {
25    pub fn new() -> Self {
26        Self {
27            pos: Point::new(0, 0),
28            text_style: MonoTextStyle::new(&FONT_9X15, Rgb888::WHITE),
29            vga: graphics::vga::controller().expect("We should have a VGA controller by now!"),
30        }
31    }
32
33    pub fn clear_current_text_line(&mut self, vga: &mut vga::VgaDisplay) {
34        let fb_info = self.vga.framebuffer_info();
35        vga.clear_rect(
36            0,
37            self.pos.y as usize,
38            fb_info.width,
39            self.text_style.line_height() as usize,
40            Pixel { r: 0, g: 0, b: 0 },
41        );
42    }
43
44    fn scroll(&mut self, lines: usize, vga: &mut vga::VgaDisplay) {
45        let fb_info = self.vga.framebuffer_info();
46        let height = fb_info.height;
47        let width = fb_info.width;
48        let mut i = 0;
49        while i < height - lines * 2 {
50            vga.blit_inner_ranges((0, i + lines), (0, i), width, lines);
51
52            i += lines;
53        }
54        self.pos.y -= lines as i32;
55    }
56
57    fn fix_after_advance(&mut self, vga: &mut vga::VgaDisplay) {
58        let fb_info = self.vga.framebuffer_info();
59        let width = fb_info.width as i32;
60        let height = fb_info.height as i32;
61        if self.pos.x + self.text_style.font.character_size.width as i32 >= width {
62            self.pos.x = 0;
63            self.pos.y += self.text_style.line_height() as i32;
64        }
65        if self.pos.y + self.text_style.line_height() as i32 >= height {
66            // scroll up
67            self.scroll(self.text_style.line_height() as usize, vga);
68
69            // clear line
70            self.clear_current_text_line(vga);
71
72            // just to make sure we are not out of bounds by more than 1 line
73            self.fix_after_advance(vga);
74        }
75    }
76}
77
78impl VideoConsole for VgaGraphics {
79    fn init(&mut self) {
80        if let Some(mut vga) = self.vga.lock_kernel() {
81            vga.clear();
82        }
83    }
84
85    fn set_attrib(&mut self, attrib: VideoConsoleAttribute) {
86        // These colors are used in PowerShell 6 in Windows 10
87        // except for black, changed to all zeros
88        let to_color = |color: u8| match color {
89            0 => Rgb888::new(0, 0, 0),
90            1 => Rgb888::new(197, 15, 31),
91            2 => Rgb888::new(19, 161, 14),
92            3 => Rgb888::new(193, 156, 0),
93            4 => Rgb888::new(0, 55, 218),
94            5 => Rgb888::new(136, 23, 152),
95            6 => Rgb888::new(58, 150, 221),
96            7 => Rgb888::new(204, 204, 204),
97
98            8 => Rgb888::new(118, 118, 118),
99            9 => Rgb888::new(231, 72, 86),
100            10 => Rgb888::new(22, 198, 12),
101            11 => Rgb888::new(249, 241, 165),
102            12 => Rgb888::new(59, 120, 255),
103            13 => Rgb888::new(180, 0, 158),
104            14 => Rgb888::new(97, 214, 214),
105            _ => Rgb888::new(242, 242, 242),
106        };
107
108        self.text_style
109            .set_background_color(Some(to_color(attrib.background as u8)));
110        self.text_style
111            .set_text_color(Some(to_color(attrib.foreground as u8)));
112
113        if attrib.bold {
114            self.text_style.font = &FONT_9X15_BOLD;
115        } else {
116            self.text_style.font = &FONT_9X15;
117        }
118    }
119
120    fn write_byte(&mut self, c: u8) {
121        let Some(mut vga) = self.vga.lock_kernel() else {
122            // don't change anything if we can't lock the VGA
123            return;
124        };
125
126        if c == b'\n' {
127            self.pos = Point::new(0, self.pos.y + self.text_style.line_height() as i32);
128        } else if c == b'\r' {
129            self.pos.x = 0;
130        } else {
131            let mut dst = [0; 4];
132            let str = (c as char).encode_utf8(&mut dst);
133
134            let style = self.text_style;
135
136            self.pos = style
137                .draw_string(str, self.pos, Baseline::Bottom, &mut *vga)
138                .unwrap();
139        }
140        self.fix_after_advance(&mut vga);
141    }
142
143    fn backspace(&mut self) {
144        let Some(mut vga) = self.vga.lock_kernel() else {
145            // don't change anything if we can't lock the VGA
146            return;
147        };
148
149        if self.pos.x < self.text_style.font.character_size.width as i32 {
150            if self.pos.y >= self.text_style.line_height() as i32 {
151                self.pos.y -= self.text_style.line_height() as i32;
152                self.pos.x = self.vga.framebuffer_info().width as i32
153                    - self.text_style.font.character_size.width as i32;
154            }
155        } else {
156            self.pos.x -= self.text_style.font.character_size.width as i32;
157        }
158
159        vga.clear_rect(
160            self.pos.x as usize,
161            (self.pos.y as usize).saturating_sub(self.text_style.line_height() as usize),
162            self.text_style.font.character_size.width as usize,
163            self.text_style.line_height() as usize,
164            Pixel { r: 0, g: 0, b: 0 },
165        );
166    }
167}