feat(ipc): saved replies: SaveReply/ReplySaved (8 per thread, released on thread death, unforgeable); terminal READ saved until Enter (line.rs editor, reader queue), now pinged
This commit is contained in:
@@ -2,10 +2,11 @@
|
||||
//! behind `Console`.
|
||||
|
||||
use server_framework::{Encode, MALFORMED, Server};
|
||||
use types::bounded::CapSlot;
|
||||
use types::log::{TEXT_BYTES, text_of, text_words};
|
||||
use types::bounded::{CapSlot, SavedReply};
|
||||
use types::log::{text_of, text_words};
|
||||
|
||||
use crate::grid::Grid;
|
||||
use crate::line::Line;
|
||||
|
||||
/// What the terminal draws on and reads from.
|
||||
pub trait Console {
|
||||
@@ -17,10 +18,12 @@ pub trait Console {
|
||||
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.
|
||||
/// A key already typed, without waiting: a byte, `\n` for Enter, 8
|
||||
/// for Backspace.
|
||||
fn poll_key(&mut self) -> Option<u8>;
|
||||
/// Keeps the caller of the request being handled, to answer later.
|
||||
fn save_reply(&mut self) -> Option<SavedReply>;
|
||||
fn answer(&mut self, reply: SavedReply, words: [usize; 4]);
|
||||
/// Takes input focus back from whoever took it, and clears the screen.
|
||||
fn take_focus(&mut self);
|
||||
fn interrupt(&mut self, notification: CapSlot);
|
||||
@@ -29,17 +32,19 @@ pub trait Console {
|
||||
}
|
||||
|
||||
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;
|
||||
/// Callers waiting for a line at once, at most.
|
||||
const READERS: usize = 4;
|
||||
|
||||
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,
|
||||
line: Line,
|
||||
/// Callers waiting for a line, oldest first, and whether theirs is
|
||||
/// secret: the oldest gets the line being typed.
|
||||
waiting: [Option<(SavedReply, bool)>; READERS],
|
||||
flush_pending: bool,
|
||||
/// How many cells of each row are drawn on the screen.
|
||||
drawn: [usize; crate::grid::MAX_ROWS],
|
||||
@@ -63,9 +68,8 @@ impl<C: Console> TerminalServer<C> {
|
||||
let mut s = Self {
|
||||
console,
|
||||
grid: Grid::new(cols, rows),
|
||||
line: [0; MAX_LINE],
|
||||
line_len: 0,
|
||||
line_read: 0,
|
||||
line: Line::new(),
|
||||
waiting: [None; READERS],
|
||||
flush_pending: false,
|
||||
drawn: [0; crate::grid::MAX_ROWS],
|
||||
typeahead: [0; TYPEAHEAD],
|
||||
@@ -131,20 +135,26 @@ impl<C: Console> TerminalServer<C> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A key typed ahead, or the next one.
|
||||
fn next_key(&mut self) -> u8 {
|
||||
/// A key typed ahead, or one just typed.
|
||||
fn take_key(&mut self) -> Option<u8> {
|
||||
if self.typeahead_len == 0 {
|
||||
return self.console.next_key();
|
||||
return self.console.poll_key();
|
||||
}
|
||||
let key = self.typeahead[0];
|
||||
self.typeahead.copy_within(1..self.typeahead_len, 0);
|
||||
self.typeahead_len -= 1;
|
||||
key
|
||||
Some(key)
|
||||
}
|
||||
|
||||
/// Keys typed while nobody reads a line: Ctrl+C interrupts, the rest
|
||||
/// wait for the next line.
|
||||
fn keys_arrived(&mut self) {
|
||||
if self.line.typing() {
|
||||
if self.type_keys() {
|
||||
self.serve_waiting();
|
||||
}
|
||||
return;
|
||||
}
|
||||
while let Some(key) = self.console.poll_key() {
|
||||
if key == types::terminal::CTRL_C {
|
||||
self.typeahead_len = 0;
|
||||
@@ -159,63 +169,73 @@ impl<C: Console> TerminalServer<C> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
fn begin_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.line.start(secret);
|
||||
}
|
||||
|
||||
/// Feeds the keys there are into the line; whether it is done.
|
||||
fn type_keys(&mut self) -> bool {
|
||||
let mut done = false;
|
||||
while !done && let Some(key) = self.take_key() {
|
||||
done = self.line.key(key, &mut self.grid);
|
||||
self.redraw();
|
||||
self.console.flush();
|
||||
}
|
||||
self.redraw();
|
||||
self.console.flush();
|
||||
done
|
||||
}
|
||||
|
||||
/// 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);
|
||||
/// The oldest waiting caller gets the finished line; once it is all
|
||||
/// read, the next waiting caller's line starts.
|
||||
fn serve_waiting(&mut self) {
|
||||
loop {
|
||||
if self.line.unread() {
|
||||
let Some((reply, _)) = self.waiting[0] else { return };
|
||||
self.waiting.rotate_left(1);
|
||||
self.waiting[READERS - 1] = None;
|
||||
let chunk = self.line.chunk();
|
||||
self.console.answer(reply, chunk);
|
||||
// More of it comes when it asks again.
|
||||
if self.line.unread() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
let Some((_, secret)) = self.waiting[0] else { return };
|
||||
self.begin_line(secret);
|
||||
if !self.type_keys() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/// The next chunk of the typed line now, if there is one; otherwise
|
||||
/// the caller is kept and answered once the line is typed.
|
||||
fn read(&mut self, secret: bool) -> Option<[usize; 4]> {
|
||||
if self.line.unread() {
|
||||
let chunk = self.line.chunk();
|
||||
if !self.line.unread() {
|
||||
self.serve_waiting();
|
||||
}
|
||||
return Some(chunk);
|
||||
}
|
||||
let nobody_waits = self.waiting[0].is_none();
|
||||
if nobody_waits && !self.line.typing() {
|
||||
self.begin_line(secret);
|
||||
}
|
||||
if nobody_waits && self.type_keys() {
|
||||
return Some(self.line.chunk());
|
||||
}
|
||||
let free = self.waiting.iter().position(Option::is_none);
|
||||
match (free, free.and_then(|_| self.console.save_reply())) {
|
||||
(Some(i), Some(reply)) => {
|
||||
self.waiting[i] = Some((reply, secret));
|
||||
None
|
||||
}
|
||||
// Too many waiting: an empty line.
|
||||
_ => Some(text_words(b"\n")),
|
||||
}
|
||||
words
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,8 +256,9 @@ impl<C: Console> Server for TerminalServer<C> {
|
||||
|
||||
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),
|
||||
// Kept for later: what goes back now is not sent.
|
||||
types::terminal::READ => self.read(false).unwrap_or([0; 4]),
|
||||
types::terminal::READ_SECRET => self.read(true).unwrap_or([0; 4]),
|
||||
types::terminal::REDRAW => {
|
||||
// Taking focus back clears the screen.
|
||||
self.console.take_focus();
|
||||
|
||||
Reference in New Issue
Block a user