feat(userspace): terminal server + shell desktop; US keymap w/ shift; libkos allocator multi-page fix; panics print
This commit is contained in:
184
crates/servers/terminal-server/src/server.rs
Normal file
184
crates/servers/terminal-server/src/server.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
//! 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);
|
||||
/// 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;
|
||||
}
|
||||
|
||||
pub const BACKSPACE: u8 = 8;
|
||||
const MAX_LINE: usize = 256;
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
};
|
||||
s.redraw();
|
||||
s.console.flush();
|
||||
s
|
||||
}
|
||||
|
||||
pub fn console(&self) -> &C {
|
||||
&self.console
|
||||
}
|
||||
|
||||
pub fn grid(&self) -> &Grid {
|
||||
&self.grid
|
||||
}
|
||||
|
||||
fn redraw(&mut self) {
|
||||
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) {
|
||||
self.console.draw_row(r, self.grid.row(r));
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// Edits a line from the keyboard, echoing it, until Enter.
|
||||
fn read_line(&mut self) {
|
||||
self.console.flush();
|
||||
self.line_len = 0;
|
||||
self.line_read = 0;
|
||||
loop {
|
||||
match self.console.next_key() {
|
||||
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(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.
|
||||
fn read(&mut self) -> [usize; 4] {
|
||||
if self.line_read == self.line_len {
|
||||
self.read_line();
|
||||
}
|
||||
let end = (self.line_read + TEXT_BYTES).min(self.line_len);
|
||||
let chunk = &self.line[self.line_read..end];
|
||||
self.line_read = end;
|
||||
text_words(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
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<CapSlot>) {
|
||||
let reply = match words[0] {
|
||||
types::terminal::READ => self.read(),
|
||||
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)
|
||||
}
|
||||
|
||||
/// The flush timer: show a burst of output in one frame.
|
||||
fn signal(&mut self, _bits: usize) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user