use alloc::sync::Arc;
use alloc::vec::Vec;
use alloc::boxed::Box;
use font_rs::{font, font::{Font, GlyphBitmap}};
use hashbrown::HashMap;
use spin::Mutex;
use sunrise_libuser::error::{ViError, Error};
use sunrise_libuser::keyboard::HidKeyboardStateType;
use sunrise_libuser::futures::WorkQueue;
use sunrise_libuser::mem::{find_free_address, PAGE_SIZE};
use sunrise_libuser::types::SharedMemory;
use sunrise_libutils::align_up;
use sunrise_libkern::MemoryPermissions;
use crate::Buffer;
use crate::VBEColor as Color;
use core::fmt::Write;
use core::sync::atomic::Ordering;
use sunrise_libuser::ps2::Keyboard;
use crate::libuser::futures_rs::future::FutureObj;
use bit_field::BitField;
#[derive(Copy, Clone, Debug)]
#[allow(clippy::missing_docs_in_private_items)]
struct Pos {
x: usize,
y: usize,
}
#[allow(missing_debug_implementations)]
pub struct Terminal {
framebuffer: Arc<Buffer>,
cursor_pos: Pos,
font: Font<'static>,
cached_glyphs: HashMap<char, GlyphBitmap>,
advance_width: usize,
linespace: usize,
ascent: usize,
descent: usize,
}
static FONT: &[u8] = include_bytes!("../../external/fonts/Monaco.ttf");
#[allow(clippy::cast_sign_loss)]
pub fn font_height() -> usize {
let my_font = font::parse(FONT)
.expect("Failed parsing provided font");
let v_metrics = my_font.get_v_metrics(FONT_SIZE).unwrap();
let my_ascent = v_metrics.ascent as usize;
let my_descent = -v_metrics.descent as usize;
my_descent + my_ascent
}
const FONT_SIZE: u32 = 10;
impl Terminal {
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_wrap)]
pub fn new(sharedmem: SharedMemory, top: i32, left: i32, width: u32, height: u32) -> Result<Self, Error> {
let my_font = font::parse(FONT)
.expect("Failed parsing provided font");
let v_metrics = my_font.get_v_metrics(FONT_SIZE).unwrap();
let h_metrics = my_font.get_h_metrics(my_font.lookup_glyph_id('A' as u32).unwrap(), FONT_SIZE).unwrap();
let my_ascent = v_metrics.ascent as usize;
let my_descent = -v_metrics.descent as usize;
let my_advance_width = h_metrics.advance_width as usize;
let my_linespace = my_descent + my_ascent;
let size = align_up(width * height * 4, PAGE_SIZE as _);
let addr = find_free_address(size as _, PAGE_SIZE)?;
let mapped = sharedmem.map(addr, size as _, MemoryPermissions::READABLE | MemoryPermissions::WRITABLE)?;
let buf = Arc::new(Buffer { mem: mapped, top, left, width, height });
super::BUFFERS.lock().push(Arc::downgrade(&buf));
Ok(Terminal {
framebuffer: buf,
font: my_font,
cached_glyphs: HashMap::with_capacity(128),
advance_width: my_advance_width,
linespace: my_linespace,
ascent: my_ascent,
descent: my_descent,
cursor_pos: Pos { x: 0, y: my_ascent },
})
}
pub fn draw(&mut self) {
self.framebuffer.draw();
}
#[inline]
fn carriage_return(&mut self) {
self.cursor_pos.x = 0;
}
#[inline]
fn line_feed(&mut self) {
if self.cursor_pos.y + self.linespace + self.descent >= self.framebuffer.height() as usize {
self.scroll_screen();
} else {
self.cursor_pos.y += self.linespace;
}
self.carriage_return();
}
#[inline]
fn advance_pos(&mut self) {
self.cursor_pos.x += self.advance_width;
if self.cursor_pos.x + self.advance_width >= self.framebuffer.width() as usize {
self.line_feed();
}
}
fn move_pos_back(&mut self) {
if self.cursor_pos.x >= self.advance_width {
self.cursor_pos.x -= self.advance_width;
}
}
#[inline]
fn scroll_screen(&mut self) {
let linespace_size_in_framebuffer = self.framebuffer.get_px_offset(0, self.linespace);
let lastline_top_left_corner = self.framebuffer.get_px_offset(0, self.cursor_pos.y - self.ascent);
assert!(lastline_top_left_corner + linespace_size_in_framebuffer < self.framebuffer.get_buffer().len(), "Window is drunk: {} + {} < {}", lastline_top_left_corner, linespace_size_in_framebuffer, self.framebuffer.get_buffer().len());
for i in 0..lastline_top_left_corner {
let to_store = self.framebuffer.get_buffer()[i + linespace_size_in_framebuffer].load(Ordering::Relaxed);
self.framebuffer.get_buffer()[i].store(to_store, Ordering::Relaxed);
}
for i in lastline_top_left_corner..self.framebuffer.get_buffer().len() {
self.framebuffer.get_buffer()[i].store(0, Ordering::Relaxed);
}
}
pub fn clear(&mut self) {
unsafe {
let buf = self.framebuffer.get_buffer();
for i in buf {
i.store(0, Ordering::Relaxed);
}
}
self.cursor_pos = Pos { x: 0, y: self.ascent };
}
pub fn print_attr(&mut self, string: &str, fg: Color, bg: Color) {
for mychar in string.chars() {
match mychar {
'\n' => { self.line_feed(); }
'\x08' => {
self.move_pos_back();
let empty_glyph = GlyphBitmap { width: 0, height: 0, top: 0, left: 0, data: Vec::new() };
Self::display_glyph_in_box(&empty_glyph, &self.framebuffer,
self.advance_width, self.ascent, self.descent,
fg, bg, self.cursor_pos);
}
mychar => {
{
let Terminal {
cached_glyphs, font, advance_width, ascent, descent, cursor_pos, ..
} = self;
if (mychar as u64) < 128 {
let glyph = cached_glyphs.entry(mychar)
.or_insert_with(|| {
font.lookup_glyph_id(mychar as u32)
.and_then(|glyphid| font.render_glyph(glyphid, FONT_SIZE))
.unwrap_or(GlyphBitmap { width: 0, height: 0, top: 0, left: 0, data: Vec::new() })
});
Self::display_glyph_in_box(glyph, &self.framebuffer,
*advance_width, *ascent, *descent,
fg, bg, *cursor_pos);
} else {
let glyph = font.lookup_glyph_id(mychar as u32)
.and_then(|glyphid| font.render_glyph(glyphid, FONT_SIZE))
.unwrap_or(GlyphBitmap { width: 0, height: 0, top: 0, left: 0, data: Vec::new() });
Self::display_glyph_in_box(&glyph, &self.framebuffer,
*advance_width, *ascent, *descent,
fg, bg, *cursor_pos);
}
}
self.advance_pos();
}
}
}
}
#[allow(clippy::cast_sign_loss)]
#[allow(clippy::cast_possible_wrap)]
#[allow(clippy::too_many_arguments)]
fn display_glyph_in_box(glyph: &GlyphBitmap, framebuffer: &Buffer,
box_width: usize, box_ascent: usize, box_descent: usize,
fg: Color, bg: Color, pos: Pos) {
#[inline]
fn blend_subpixels(fg: u8, bg: u8, fg_alpha: u8) -> u8 {
(( u16::from(fg) * u16::from(fg_alpha)
+ u16::from(bg) * u16::from(0xFF - fg_alpha)
) / 0xFF
) as u8
}
for y in -(box_ascent as i32)..=(box_descent as i32) {
for x in 0..(box_width as i32) {
let glyphx: i32 = x - glyph.left;
let glyphy: i32 = y - glyph.top;
let to_display =
if glyphx >= 0 && glyphy >= 0
&& glyphx < (glyph.width as i32) && glyphy < (glyph.height as i32) {
let glyph_alpha = glyph.data[glyphy as usize * glyph.width + glyphx as usize];
Color::rgb(
blend_subpixels(fg.r, bg.r, glyph_alpha),
blend_subpixels(fg.g, bg.g, glyph_alpha),
blend_subpixels(fg.b, bg.b, glyph_alpha),
)
} else {
bg
};
let idx = framebuffer.get_px_offset(
(pos.x as i32 + x) as usize,
(pos.y as i32 + y) as usize);
let color: u32 = unsafe {
core::mem::transmute(to_display)
};
framebuffer.get_buffer()[idx].store(color, Ordering::Relaxed);
}
}
}
}
impl Write for Terminal {
fn write_str(&mut self, s: &str) -> Result<(), ::core::fmt::Error> {
let fg = Color::rgb(255, 255, 255);
let bg = Color::rgb(0, 0, 0);
self.print_attr(s, fg, bg);
Ok(())
}
}
#[derive(Clone)]
pub struct TerminalPipe {
terminal: Arc<Mutex<Terminal>>
}
impl TerminalPipe {
pub fn new(terminal: Terminal) -> TerminalPipe {
TerminalPipe {
terminal: Arc::new(Mutex::new(terminal))
}
}
}
impl sunrise_libuser::twili::IPipeAsync for TerminalPipe {
fn read<'a>(&'a mut self, manager: WorkQueue<'static>, buf: &'a mut [u8]) -> FutureObj<'a, Result<u64, Error>> {
FutureObj::new(Box::new(async move {
let mut keyboard = Keyboard::new().unwrap();
let mut i = 0;
while buf.len() - i >= 4 {
let state = keyboard.read_keystate_async(manager.clone()).await;
let key = if let HidKeyboardStateType::Ascii = state.state_type {
let lower_case = char::from(state.data);
let upper_case = char::from(state.additional_data);
let is_upper = state.modifiers.get_bit(0) || state.modifiers.get_bit(1) || state.modifiers.get_bit(2);
let is_pressed = state.modifiers.get_bit(7);
if is_pressed {
if is_upper {
upper_case
} else {
lower_case
}
} else {
continue;
}
} else {
continue;
};
let is_ctrl = state.modifiers.get_bit(3) || state.modifiers.get_bit(4);
log::info!("{:?}", state);
if is_ctrl && key == 'd' {
return Ok(i as u64);
}
if key == '\x08' && i == 0 {
continue;
}
let mut data = [0u8; 4];
let data = key.encode_utf8(&mut data[..]);
let mut locked = self.terminal.lock();
let err = locked.write_str(data);
if err.is_err() {
log::error!("{:?}", err);
}
locked.draw();
core::mem::drop(locked);
if key == '\x08' {
let mut done = false;
for j in 1..=core::cmp::min(i, 4) {
if core::str::from_utf8(&buf[i - j..i]).is_ok() {
i -= j;
done = true;
break;
}
}
assert!(done, "Data contained invalid utf-8?");
} else {
buf[i..i + data.len()].copy_from_slice(data.as_bytes());
i += data.len();
if key == '\n' {
return Ok(i as u64);
}
}
}
Ok(i as u64)
}))
}
fn write<'a>(&'a mut self, _manager: WorkQueue<'static>, data: &'a [u8]) -> FutureObj<'a, Result<(), Error>> {
FutureObj::new(Box::new(async move {
let s = core::str::from_utf8(data).or(Err(ViError::InvalidUtf8))?;
let mut locked = self.terminal.lock();
let _ = locked.write_str(s);
locked.draw();
Ok(())
}))
}
}