feat(display): input routed via display server to focused app; input Subscribe; uses caps badged + Delegate

This commit is contained in:
2026-10-27 16:38:16 +01:00
parent 64ec5df379
commit 2845d190f0
18 changed files with 286 additions and 26 deletions

View File

@@ -12,8 +12,8 @@ use libkos::syscall;
use server_framework::Encode; use server_framework::Encode;
use display_server::protocol::DisplayRequest; use display_server::protocol::DisplayRequest;
const INPUT_EP: CapSlot = CapSlot::const_new(0); /// Input comes through the display server too, while this app has focus.
const DISPLAY_EP: CapSlot = CapSlot::const_new(1); const DISPLAY_EP: CapSlot = CapSlot::const_new(0);
const TICK_DELAY: u64 = 5_000_000; const TICK_DELAY: u64 = 5_000_000;
const MAX_RENDER_CMDS: usize = 140; const MAX_RENDER_CMDS: usize = 140;
@@ -37,6 +37,8 @@ fn flush() {
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn main() { extern "C" fn main() {
// Takes focus; the game polls every tick, so it never waits on the signal.
let _ = libkos::display::subscribe_input(DISPLAY_EP);
let mut snake = Snake::new(); let mut snake = Snake::new();
let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS]; let mut commands = [RenderCommand { x: 0, y: 0, w: 0, h: 0, color: 0 }; MAX_RENDER_CMDS];
@@ -65,7 +67,7 @@ extern "C" fn main() {
fn handle_input(snake: &mut Snake) { fn handle_input(snake: &mut Snake) {
// Drain all pending events, apply the latest direction // Drain all pending events, apply the latest direction
loop { loop {
let Ok([tag, kind, _, _]) = syscall::request(INPUT_EP, [0; 4]) else { let Ok([tag, kind, _, _]) = syscall::request(DISPLAY_EP, [types::display::input::POLL, 0, 0, 0]) else {
break; break;
}; };
if tag == 0 { break; } // NoEvent — queue drained if tag == 0 { break; } // NoEvent — queue drained

View File

@@ -5,25 +5,50 @@ extern crate libkos;
use types::bounded::CapSlot; use types::bounded::CapSlot;
/// Where init put them (see init's manifest: own endpoint, then `uses`).
const EP: CapSlot = CapSlot::const_new(0);
const INPUT: CapSlot = CapSlot::const_new(1);
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn main() { extern "C" fn main() {
let ep = CapSlot::const_new(0);
// Init hands over the GPU, or nothing if the machine has none. // Init hands over the GPU, or nothing if the machine has none.
let dev = libkos::device::receive_device(ep); let dev = libkos::device::receive_device(EP);
let input = subscribe_to_input();
if let Some(fb) = dev.mmio.and_then(driver_framebuffer_virtio::VirtioFramebuffer::try_new) { if let Some(fb) = dev.mmio.and_then(driver_framebuffer_virtio::VirtioFramebuffer::try_new) {
run_virtio(fb, ep); run_virtio(fb, input);
} }
libkos::println!("display: no GPU, running headless"); libkos::println!("display: no GPU, running headless");
run_headless(ep); run_headless(input);
}
/// Has the input server signal this thread whenever events arrive; the
/// server then passes them on to the app in focus.
fn subscribe_to_input() -> Option<CapSlot> {
use types::badge::Badge;
use types::permission::{Permission, PermissionSet};
let notify = libkos::syscall::create_notification().ok()?;
libkos::syscall::bind_notification(notify).ok()?;
let signal = PermissionSet::empty().with(Permission::Write).with(Permission::Delegate);
let copy = libkos::syscall::mint(notify, Badge::NONE, signal).ok()?;
let sent = libkos::syscall::call(INPUT, [types::input::SUBSCRIBE, 0, 0, 0], Some(copy));
libkos::syscall::drop_cap(copy);
sent.ok().map(|_| INPUT)
}
fn server<F: port_framebuffer::Framebuffer>(fb: F, input: Option<CapSlot>) -> display_server::server::DisplayServer<F> {
match input {
Some(input) => display_server::server::DisplayServer::with_input(fb, input),
None => display_server::server::DisplayServer::new(fb),
}
} }
#[inline(never)] #[inline(never)]
fn run_virtio(mut fb: driver_framebuffer_virtio::VirtioFramebuffer, ep: CapSlot) -> ! { fn run_virtio(mut fb: driver_framebuffer_virtio::VirtioFramebuffer, input: Option<CapSlot>) -> ! {
use port_framebuffer::Framebuffer; use port_framebuffer::Framebuffer;
fb.clear(types::display::Color::rgb(30, 30, 60)); fb.clear(types::display::Color::rgb(30, 30, 60));
fb.flush(); fb.flush();
let mut srv = display_server::server::DisplayServer::new(fb); let mut srv = server(fb, input);
server_framework::serve(&mut srv, ep) server_framework::serve(&mut srv, EP)
} }
/// No GPU: clients still get answers (and the dimensions they draw for); /// No GPU: clients still get answers (and the dimensions they draw for);
@@ -52,7 +77,7 @@ impl port_framebuffer::Framebuffer for Headless {
} }
#[inline(never)] #[inline(never)]
fn run_headless(ep: CapSlot) -> ! { fn run_headless(input: Option<CapSlot>) -> ! {
let mut srv = display_server::server::DisplayServer::new(Headless); let mut srv = server(Headless, input);
server_framework::serve(&mut srv, ep) server_framework::serve(&mut srv, EP)
} }

View File

@@ -27,6 +27,8 @@ pub(crate) struct Launcher<'a> {
devices: Devices, devices: Devices,
/// The endpoint of every server started so far, by name. /// The endpoint of every server started so far, by name.
served: [Option<(&'static str, CapSlot)>; MAX_SERVED], served: [Option<(&'static str, CapSlot)>; MAX_SERVED],
/// The badge on the next client's endpoint copy (0 is init's own).
next_badge: usize,
/// Init's own endpoint, for the spawn service. /// Init's own endpoint, for the spawn service.
pub(crate) spawn_ep: CapSlot, pub(crate) spawn_ep: CapSlot,
/// Bound to init's thread: spawned children's deaths, a bit each. /// Bound to init's thread: spawned children's deaths, a bit each.
@@ -42,7 +44,7 @@ impl<'a> Launcher<'a> {
) -> Result<Self, &'static str> { ) -> Result<Self, &'static str> {
let spawn_ep = syscall::create_endpoint().ok_or("no spawn endpoint")?; let spawn_ep = syscall::create_endpoint().ok_or("no spawn endpoint")?;
let deaths = syscall::create_notification().map_err(|_| "no death notification")?; let deaths = syscall::create_notification().map_err(|_| "no death notification")?;
Ok(Self { storage, scratch, devices, served: [None; MAX_SERVED], spawn_ep, deaths, spawner: Spawner::new() }) Ok(Self { storage, scratch, devices, served: [None; MAX_SERVED], next_badge: 1, spawn_ep, deaths, spawner: Spawner::new() })
} }
pub(crate) fn storage(&mut self) -> (&mut Storage, &mut [u8]) { pub(crate) fn storage(&mut self) -> (&mut Storage, &mut [u8]) {
@@ -68,11 +70,18 @@ impl<'a> Launcher<'a> {
} }
false => None, false => None,
}; };
// A client only calls: Execute, never Read, so it cannot pose as the // A client calls (Execute) and may send capabilities through it and
// server and take other clients' requests. // pass it on (Delegate); never Read, so it cannot pose as the server
// and take other clients' requests.
for name in p.uses { for name in p.uses {
let ep = self.endpoint(name).ok_or("needs a server that is not running")?; let ep = self.endpoint(name).ok_or("needs a server that is not running")?;
syscall::delegate(ep, child, perms(&[Permission::Execute])).ok_or("cannot give an endpoint")?; let badge = Badge::new(self.next_badge);
self.next_badge += 1;
let copy = syscall::mint(ep, badge, perms(&[Permission::Execute, Permission::Delegate]))
.map_err(|_| "cannot mint an endpoint")?;
let given = syscall::delegate(copy, child, perms(&[Permission::Execute, Permission::Delegate]));
syscall::drop_cap(copy);
given.ok_or("cannot give an endpoint")?;
} }
if let Some(allowance) = p.spawns { if let Some(allowance) = p.spawns {
self.give_spawn(child, allowance)?; self.give_spawn(child, allowance)?;

View File

@@ -16,7 +16,8 @@ pub struct Program {
/// Serves an endpoint, which it finds in slot 0. /// Serves an endpoint, which it finds in slot 0.
pub serves: bool, pub serves: bool,
pub device: Option<Device>, pub device: Option<Device>,
/// Earlier programs' endpoints it may call, in its next slots. /// Earlier programs' endpoints it may call, in its next slots; each
/// program's copy carries its own badge, so a server can tell them apart.
pub uses: &'static [&'static str], pub uses: &'static [&'static str],
/// Takes the console over from init; everyone else prints through it. /// Takes the console over from init; everyone else prints through it.
pub console: bool, pub console: bool,
@@ -51,12 +52,14 @@ const fn server(name: &'static str, file: &'static [u8], limits: Limits) -> Prog
/// Started on every boot. The log server comes first so the rest can print. /// Started on every boot. The log server comes first so the rest can print.
pub const SERVERS: &[Program] = &[ pub const SERVERS: &[Program] = &[
Program { console: true, ..server("log", b"log.elf", SMALL) }, Program { console: true, ..server("log", b"log.elf", SMALL) },
Program { device: Some(Device::Input), ..server("input", b"input.elf", Limits { regions: 4, ..SMALL }) },
// The input server's only client: it passes input on to the app in focus.
Program { Program {
device: Some(Device::Gpu), device: Some(Device::Gpu),
uses: &["input"],
// The framebuffer alone is ~1000 pages at 1280x800. // The framebuffer alone is ~1000 pages at 1280x800.
..server("display", b"display.elf", Limits { pages: 1280, regions: 4, ..SMALL }) ..server("display", b"display.elf", Limits { pages: 1280, regions: 4, ..SMALL })
}, },
Program { device: Some(Device::Input), ..server("input", b"input.elf", Limits { regions: 4, ..SMALL }) },
server("permis", b"permis.elf", SMALL), server("permis", b"permis.elf", SMALL),
server("session", b"session.elf", SMALL), server("session", b"session.elf", SMALL),
]; ];
@@ -73,6 +76,6 @@ pub const TESTS: &[Program] = &[Program {
/// The desktop; the test image runs the SMP self-test instead. /// The desktop; the test image runs the SMP self-test instead.
pub const APPS: &[Program] = &[Program { pub const APPS: &[Program] = &[Program {
serves: false, serves: false,
uses: &["input", "display"], uses: &["display"],
..server("snake", b"snake.elf", Limits { pages: 128, ..SMALL }) ..server("snake", b"snake.elf", Limits { pages: 128, ..SMALL })
}]; }];

View File

@@ -50,3 +50,23 @@ pub fn draw_text(ep: CapSlot, x: ScreenCoord, y: ScreenCoord, fg: Color, bg: Col
/// The display server's character cell, in pixels. /// The display server's character cell, in pixels.
pub const CELL_WIDTH: usize = 8; pub const CELL_WIDTH: usize = 8;
pub const CELL_HEIGHT: usize = 16; pub const CELL_HEIGHT: usize = 16;
/// Takes input focus: returns a notification signalled whenever events
/// arrive; read them with `poll_input`.
pub fn subscribe_input(ep: CapSlot) -> Option<CapSlot> {
use types::badge::Badge;
use types::permission::{Permission, PermissionSet};
let notify = syscall::create_notification().ok()?;
// Signal only (Delegate just to send it; the transfer strips it).
let signal = PermissionSet::empty().with(Permission::Write).with(Permission::Delegate);
let copy = syscall::mint(notify, Badge::NONE, signal).ok()?;
let sent = syscall::call(ep, [types::display::input::SUBSCRIBE, 0, 0, 0], Some(copy));
syscall::drop_cap(copy);
(sent.ok()?.words()[0] == 0).then_some(notify)
}
/// The next input event, if this process has focus and one is waiting.
pub fn poll_input(ep: CapSlot) -> Option<crate::input::InputEvent> {
let words = syscall::request(ep, [types::display::input::POLL, 0, 0, 0]).ok()?;
crate::input::decode(words)
}

View File

@@ -38,7 +38,11 @@ fn decode_key(kind: usize, extra: usize) -> Option<Key> {
} }
pub fn poll_event(ep: CapSlot) -> Option<InputEvent> { pub fn poll_event(ep: CapSlot) -> Option<InputEvent> {
let [tag, kind, extra, _] = syscall::request(ep, [0; 4]).ok()?; decode(syscall::request(ep, [0; 4]).ok()?)
}
/// An event as the input server words it.
pub fn decode([tag, kind, extra, _]: [usize; 4]) -> Option<InputEvent> {
match tag { match tag {
0 => None, 0 => None,
1 => decode_key(kind, extra).map(InputEvent::KeyDown), 1 => decode_key(kind, extra).map(InputEvent::KeyDown),

View File

@@ -6,6 +6,7 @@ edition = "2024"
[dependencies] [dependencies]
types = { workspace = true } types = { workspace = true }
server-framework = { workspace = true } server-framework = { workspace = true }
libkos = { workspace = true }
port-framebuffer = { workspace = true } port-framebuffer = { workspace = true }
[dev-dependencies] [dev-dependencies]

View File

@@ -0,0 +1,56 @@
//! Who gets input: the display server is the input server's only client
//! and passes events on to the app in focus, the one that subscribed last.
//! Apps out of focus get nothing.
/// Events received and not yet polled by the app in focus.
const QUEUED: usize = 32;
pub struct Focus<N: Copy> {
/// The app in focus: its badge and the notification it gave to be told.
holder: Option<(usize, N)>,
events: [[usize; 4]; QUEUED],
head: usize,
len: usize,
}
impl<N: Copy> Focus<N> {
pub const fn new() -> Self {
Self { holder: None, events: [[0; 4]; QUEUED], head: 0, len: 0 }
}
/// `badge` takes focus; what the last holder had not read is dropped.
pub fn subscribe(&mut self, badge: usize, notify: N) {
self.holder = Some((badge, notify));
self.len = 0;
}
/// Queues an event (as the input server sends it) for the holder;
/// returns whom to tell. With no holder the event goes nowhere.
pub fn push(&mut self, event: [usize; 4]) -> Option<N> {
let (_, notify) = self.holder?;
if self.len == QUEUED {
self.head = (self.head + 1) % QUEUED;
self.len -= 1;
}
self.events[(self.head + self.len) % QUEUED] = event;
self.len += 1;
Some(notify)
}
/// The next event for `badge`, if it is in focus and one is queued.
pub fn poll(&mut self, badge: usize) -> Option<[usize; 4]> {
if self.holder.is_none_or(|(b, _)| b != badge) || self.len == 0 {
return None;
}
let event = self.events[self.head];
self.head = (self.head + 1) % QUEUED;
self.len -= 1;
Some(event)
}
}
impl<N: Copy> Default for Focus<N> {
fn default() -> Self {
Self::new()
}
}

View File

@@ -1,5 +1,6 @@
#![no_std] #![no_std]
pub mod focus;
pub mod font; pub mod font;
pub mod protocol; pub mod protocol;
pub mod server; pub mod server;

View File

@@ -1,19 +1,29 @@
use server_framework::Server; use server_framework::{Decode, Encode, MALFORMED, Server};
use types::bounded::CapSlot;
use crate::protocol::{DisplayRequest, DisplayResponse}; use crate::protocol::{DisplayRequest, DisplayResponse};
use port_framebuffer::Framebuffer; use port_framebuffer::Framebuffer;
use types::display::text::Text; use types::display::text::Text;
use types::display::{Dimension, ScreenCoord}; use types::display::{Dimension, ScreenCoord};
use crate::focus::Focus;
use crate::font; use crate::font;
pub struct DisplayServer<F: Framebuffer> { pub struct DisplayServer<F: Framebuffer> {
fb: F, fb: F,
/// The input server, which signals this server's bound notification.
input: Option<CapSlot>,
focus: Focus<CapSlot>,
} }
impl<F: Framebuffer> DisplayServer<F> { impl<F: Framebuffer> DisplayServer<F> {
pub fn new(fb: F) -> Self { pub fn new(fb: F) -> Self {
Self { fb } Self { fb, input: None, focus: Focus::new() }
}
/// A server that passes `input`'s events on to the app in focus.
pub fn with_input(fb: F, input: CapSlot) -> Self {
Self { input: Some(input), ..Self::new(fb) }
} }
pub fn framebuffer(&self) -> &F { pub fn framebuffer(&self) -> &F {
@@ -47,6 +57,39 @@ impl<F: Framebuffer> Server for DisplayServer<F> {
type Request = DisplayRequest; type Request = DisplayRequest;
type Response = DisplayResponse; type Response = DisplayResponse;
fn handle_call(&mut self, badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<CapSlot>) {
use types::display::input::{POLL, SUBSCRIBE};
match (words[0], cap) {
(SUBSCRIBE, Some(notify)) => {
self.focus.subscribe(badge, notify);
(DisplayResponse::Ok.encode(), None)
}
(SUBSCRIBE, None) => (DisplayResponse::Error.encode(), None),
(POLL, _) => (self.focus.poll(badge).unwrap_or([0; 4]), None),
_ => match DisplayRequest::decode(words) {
Some(request) => (self.handle(request).encode(), None),
None => ([MALFORMED, 0, 0, 0], None),
},
}
}
/// The input server has events: take them all, tell the app in focus.
fn signal(&mut self, _bits: usize) {
let Some(input) = self.input else {
return;
};
let mut told = None;
while let Ok(event) = libkos::syscall::request(input, [types::input::POLL_EVENT, 0, 0, 0]) {
if event[0] == 0 {
break;
}
told = self.focus.push(event).or(told);
}
if let Some(notify) = told {
let _ = libkos::syscall::signal(notify);
}
}
fn handle(&mut self, request: DisplayRequest) -> DisplayResponse { fn handle(&mut self, request: DisplayRequest) -> DisplayResponse {
match request { match request {
DisplayRequest::DrawRect { x, y, w, h, color } => { DisplayRequest::DrawRect { x, y, w, h, color } => {

View File

@@ -0,0 +1,36 @@
use crate::focus::Focus;
const KEY: [usize; 4] = [1, 9, b'a' as usize, 0];
#[test]
fn only_the_app_in_focus_gets_events() {
let mut f: Focus<u8> = Focus::new();
assert_eq!(f.push(KEY), None, "nobody to give it to");
f.subscribe(1, 10);
assert_eq!(f.push(KEY), Some(10));
assert_eq!(f.poll(2), None);
assert_eq!(f.poll(1), Some(KEY));
assert_eq!(f.poll(1), None);
}
#[test]
fn a_new_subscriber_takes_focus_and_the_old_queue_is_dropped() {
let mut f: Focus<u8> = Focus::new();
f.subscribe(1, 10);
f.push(KEY);
f.subscribe(2, 20);
assert_eq!(f.poll(1), None);
assert_eq!(f.poll(2), None);
assert_eq!(f.push(KEY), Some(20));
assert_eq!(f.poll(2), Some(KEY));
}
#[test]
fn a_full_queue_drops_the_oldest() {
let mut f: Focus<u8> = Focus::new();
f.subscribe(1, 10);
for i in 0..40 {
f.push([1, 9, i, 0]);
}
assert_eq!(f.poll(1), Some([1, 9, 8, 0]));
}

View File

@@ -1,2 +1,3 @@
mod focus;
mod protocol; mod protocol;
mod server; mod server;

View File

@@ -1,4 +1,4 @@
use server_framework::Server; use server_framework::{Decode, Encode, MALFORMED, Server};
use port_input_device::{InputDevice, InputEvent}; use port_input_device::{InputDevice, InputEvent};
use types::bounded::CapSlot; use types::bounded::CapSlot;
@@ -14,12 +14,14 @@ pub struct InputServer<D: InputDevice> {
events: [Option<InputEvent>; BUFFERED], events: [Option<InputEvent>; BUFFERED],
head: usize, head: usize,
len: usize, len: usize,
/// Signalled when events arrive (the display server's notification).
subscriber: Option<CapSlot>,
} }
impl<D: InputDevice> InputServer<D> { impl<D: InputDevice> InputServer<D> {
/// A server that reads the device only when a client asks. /// A server that reads the device only when a client asks.
pub fn new(device: D) -> Self { pub fn new(device: D) -> Self {
Self { device, irq: None, events: [None; BUFFERED], head: 0, len: 0 } Self { device, irq: None, events: [None; BUFFERED], head: 0, len: 0, subscriber: None }
} }
/// A server woken by `irq` (routed and bound by the caller): it drains /// A server woken by `irq` (routed and bound by the caller): it drains
@@ -61,6 +63,10 @@ impl<D: InputDevice> InputServer<D> {
&self.device &self.device
} }
pub fn subscriber(&self) -> Option<CapSlot> {
self.subscriber
}
#[cfg(test)] #[cfg(test)]
pub fn replace_device_for_test(&mut self, device: D) { pub fn replace_device_for_test(&mut self, device: D) {
self.device = device; self.device = device;
@@ -82,9 +88,25 @@ impl<D: InputDevice> Server for InputServer<D> {
} }
} }
fn handle_call(&mut self, _badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<CapSlot>) {
if words[0] == types::input::SUBSCRIBE {
self.subscriber = cap;
return ([0; 4], None);
}
match InputRequest::decode(words) {
Some(request) => (self.handle(request).encode(), None),
None => ([MALFORMED, 0, 0, 0], None),
}
}
fn signal(&mut self, _bits: usize) { fn signal(&mut self, _bits: usize) {
let mut arrived = false;
while let Some(event) = self.device.poll() { while let Some(event) = self.device.poll() {
self.push(event); self.push(event);
arrived = true;
}
if let (true, Some(subscriber)) = (arrived, self.subscriber) {
let _ = libkos::syscall::signal(subscriber);
} }
self.device.acknowledge(); self.device.acknowledge();
if let Some(irq) = self.irq { if let Some(irq) = self.irq {

View File

@@ -1,7 +1,7 @@
use crate::adapters::mock::MockInputDevice; use crate::adapters::mock::MockInputDevice;
use crate::protocol::*; use crate::protocol::*;
use crate::server::InputServer; use crate::server::InputServer;
use server_framework::Server; use server_framework::{Decode, Encode, Server};
fn test_server() -> InputServer<MockInputDevice> { fn test_server() -> InputServer<MockInputDevice> {
InputServer::new(MockInputDevice::new()) InputServer::new(MockInputDevice::new())
@@ -85,3 +85,14 @@ fn a_full_buffer_drops_the_oldest_event() {
InputResponse::Event(InputEvent::KeyDown(Key::Char(8))) InputResponse::Event(InputEvent::KeyDown(Key::Char(8)))
); );
} }
#[test]
fn a_subscription_is_kept_and_other_requests_still_work() {
let mut server = test_server();
let note = types::bounded::CapSlot::new(7);
let (reply, _) = server.handle_call(0, [types::input::SUBSCRIBE, 0, 0, 0], note);
assert_eq!(reply, [0; 4]);
assert_eq!(server.subscriber(), note);
let (reply, _) = server.handle_call(0, InputRequest::PollEvent.encode(), None);
assert_eq!(InputResponse::decode(reply), Some(InputResponse::NoEvent));
}

View File

@@ -123,3 +123,13 @@ pub mod text {
} }
} }
} }
/// Input through the display server: an app subscribes with a notification
/// and is then the one keys go to (focus); when it is signalled it polls.
/// A poll's reply is an input event exactly as the input server sends it
/// (tag 0: no event).
pub mod input {
/// Carries the notification to signal; the subscriber takes focus.
pub const SUBSCRIBE: usize = 5;
pub const POLL: usize = 6;
}

View File

@@ -0,0 +1,5 @@
//! Input server requests other crates need to name.
pub const POLL_EVENT: usize = 0;
/// Carries a notification the server signals whenever new events arrive.
pub const SUBSCRIBE: usize = 2;

View File

@@ -8,6 +8,7 @@ pub mod budget;
pub mod display; pub mod display;
pub mod generation; pub mod generation;
pub mod id; pub mod id;
pub mod input;
pub mod log; pub mod log;
pub mod macros; pub mod macros;
pub mod mapping; pub mod mapping;

View File

@@ -84,6 +84,16 @@ and budget listed there. Storage, started before the log server, receives
its log endpoint from init later (`SET_LOG`, accepted only from the its log endpoint from init later (`SET_LOG`, accepted only from the
unbadged holder of its endpoint). unbadged holder of its endpoint).
A program's `uses` endpoints arrive as badged copies with `Execute` and
`Delegate`: the badge (unique per copy) tells the server which client is
calling, and `Delegate` lets the client send capabilities with its calls
and hand its access to programs it starts.
Input reaches apps through the display server, the input server's only
client. It subscribes to the input server with a notification, drains it
when signalled, and passes events on to the app in focus: the last one to
subscribe (`types::display::input`). Apps out of focus get nothing.
Only init holds `ProcessCreate`. Everyone else runs programs through init's Only init holds `ProcessCreate`. Everyone else runs programs through init's
spawn service (`types::spawn`, client `libkos::spawn`), which init serves spawn service (`types::spawn`, client `libkos::spawn`), which init serves
once boot is done. A manifest entry with `spawns` gets a badged copy of once boot is done. A manifest entry with `spawns` gets a badged copy of