godspeed/app.rs
1// SPDX-License-Identifier: Apache-2.0
2//! A program with the screen and the keyboard to itself while it runs: an `app`.
3//!
4//! An app is a utility ([`crate::program`]) that, for as long as it runs, OWNS the console: what it prints is the
5//! screen, and the keys typed are its, not the shell's. When it ends - by [`App::close`], or by dying - the
6//! console goes back to the shell, which draws a fresh prompt. That is the mechanism the built-in full-screen
7//! tools (`chaos`, `observe`) already use: the console foreground (syscall 40), released by the kernel when its
8//! holder dies, so a crashed app cannot take the keyboard with it.
9//!
10//! ```ignore
11//! use godspeed::{self as gs, ServiceContext};
12//!
13//! #[allow(unsafe_code)]
14//! #[no_mangle]
15//! pub extern "C" fn service_main(ctx: ServiceContext) -> ! {
16//! let mut buf = [0u8; gs::program::ARGS_MAX];
17//! let _args = gs::program::args(&ctx, &mut buf);
18//! let app = gs::app::App::open(&ctx);
19//! app.println("hello - press any key");
20//! let _ = app.wait_key();
21//! app.close(0)
22//! }
23//! ```
24//!
25//! **What it is given to do this**: the console, which the operator approves at `godspeed install` - "it reads
26//! the keyboard while it runs" - because keystrokes are commands (CLAUDE.md 6.4, SEC-2). Nothing else.
27
28use crate::ServiceContext;
29
30/// How often [`App::wait_key`] looks for a key.
31const KEY_POLL_MS: u64 = 20;
32
33/// The console, held by this program until [`App::close`].
34pub struct App<'c> {
35 ctx: &'c ServiceContext,
36}
37
38impl<'c> App<'c> {
39 /// Take the console: its keys are this program's from now on, they are not echoed, and the screen is cleared.
40 pub fn open(ctx: &'c ServiceContext) -> App<'c> {
41 ctx.claim_console_foreground();
42 ctx.console_echo(false);
43 crate::io::print(ctx, "\x1b[2J\x1b[H");
44 App { ctx }
45 }
46
47 /// Print `s` where the cursor is.
48 pub fn print(&self, s: &str) { crate::io::print(self.ctx, s); }
49
50 /// Print `s` and a newline.
51 pub fn println(&self, s: &str) { crate::io::println(self.ctx, s); }
52
53 /// Print formatted text.
54 pub fn print_fmt(&self, args: core::fmt::Arguments) { crate::io::print_fmt(self.ctx, args); }
55
56 /// Clear the screen and put the cursor at the top left.
57 pub fn clear(&self) { crate::io::print(self.ctx, "\x1b[2J\x1b[H"); }
58
59 /// A key, if one was typed: does not wait.
60 pub fn key(&self) -> Option<u8> { self.ctx.try_console_read() }
61
62 /// The next key typed: waits for it.
63 pub fn wait_key(&self) -> u8 {
64 loop {
65 if let Some(k) = self.ctx.try_console_read() { return k; }
66 crate::task::sleep_ms(self.ctx, KEY_POLL_MS);
67 }
68 }
69
70 /// Give the console back to the shell and end this program with `status`.
71 pub fn close(self, status: u32) -> ! {
72 self.ctx.console_echo(true);
73 self.ctx.release_console_foreground();
74 crate::program::exit(self.ctx, status)
75 }
76}