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:
2026-10-28 02:09:34 +01:00
parent 3d69c10079
commit d04dcfffa1
19 changed files with 588 additions and 94 deletions

View File

@@ -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();