Files
k-os/crates/servers/terminal-server/src/server.rs

295 lines
9.5 KiB
Rust

//! Text out (the log protocol) and lines in, on a screen and keyboard
//! behind `Console`.
use server_framework::{Encode, MALFORMED, Server};
use types::bounded::CapSlot;
use types::log::{TEXT_BYTES, text_of, text_words};
use crate::grid::Grid;
/// What the terminal draws on and reads from.
pub trait Console {
fn draw_row(&mut self, row: usize, text: &[u8]);
/// Shows the cursor at a cell, over what `draw_row` drew there.
fn draw_cursor(&mut self, col: usize, row: usize, under: u8);
fn flush(&mut self);
/// Moves everything drawn up by `rows` text rows, blank below.
fn scroll(&mut self, rows: usize);
/// Soon calls `TerminalServer::signal`, to flush a burst of output once.
fn flush_later(&mut self);
/// Blocks until a key is typed: a byte, `\n` for Enter, 8 for Backspace.
fn next_key(&mut self) -> u8;
/// A key already typed, without waiting.
fn poll_key(&mut self) -> Option<u8>;
/// Takes input focus back from whoever took it, and clears the screen.
fn take_focus(&mut self);
fn interrupt(&mut self, notification: CapSlot);
/// Lets go of a notification it was given.
fn forget(&mut self, notification: CapSlot);
}
pub const BACKSPACE: u8 = 8;
const MAX_LINE: usize = 256;
/// Keys typed while no line is being read, kept for the next one.
const TYPEAHEAD: usize = 64;
pub struct TerminalServer<C: Console> {
console: C,
grid: Grid,
/// A typed line not yet handed out in full, and how much of it was.
line: [u8; MAX_LINE],
line_len: usize,
line_read: usize,
flush_pending: bool,
/// How many cells of each row are drawn on the screen.
drawn: [usize; crate::grid::MAX_ROWS],
typeahead: [u8; TYPEAHEAD],
typeahead_len: usize,
/// Told about Ctrl+C typed while no line is being read.
on_interrupt: Option<CapSlot>,
}
/// An empty reply, whose tag is not text.
pub struct Done;
impl Encode for Done {
fn encode(&self) -> [usize; 4] {
[0; 4]
}
}
impl<C: Console> TerminalServer<C> {
pub fn new(console: C, cols: usize, rows: usize) -> Self {
let mut s = Self {
console,
grid: Grid::new(cols, rows),
line: [0; MAX_LINE],
line_len: 0,
line_read: 0,
flush_pending: false,
drawn: [0; crate::grid::MAX_ROWS],
typeahead: [0; TYPEAHEAD],
typeahead_len: 0,
on_interrupt: None,
};
s.redraw();
s.console.flush();
s
}
pub fn console(&self) -> &C {
&self.console
}
#[cfg(test)]
pub fn console_mut_for_test(&mut self) -> &mut C {
&mut self.console
}
pub fn grid(&self) -> &Grid {
&self.grid
}
fn redraw(&mut self) {
let scrolled = self.grid.take_scrolled();
if scrolled > 0 {
self.console.scroll(scrolled);
let rows = self.grid.rows();
self.drawn.copy_within(scrolled.min(rows)..rows, 0);
self.drawn[rows - scrolled.min(rows)..rows].fill(0);
}
let mut dirty = [false; crate::grid::MAX_ROWS];
for r in self.grid.take_dirty() {
dirty[r] = true;
}
for (r, _) in dirty.iter().enumerate().filter(|(_, d)| **d) {
// As far as there is text, or as far as was drawn before (so
// what is gone is erased): rarely the whole width.
let row = self.grid.row(r);
let text = row.iter().rposition(|b| *b != b' ').map_or(0, |i| i + 1);
let width = text.max(self.drawn[r]).min(row.len());
if width > 0 {
self.console.draw_row(r, &row[..width]);
}
self.drawn[r] = text;
}
let (col, row) = (self.grid.col, self.grid.row);
let under = self.grid.row(row).get(col).copied().unwrap_or(b' ');
self.console.draw_cursor(col, row, under);
// The cursor cell is drawn too, so it is erased when it moves.
self.drawn[row] = self.drawn[row].max(col + 1);
}
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.grid.put(b);
}
self.redraw();
if !self.flush_pending {
self.flush_pending = true;
self.console.flush_later();
}
}
/// A key typed ahead, or the next one.
fn next_key(&mut self) -> u8 {
if self.typeahead_len == 0 {
return self.console.next_key();
}
let key = self.typeahead[0];
self.typeahead.copy_within(1..self.typeahead_len, 0);
self.typeahead_len -= 1;
key
}
/// Keys typed while nobody reads a line: Ctrl+C interrupts, the rest
/// wait for the next line.
fn keys_arrived(&mut self) {
while let Some(key) = self.console.poll_key() {
if key == types::terminal::CTRL_C {
self.typeahead_len = 0;
self.write(b"^C\n");
if let Some(n) = self.on_interrupt {
self.console.interrupt(n);
}
} else if self.typeahead_len < TYPEAHEAD {
self.typeahead[self.typeahead_len] = key;
self.typeahead_len += 1;
}
}
}
/// Edits a line from the keyboard, echoing it (as `*` if `secret`),
/// until Enter. Ctrl+C drops it and reads as an empty line.
fn read_line(&mut self, secret: bool) {
self.console.flush();
// Flushed now; a flush timer still running is harmless.
self.flush_pending = false;
self.line_len = 0;
self.line_read = 0;
loop {
match self.next_key() {
types::terminal::CTRL_C => {
self.line[0] = b'\n';
self.line_len = 1;
for &b in b"^C\n" {
self.grid.put(b);
}
break;
}
b'\n' => {
self.line[self.line_len] = b'\n';
self.line_len += 1;
self.grid.put(b'\n');
break;
}
BACKSPACE if self.line_len > 0 => {
self.line_len -= 1;
self.grid.back();
}
BACKSPACE => continue,
// Room is kept for the closing newline.
b if self.line_len + 1 < MAX_LINE && (b' '..=b'~').contains(&b) => {
self.line[self.line_len] = b;
self.line_len += 1;
self.grid.put(if secret { b'*' } else { b });
}
_ => continue,
}
self.redraw();
self.console.flush();
}
self.redraw();
self.console.flush();
}
/// The next chunk of the typed line, reading one first if none is left.
/// A secret line never reaches the screen, and is forgotten once read.
fn read(&mut self, secret: bool) -> [usize; 4] {
if self.line_read == self.line_len {
self.read_line(secret);
}
let end = (self.line_read + TEXT_BYTES).min(self.line_len);
let words = text_words(&self.line[self.line_read..end]);
self.line_read = end;
if secret && self.line_read == self.line_len {
self.line.fill(0);
}
words
}
}
impl<C: Console> Server for TerminalServer<C> {
type Request = Request;
type Response = Done;
fn handle(&mut self, request: Request) -> Done {
match request {
Request::Clear => {
self.grid.clear();
self.redraw();
self.console.flush();
}
}
Done
}
fn handle_call(&mut self, badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<libkos::syscall::Give>) {
let reply = match words[0] {
types::terminal::READ => self.read(false),
types::terminal::READ_SECRET => self.read(true),
types::terminal::REDRAW => {
// Taking focus back clears the screen.
self.console.take_focus();
self.drawn = [0; crate::grid::MAX_ROWS];
self.grid.touch_all();
self.redraw();
self.console.flush();
[0; 4]
}
types::terminal::ON_INTERRUPT => match cap {
Some(n) if badge & types::terminal::CONTROL != 0 => {
if let Some(old) = self.on_interrupt.replace(n) {
self.console.forget(old);
}
[0; 4]
}
_ => [MALFORMED, 0, 0, 0],
},
types::terminal::CLEAR => {
self.handle(Request::Clear);
[0; 4]
}
_ => match text_of(words) {
Some((bytes, len)) => {
self.write(&bytes[..len]);
[0; 4]
}
None => [MALFORMED, 0, 0, 0],
},
};
(reply, None)
}
/// Keys, or the flush timer: show a burst of output in one frame.
fn signal(&mut self, bits: usize) {
if bits & types::terminal::KEYS_BIT != 0 {
self.keys_arrived();
}
if bits & types::terminal::TIMER_BIT != 0 {
self.flush_pending = false;
self.console.flush();
}
}
}
pub enum Request {
Clear,
}
impl server_framework::Decode for Request {
fn decode(words: [usize; 4]) -> Option<Self> {
(words[0] == types::terminal::CLEAR).then_some(Request::Clear)
}
}