diff --git a/Cargo.lock b/Cargo.lock index 117632ba3e..4a5c67485a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,18 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if 1.0.4", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "ahcid" version = "0.1.0" @@ -425,6 +437,7 @@ dependencies = [ "drm", "graphics-ipc", "orbclient", + "psf-rs", "ransid", ] @@ -751,11 +764,14 @@ dependencies = [ "libredox", "log", "orbclient", + "psf-rs", "ransid", "redox-scheme", "redox_event", "redox_syscall 0.7.5", "scheme-utils", + "serde", + "toml", ] [[package]] @@ -1614,6 +1630,17 @@ dependencies = [ "redox_syscall 0.7.5", ] +[[package]] +name = "psf-rs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065311c47c07d1bd124117c2739388a5fd5788f8997fab6065e93e77874b7123" +dependencies = [ + "ahash", + "hash32", + "heapless", +] + [[package]] name = "ptyd" version = "0.1.0" @@ -2498,6 +2525,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "vesad" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index c41de7b05d..396d7da9c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ libc = "0.2.181" log = "0.4" libredox = "0.1.16" orbclient = "0.3.51" +psf-rs = "2.2.0" parking_lot = "0.12" pico-args = "0.5" plain = "0.2.3" diff --git a/drivers/graphics/console-draw/Cargo.toml b/drivers/graphics/console-draw/Cargo.toml index c92a7b9f42..2c9f625e44 100644 --- a/drivers/graphics/console-draw/Cargo.toml +++ b/drivers/graphics/console-draw/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" drm.workspace = true orbclient.workspace = true ransid.workspace = true +psf-rs.workspace = true graphics-ipc = { path = "../graphics-ipc" } diff --git a/drivers/graphics/console-draw/src/lib.rs b/drivers/graphics/console-draw/src/lib.rs index 9815e399ad..e06b2adfd4 100644 --- a/drivers/graphics/console-draw/src/lib.rs +++ b/drivers/graphics/console-draw/src/lib.rs @@ -1,5 +1,3 @@ -extern crate ransid; - use std::collections::VecDeque; use std::convert::TryFrom; use std::{cmp, io, mem, ptr}; @@ -7,6 +5,7 @@ use std::{cmp, io, mem, ptr}; use drm::buffer::{Buffer, DrmFourcc}; use drm::control::{connector, crtc, framebuffer, ClipRect, Device, Mode}; use graphics_ipc::{CpuBackedBuffer, V2GraphicsHandle}; + use orbclient::FONT; #[derive(Debug, Copy, Clone)] @@ -133,15 +132,68 @@ struct DisplayMap { height: usize, } +#[derive(Clone)] +pub struct ConsoleFont { + glyphs: Vec, + width: usize, + height: usize, +} + +impl ConsoleFont { + pub fn new(glyphs: Vec, width: usize, height: usize) -> ConsoleFont { + ConsoleFont { + glyphs, + width, + height, + } + } + + pub fn from_psf(data: &[u8]) -> ConsoleFont { + let font = psf_rs::Font::load(data); + + let width = font.header.glyph_width as usize; + let height = font.header.glyph_height as usize; + let bytes_per_row = (width + 7) / 8; + let glyph_count = font.header.length as usize; + + let mut glyphs = vec![0u8; glyph_count * height * bytes_per_row]; + + for i in 0..glyph_count { + if let Some(c) = char::from_u32(i as u32) { + let glyph_offset = i * height * bytes_per_row; + + font.display_glyph(c, |bit, x, y| { + if bit != 0 { + let byte_offset = + glyph_offset + (y as usize) * bytes_per_row + ((x as usize) / 8); + let bit_offset = 7 - (x % 8); + if byte_offset < glyphs.len() { + glyphs[byte_offset] |= 1 << bit_offset; + } + } + }); + } + } + + Self { + glyphs, + width, + height, + } + } +} + pub struct TextScreen { console: ransid::Console, + font: ConsoleFont, } impl TextScreen { - pub fn new() -> TextScreen { + pub fn new(font: Option) -> TextScreen { TextScreen { // Width and height will be filled in on the next write to the console console: ransid::Console::new(0, 0), + font: font.unwrap_or_else(|| ConsoleFont::new(FONT.to_vec(), 8, 16)), } } @@ -210,18 +262,19 @@ impl TextScreen { x: usize, y: usize, character: char, + font: &ConsoleFont, color: u32, _bold: bool, _italic: bool, ) { - if x + 8 <= map.width && y + 16 <= map.height { + if x + font.width <= map.width && y + font.height <= map.height { let mut dst = map.offscreen as *mut u8 as usize + (y * map.width + x) * 4; - let font_i = 16 * (character as usize); - if font_i + 16 <= FONT.len() { - for row in 0..16 { - let row_data = FONT[font_i + row]; - for col in 0..8 { + let font_i = font.height * (character as usize); + if font_i + font.height <= font.glyphs.len() { + for row in 0..font.height { + let row_data = font.glyphs[font_i + row]; + for col in 0..font.width { if (row_data >> (7 - col)) & 1 == 1 { unsafe { *((dst + col * 4) as *mut u32) = color; @@ -265,7 +318,8 @@ impl TextScreen { } }; - self.console.resize(map.width / 8, map.height / 16); + self.console + .resize(map.width / self.font.width, map.height / self.font.height); if self.console.state.x >= self.console.state.w { self.console.state.x = self.console.state.w - 1; } @@ -279,7 +333,13 @@ impl TextScreen { { let x = self.console.state.x; let y = self.console.state.y; - Self::invert(map, x * 8, y * 16, 8, 16); + Self::invert( + map, + x * self.font.width, + y * self.font.height, + self.font.width, + self.font.height, + ); col_changed(x); line_changed(y); } @@ -293,13 +353,29 @@ impl TextScreen { bold, .. } => { - Self::char(map, x * 8, y * 16, c, color.as_rgb(), bold, false); + Self::char( + map, + x * self.font.width, + y * self.font.height, + c, + &self.font, + color.as_rgb(), + bold, + false, + ); col_changed(x); line_changed(y); } ransid::Event::Input { data } => input.extend(data), ransid::Event::Rect { x, y, w, h, color } => { - Self::rect(map, x * 8, y * 16, w * 8, h * 16, color.as_rgb()); + Self::rect( + map, + x * self.font.width, + y * self.font.height, + w * self.font.width, + h * self.font.height, + color.as_rgb(), + ); for y2 in y..y + h { line_changed(y2); } @@ -322,11 +398,13 @@ impl TextScreen { for raw_y in 0..h { let y = if from_y > to_y { raw_y } else { h - raw_y - 1 }; - for pixel_y in 0..16 { + for pixel_y in 0..self.font.width { { - let off_from = ((from_y + y) * 16 + pixel_y) * width + from_x * 8; - let off_to = ((to_y + y) * 16 + pixel_y) * width + to_x * 8; - let len = w * 8; + let off_from = ((from_y + y) * self.font.height + pixel_y) * width + + from_x * self.font.width; + let off_to = ((to_y + y) * self.font.height + pixel_y) * width + + to_x * self.font.width; + let len = w * self.font.width; if off_from + len <= pixels.len() && off_to + len <= pixels.len() { unsafe { @@ -356,15 +434,23 @@ impl TextScreen { { let x = self.console.state.x; let y = self.console.state.y; - Self::invert(map, x * 8, y * 16, 8, 16); + Self::invert( + map, + x * self.font.width, + y * self.font.height, + self.font.width, + self.font.height, + ); line_changed(y); } let damage = Damage { - x: u32::try_from(min_changed_x).unwrap() * 8, - y: u32::try_from(min_changed_y).unwrap() * 16, - width: u32::try_from(max_changed_x.saturating_sub(min_changed_x) + 1).unwrap() * 8, - height: u32::try_from(max_changed_y.saturating_sub(min_changed_y) + 1).unwrap() * 16, + x: u32::try_from(min_changed_x).unwrap() * self.font.width as u32, + y: u32::try_from(min_changed_y).unwrap() * self.font.height as u32, + width: u32::try_from(max_changed_x.saturating_sub(min_changed_x) + 1).unwrap() + * self.font.width as u32, + height: u32::try_from(max_changed_y.saturating_sub(min_changed_y) + 1).unwrap() + * self.font.height as u32, }; damage @@ -420,10 +506,10 @@ impl TextScreen { } else { let deleted_rows = (old_map.height - new_map.height).div_ceil(16); for row in 0..new_map.height { - if row + (deleted_rows + 1) * 16 >= old_map.height { + if row + (deleted_rows + 1) * self.font.height >= old_map.height { break; } - copy_row(old_map, new_map, row + deleted_rows * 16, row); + copy_row(old_map, new_map, row + deleted_rows * self.font.height, row); } self.console.state.y = self.console.state.y.saturating_sub(deleted_rows); } diff --git a/drivers/graphics/fbcond/Cargo.toml b/drivers/graphics/fbcond/Cargo.toml index ea9e2d6759..8ff76e676d 100644 --- a/drivers/graphics/fbcond/Cargo.toml +++ b/drivers/graphics/fbcond/Cargo.toml @@ -20,6 +20,9 @@ daemon = { path = "../../../daemon" } graphics-ipc = { path = "../graphics-ipc" } inputd = { path = "../../inputd" } libredox.workspace = true +psf-rs.workspace = true +serde.workspace = true +toml.workspace = true [features] default = [] diff --git a/drivers/graphics/fbcond/src/scheme.rs b/drivers/graphics/fbcond/src/scheme.rs index 1bee134e29..778cef4064 100644 --- a/drivers/graphics/fbcond/src/scheme.rs +++ b/drivers/graphics/fbcond/src/scheme.rs @@ -1,13 +1,18 @@ use std::collections::BTreeMap; +use std::fs; use std::os::fd::AsRawFd; +use console_draw::ConsoleFont; use event::{EventQueue, UserData}; +use orbclient::FONT; use redox_scheme::scheme::SchemeSync; use redox_scheme::{CallerCtx, OpenResult}; use scheme_utils::{FpathWriter, HandleMap}; use syscall::schemev2::NewFdFlags; use syscall::{Error, EventFlags, Result, EACCES, EAGAIN, EBADF, ENOENT, O_NONBLOCK}; +use serde::Deserialize; + use crate::display::Display; use crate::text::TextScreen; @@ -49,6 +54,39 @@ impl FbconScheme { pub fn new(vt_ids: &[usize], event_queue: &mut EventQueue) -> FbconScheme { let mut vts = BTreeMap::new(); + let config = match fs::read_to_string("/etc/fbcond.toml") { + Ok(config) => config, + Err(err) => { + log::debug!("Failed to read config: {err}"); + String::new() + } + }; + let config = match toml::from_str::(&config) { + Ok(config) => config, + Err(err) => { + log::debug!("Failed to parse config: {err}"); + log::debug!("Using fallback font"); + FbconConfig { + font: FontConfig { + path: String::new(), + }, + } + } + }; + + let font = if !&config.font.path.is_empty() { + match fs::read(&config.font.path) { + Ok(contents) => Some(ConsoleFont::from_psf(&contents)), + Err(err) => { + log::debug!("Failed to read font {}: {err}", &config.font.path); + log::debug!("Using fallback font"); + None + } + } + } else { + None + }; + for &vt_i in vt_ids { let display = Display::open_new_vt().expect("Failed to open display for vt"); event_queue @@ -58,7 +96,8 @@ impl FbconScheme { event::EventFlags::READ, ) .expect("Failed to subscribe to input events for vt"); - vts.insert(VtIndex(vt_i), TextScreen::new(display)); + + vts.insert(VtIndex(vt_i), TextScreen::new(display, font.clone())); } FbconScheme { @@ -191,3 +230,12 @@ impl SchemeSync for FbconScheme { self.handles.remove(id); } } + +#[derive(Deserialize)] +struct FbconConfig { + font: FontConfig, +} +#[derive(Deserialize)] +struct FontConfig { + path: String, +} diff --git a/drivers/graphics/fbcond/src/text.rs b/drivers/graphics/fbcond/src/text.rs index 8a24bbebe6..7eed9733d8 100644 --- a/drivers/graphics/fbcond/src/text.rs +++ b/drivers/graphics/fbcond/src/text.rs @@ -13,10 +13,10 @@ pub struct TextScreen { } impl TextScreen { - pub fn new(display: Display) -> TextScreen { + pub fn new(display: Display, font: Option) -> TextScreen { TextScreen { display, - inner: console_draw::TextScreen::new(), + inner: console_draw::TextScreen::new(font), ctrl: false, input: VecDeque::new(), }