feat(display): input routed via display server to focused app; input Subscribe; uses caps badged + Delegate
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)?;
|
||||||
|
|||||||
@@ -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 })
|
||||||
}];
|
}];
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
56
crates/servers/display-server/src/focus.rs
Normal file
56
crates/servers/display-server/src/focus.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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 } => {
|
||||||
|
|||||||
36
crates/servers/display-server/src/tests/focus.rs
Normal file
36
crates/servers/display-server/src/tests/focus.rs
Normal 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]));
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
|
mod focus;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod server;
|
mod server;
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
5
crates/types/src/input.rs
Normal file
5
crates/types/src/input.rs
Normal 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;
|
||||||
@@ -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;
|
||||||
|
|||||||
10
docs/ipc.md
10
docs/ipc.md
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user