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

@@ -6,6 +6,7 @@ edition = "2024"
[dependencies]
types = { workspace = true }
server-framework = { workspace = true }
libkos = { workspace = true }
port-framebuffer = { workspace = true }
[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]
pub mod focus;
pub mod font;
pub mod protocol;
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 port_framebuffer::Framebuffer;
use types::display::text::Text;
use types::display::{Dimension, ScreenCoord};
use crate::focus::Focus;
use crate::font;
pub struct DisplayServer<F: Framebuffer> {
fb: F,
/// The input server, which signals this server's bound notification.
input: Option<CapSlot>,
focus: Focus<CapSlot>,
}
impl<F: Framebuffer> DisplayServer<F> {
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 {
@@ -47,6 +57,39 @@ impl<F: Framebuffer> Server for DisplayServer<F> {
type Request = DisplayRequest;
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 {
match request {
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 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 types::bounded::CapSlot;
@@ -14,12 +14,14 @@ pub struct InputServer<D: InputDevice> {
events: [Option<InputEvent>; BUFFERED],
head: usize,
len: usize,
/// Signalled when events arrive (the display server's notification).
subscriber: Option<CapSlot>,
}
impl<D: InputDevice> InputServer<D> {
/// A server that reads the device only when a client asks.
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
@@ -61,6 +63,10 @@ impl<D: InputDevice> InputServer<D> {
&self.device
}
pub fn subscriber(&self) -> Option<CapSlot> {
self.subscriber
}
#[cfg(test)]
pub fn replace_device_for_test(&mut self, device: D) {
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) {
let mut arrived = false;
while let Some(event) = self.device.poll() {
self.push(event);
arrived = true;
}
if let (true, Some(subscriber)) = (arrived, self.subscriber) {
let _ = libkos::syscall::signal(subscriber);
}
self.device.acknowledge();
if let Some(irq) = self.irq {

View File

@@ -1,7 +1,7 @@
use crate::adapters::mock::MockInputDevice;
use crate::protocol::*;
use crate::server::InputServer;
use server_framework::Server;
use server_framework::{Decode, Encode, Server};
fn test_server() -> InputServer<MockInputDevice> {
InputServer::new(MockInputDevice::new())
@@ -85,3 +85,14 @@ fn a_full_buffer_drops_the_oldest_event() {
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));
}