feat(userspace): programs in /bin w/o .elf; bin manifests (needs = display) so shell runs snake; Ctrl+C kills foreground via terminal interrupt notification; ctrl keys; sleep
This commit is contained in:
@@ -11,7 +11,7 @@ pub fn run() -> ! {
|
||||
// Slot 0 holds the endpoint init delegated. Init has no process
|
||||
// creation authority to hand on yet, so Launch reports an error.
|
||||
let mut srv = server::SessionServer::new(None);
|
||||
let _ = srv.register_app("snake", "/apps/snake.elf");
|
||||
let _ = srv.register_app("snake", "bin/snake");
|
||||
let ep = types::bounded::CapSlot::new(0).unwrap();
|
||||
server_framework::serve(&mut srv, ep)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use server_framework::Server;
|
||||
fn test_server() -> SessionServer {
|
||||
let cap = types::bounded::CapSlot::new(0).unwrap();
|
||||
let mut s = SessionServer::new(Some(cap));
|
||||
s.register_app("snake", "/apps/snake.elf").unwrap();
|
||||
s.register_app("snake", "bin/snake").unwrap();
|
||||
s.register_app("clock", "/apps/clock.elf").unwrap();
|
||||
s
|
||||
}
|
||||
@@ -108,7 +108,7 @@ fn the_app_table_fills_up_without_panicking() {
|
||||
#[test]
|
||||
fn launching_needs_process_creation_authority() {
|
||||
let mut server = SessionServer::new(None);
|
||||
server.register_app("snake", "/apps/snake.elf").unwrap();
|
||||
server.register_app("snake", "bin/snake").unwrap();
|
||||
let resp = server.handle(SessionRequest::Launch { app: AppId::new(0) });
|
||||
assert_eq!(resp, SessionResponse::Error);
|
||||
}
|
||||
|
||||
@@ -80,6 +80,11 @@ impl Grid {
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks every row to be drawn again.
|
||||
pub fn touch_all(&mut self) {
|
||||
self.dirty.fill(true);
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.cells.fill(b' ');
|
||||
self.col = 0;
|
||||
|
||||
@@ -12,8 +12,10 @@ mod tests;
|
||||
use libkos::display::{self, CELL_HEIGHT, CELL_WIDTH};
|
||||
use libkos::input::{InputEvent, Key};
|
||||
use libkos::syscall;
|
||||
use types::badge::Badge;
|
||||
use types::bounded::CapSlot;
|
||||
use types::display::{Color, ScreenCoord};
|
||||
use types::terminal::{KEYS_BIT, TIMER_BIT};
|
||||
|
||||
/// Where init put them (its manifest: own endpoint, then `uses`).
|
||||
const EP: CapSlot = CapSlot::const_new(0);
|
||||
@@ -26,11 +28,23 @@ const BG: Color = Color::rgb(16, 16, 24);
|
||||
const FLUSH_DELAY: u64 = 20 * 10_000;
|
||||
|
||||
struct Screen {
|
||||
keys: CapSlot,
|
||||
/// Bound to the serving thread; the flush timer signals it.
|
||||
/// Bound to the serving thread: the display signals it with
|
||||
/// `KEYS_BIT`, the flush timer with `TIMER_BIT`.
|
||||
notify: CapSlot,
|
||||
timer: CapSlot,
|
||||
}
|
||||
|
||||
/// A key as the line editor wants it, if it is one.
|
||||
fn byte(event: InputEvent) -> Option<u8> {
|
||||
match event {
|
||||
InputEvent::KeyDown(Key::Char(c)) => Some(c),
|
||||
InputEvent::KeyDown(Key::Space | Key::Tab) => Some(b' '),
|
||||
InputEvent::KeyDown(Key::Enter) => Some(b'\n'),
|
||||
InputEvent::KeyDown(Key::Backspace) => Some(server::BACKSPACE),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn at(col: usize, row: usize) -> (ScreenCoord, ScreenCoord) {
|
||||
(ScreenCoord::new((col * CELL_WIDTH) as u16), ScreenCoord::new((row * CELL_HEIGHT) as u16))
|
||||
}
|
||||
@@ -57,34 +71,50 @@ impl server::Console for Screen {
|
||||
|
||||
fn next_key(&mut self) -> u8 {
|
||||
loop {
|
||||
while let Some(event) = display::poll_input(DISPLAY) {
|
||||
let InputEvent::KeyDown(key) = event else { continue };
|
||||
match key {
|
||||
Key::Char(c) => return c,
|
||||
Key::Space | Key::Tab => return b' ',
|
||||
Key::Enter => return b'\n',
|
||||
Key::Backspace => return server::BACKSPACE,
|
||||
_ => {}
|
||||
}
|
||||
if let Some(key) = self.poll_key() {
|
||||
return key;
|
||||
}
|
||||
let _ = syscall::wait(self.keys);
|
||||
let _ = syscall::wait(self.notify);
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_key(&mut self) -> Option<u8> {
|
||||
while let Some(event) = display::poll_input(DISPLAY) {
|
||||
if let Some(key) = byte(event) {
|
||||
return Some(key);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn take_focus(&mut self) {
|
||||
display::subscribe(DISPLAY, self.notify, Badge::new(KEYS_BIT));
|
||||
display::clear(DISPLAY, BG);
|
||||
}
|
||||
|
||||
fn interrupt(&mut self, notification: CapSlot) {
|
||||
let _ = syscall::signal(notification);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run() -> ! {
|
||||
let Some(keys) = display::subscribe_input(DISPLAY) else {
|
||||
let Some(notify) = syscall::create_notification().ok().filter(|n| syscall::bind_notification(*n).is_ok()) else {
|
||||
libkos::println!("terminal: no notification, exiting");
|
||||
syscall::exit();
|
||||
};
|
||||
let timer_perms = types::permission::PermissionSet::empty().with(types::permission::Permission::Write);
|
||||
let Ok(timer) = syscall::mint(notify, Badge::new(TIMER_BIT), timer_perms) else {
|
||||
libkos::println!("terminal: no timer, exiting");
|
||||
syscall::exit();
|
||||
};
|
||||
if !display::subscribe(DISPLAY, notify, Badge::new(KEYS_BIT)) {
|
||||
libkos::println!("terminal: no input focus, exiting");
|
||||
syscall::exit();
|
||||
};
|
||||
let Some(timer) = syscall::create_notification().ok().filter(|t| syscall::bind_notification(*t).is_ok()) else {
|
||||
libkos::println!("terminal: no timer notification, exiting");
|
||||
syscall::exit();
|
||||
};
|
||||
}
|
||||
let (w, h) = display::get_dimensions(DISPLAY);
|
||||
display::clear(DISPLAY, BG);
|
||||
let cols = w.raw() as usize / CELL_WIDTH;
|
||||
let rows = h.raw() as usize / CELL_HEIGHT;
|
||||
let mut srv = server::TerminalServer::new(Screen { keys, timer }, cols, rows);
|
||||
let mut srv = server::TerminalServer::new(Screen { notify, timer }, cols, rows);
|
||||
server_framework::serve(&mut srv, EP)
|
||||
}
|
||||
|
||||
@@ -17,10 +17,17 @@ pub trait Console {
|
||||
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);
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -30,6 +37,10 @@ pub struct TerminalServer<C: Console> {
|
||||
line_len: usize,
|
||||
line_read: usize,
|
||||
flush_pending: bool,
|
||||
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.
|
||||
@@ -50,6 +61,9 @@ impl<C: Console> TerminalServer<C> {
|
||||
line_len: 0,
|
||||
line_read: 0,
|
||||
flush_pending: false,
|
||||
typeahead: [0; TYPEAHEAD],
|
||||
typeahead_len: 0,
|
||||
on_interrupt: None,
|
||||
};
|
||||
s.redraw();
|
||||
s.console.flush();
|
||||
@@ -60,6 +74,11 @@ impl<C: Console> TerminalServer<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
|
||||
}
|
||||
@@ -88,13 +107,52 @@ impl<C: Console> TerminalServer<C> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Edits a line from the keyboard, echoing it, until Enter.
|
||||
/// 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, until Enter. Ctrl+C
|
||||
/// drops it and reads as an empty line.
|
||||
fn read_line(&mut self) {
|
||||
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.console.next_key() {
|
||||
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;
|
||||
@@ -148,9 +206,23 @@ impl<C: Console> Server for TerminalServer<C> {
|
||||
Done
|
||||
}
|
||||
|
||||
fn handle_call(&mut self, _badge: usize, words: [usize; 4], _cap: Option<CapSlot>) -> ([usize; 4], Option<libkos::syscall::Give>) {
|
||||
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(),
|
||||
types::terminal::REDRAW => {
|
||||
self.console.take_focus();
|
||||
self.grid.touch_all();
|
||||
self.redraw();
|
||||
self.console.flush();
|
||||
[0; 4]
|
||||
}
|
||||
types::terminal::ON_INTERRUPT => match (cap, self.on_interrupt) {
|
||||
(Some(n), None) => {
|
||||
self.on_interrupt = Some(n);
|
||||
[0; 4]
|
||||
}
|
||||
_ => [MALFORMED, 0, 0, 0],
|
||||
},
|
||||
types::terminal::CLEAR => {
|
||||
self.handle(Request::Clear);
|
||||
[0; 4]
|
||||
@@ -166,10 +238,15 @@ impl<C: Console> Server for TerminalServer<C> {
|
||||
(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();
|
||||
/// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,21 @@ extern crate std;
|
||||
|
||||
use crate::server::{BACKSPACE, Console, TerminalServer};
|
||||
use server_framework::Server;
|
||||
use std::vec;
|
||||
use std::vec::Vec;
|
||||
use types::bounded::CapSlot;
|
||||
use types::log::{text_of, text_words};
|
||||
use types::terminal::{CTRL_C, KEYS_BIT, TIMER_BIT};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Fake {
|
||||
keys: Vec<u8>,
|
||||
/// Typed while nobody reads a line.
|
||||
typed: Vec<u8>,
|
||||
flushes: usize,
|
||||
timers: usize,
|
||||
focus_taken: usize,
|
||||
interrupted: Vec<CapSlot>,
|
||||
}
|
||||
|
||||
impl Console for Fake {
|
||||
@@ -24,6 +31,15 @@ impl Console for Fake {
|
||||
fn next_key(&mut self) -> u8 {
|
||||
self.keys.remove(0)
|
||||
}
|
||||
fn poll_key(&mut self) -> Option<u8> {
|
||||
(!self.typed.is_empty()).then(|| self.typed.remove(0))
|
||||
}
|
||||
fn take_focus(&mut self) {
|
||||
self.focus_taken += 1;
|
||||
}
|
||||
fn interrupt(&mut self, notification: CapSlot) {
|
||||
self.interrupted.push(notification);
|
||||
}
|
||||
}
|
||||
|
||||
fn term(keys: &[u8]) -> TerminalServer<Fake> {
|
||||
@@ -45,7 +61,7 @@ fn writes_land_on_the_grid_and_flush_once_per_burst() {
|
||||
}
|
||||
assert_eq!(&t.grid().row(0)[..11], b"hello world");
|
||||
assert_eq!((t.console().timers, t.console().flushes), (1, before));
|
||||
t.signal(1);
|
||||
t.signal(TIMER_BIT);
|
||||
assert_eq!(t.console().flushes, before + 1);
|
||||
}
|
||||
|
||||
@@ -68,3 +84,62 @@ fn backspace_on_an_empty_line_does_nothing() {
|
||||
let mut t = term(&[BACKSPACE, b'x', b'\n']);
|
||||
assert_eq!(read(&mut t), b"x\n");
|
||||
}
|
||||
|
||||
fn line_of(t: &mut TerminalServer<Fake>) -> Vec<u8> {
|
||||
let mut line = read(t);
|
||||
while line.last() != Some(&b'\n') {
|
||||
line.extend(read(t));
|
||||
}
|
||||
line
|
||||
}
|
||||
|
||||
fn on_interrupt(t: &mut TerminalServer<Fake>, slot: u8) -> usize {
|
||||
t.handle_call(1, [types::terminal::ON_INTERRUPT, 0, 0, 0], CapSlot::new(slot)).0[0]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctrl_c_while_a_program_runs_interrupts_it() {
|
||||
let mut t = term(&[]);
|
||||
assert_eq!(on_interrupt(&mut t, 7), 0);
|
||||
t.console_mut_for_test().typed = vec![b'x', CTRL_C];
|
||||
t.signal(KEYS_BIT);
|
||||
assert_eq!(t.console().interrupted, [CapSlot::new(7).unwrap()]);
|
||||
assert_eq!(&t.grid().row(0)[..2], b"^C");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_first_interrupt_notification_is_taken() {
|
||||
let mut t = term(&[]);
|
||||
assert_eq!(on_interrupt(&mut t, 7), 0);
|
||||
assert_ne!(on_interrupt(&mut t, 8), 0);
|
||||
t.console_mut_for_test().typed = vec![CTRL_C];
|
||||
t.signal(KEYS_BIT);
|
||||
assert_eq!(t.console().interrupted, [CapSlot::new(7).unwrap()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keys_typed_ahead_start_the_next_line() {
|
||||
let mut t = term(b"s\n");
|
||||
t.console_mut_for_test().typed = b"l".to_vec();
|
||||
t.signal(KEYS_BIT);
|
||||
assert!(t.console().interrupted.is_empty());
|
||||
assert_eq!(line_of(&mut t), b"ls\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ctrl_c_drops_the_line_being_typed() {
|
||||
let mut t = term(&[b'r', b'm', CTRL_C, b'l', b's', b'\n']);
|
||||
assert_eq!(on_interrupt(&mut t, 7), 0);
|
||||
assert_eq!(line_of(&mut t), b"\n");
|
||||
assert!(t.console().interrupted.is_empty(), "nothing runs to interrupt");
|
||||
assert_eq!(line_of(&mut t), b"ls\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn redraw_takes_focus_back_and_paints_every_row() {
|
||||
let mut t = term(&[]);
|
||||
t.handle_call(1, text_words(b"hi"), None);
|
||||
t.handle_call(1, [types::terminal::REDRAW, 0, 0, 0], None);
|
||||
assert_eq!(t.console().focus_taken, 1);
|
||||
assert_eq!(&t.grid().row(0)[..2], b"hi");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user