feat(display): windows: full-screen, F1 cycles, OS bar (names, front, user); canvas-only (display-owned buffers, 4 max, reused); launchers OPEN (pm per app, terminal own), window caps minted by display; FORGET on launcher death via init courier; raster crate (font, chunked text); F-keys in input; terminal+snake on canvas; REDRAW gone
This commit is contained in:
@@ -8,6 +8,7 @@ types = { workspace = true }
|
||||
server-framework = { workspace = true }
|
||||
libkos = { workspace = true }
|
||||
port-framebuffer = { workspace = true }
|
||||
raster = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
driver-framebuffer-mock = { workspace = true }
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
//! 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,24 +0,0 @@
|
||||
Copyright (c) 2018-2024, Frederic Cambus
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,114 +0,0 @@
|
||||
//! The display server's one font: Spleen 8x16 (BSD-2-Clause, Frederic
|
||||
//! Cambus; see LICENSE-spleen), printable ASCII only. Generated from
|
||||
//! spleen-8x16.bdf; each glyph is 16 rows, bit 7 the leftmost pixel.
|
||||
|
||||
pub const WIDTH: u16 = 8;
|
||||
pub const HEIGHT: u16 = 16;
|
||||
|
||||
const FIRST: u8 = b' ';
|
||||
const LAST: u8 = b'~';
|
||||
|
||||
/// The glyph for `c`; anything unprintable shows as `?`.
|
||||
pub fn glyph(c: u8) -> &'static [u8; 16] {
|
||||
let c = if (FIRST..=LAST).contains(&c) { c } else { b'?' };
|
||||
&GLYPHS[usize::from(c - FIRST)]
|
||||
}
|
||||
|
||||
#[rustfmt::skip]
|
||||
static GLYPHS: [[u8; 16]; 95] = [
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // ' '
|
||||
[0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00], // '!'
|
||||
[0x00, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '"'
|
||||
[0x00, 0x00, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x6c, 0x6c, 0xfe, 0x6c, 0x6c, 0x00, 0x00, 0x00, 0x00], // '#'
|
||||
[0x00, 0x10, 0x7e, 0xd0, 0xd0, 0xd0, 0x7c, 0x16, 0x16, 0x16, 0x16, 0xfc, 0x10, 0x00, 0x00, 0x00], // '$'
|
||||
[0x00, 0x00, 0x06, 0x66, 0x6c, 0x0c, 0x18, 0x18, 0x30, 0x36, 0x66, 0x60, 0x00, 0x00, 0x00, 0x00], // '%'
|
||||
[0x00, 0x00, 0x38, 0x6c, 0x6c, 0x6c, 0x38, 0x70, 0xda, 0xcc, 0xcc, 0x7a, 0x00, 0x00, 0x00, 0x00], // '&'
|
||||
[0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // "'"
|
||||
[0x00, 0x0e, 0x18, 0x30, 0x30, 0x60, 0x60, 0x60, 0x60, 0x30, 0x30, 0x18, 0x0e, 0x00, 0x00, 0x00], // '('
|
||||
[0x00, 0x70, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x70, 0x00, 0x00, 0x00], // ')'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x66, 0x3c, 0x18, 0xff, 0x18, 0x3c, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00], // '*'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '+'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00], // ','
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '-'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00], // '.'
|
||||
[0x00, 0x06, 0x06, 0x0c, 0x0c, 0x18, 0x18, 0x30, 0x30, 0x60, 0x60, 0xc0, 0xc0, 0x00, 0x00, 0x00], // '/'
|
||||
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xce, 0xde, 0xf6, 0xe6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '0'
|
||||
[0x00, 0x00, 0x18, 0x38, 0x78, 0x58, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00], // '1'
|
||||
[0x00, 0x00, 0x7c, 0xc6, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc6, 0xfe, 0x00, 0x00, 0x00, 0x00], // '2'
|
||||
[0x00, 0x00, 0x7c, 0xc6, 0x06, 0x06, 0x3c, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '3'
|
||||
[0x00, 0x00, 0xc0, 0xc0, 0xcc, 0xcc, 0xcc, 0xcc, 0xfe, 0x0c, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x00], // '4'
|
||||
[0x00, 0x00, 0xfe, 0xc6, 0xc0, 0xc0, 0xfc, 0x06, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '5'
|
||||
[0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '6'
|
||||
[0x00, 0x00, 0xfe, 0xc6, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00], // '7'
|
||||
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '8'
|
||||
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // '9'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00], // ':'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00], // ';'
|
||||
[0x00, 0x00, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x00, 0x00, 0x00, 0x00], // '<'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '='
|
||||
[0x00, 0x00, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00], // '>'
|
||||
[0x00, 0x00, 0x7c, 0xc6, 0x06, 0x0c, 0x18, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00], // '?'
|
||||
[0x00, 0x00, 0x00, 0x7c, 0xc2, 0xda, 0xda, 0xda, 0xda, 0xde, 0xc0, 0x7c, 0x00, 0x00, 0x00, 0x00], // '@'
|
||||
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'A'
|
||||
[0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00], // 'B'
|
||||
[0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'C'
|
||||
[0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00], // 'D'
|
||||
[0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0xf8, 0xc0, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'E'
|
||||
[0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0xf8, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00], // 'F'
|
||||
[0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0xde, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'G'
|
||||
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'H'
|
||||
[0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'I'
|
||||
[0x00, 0x00, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xf0, 0x00, 0x00, 0x00, 0x00], // 'J'
|
||||
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xcc, 0xf8, 0xcc, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'K'
|
||||
[0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'L'
|
||||
[0x00, 0x00, 0xc6, 0xee, 0xfe, 0xd6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'M'
|
||||
[0x00, 0x00, 0xc6, 0xc6, 0xe6, 0xe6, 0xd6, 0xd6, 0xce, 0xce, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'N'
|
||||
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // 'O'
|
||||
[0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xfc, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00], // 'P'
|
||||
[0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xd6, 0x7c, 0x18, 0x0c, 0x00, 0x00], // 'Q'
|
||||
[0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'R'
|
||||
[0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0x7c, 0x06, 0x06, 0x06, 0x06, 0xfc, 0x00, 0x00, 0x00, 0x00], // 'S'
|
||||
[0x00, 0x00, 0xff, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00], // 'T'
|
||||
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'U'
|
||||
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00], // 'V'
|
||||
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xd6, 0xfe, 0xee, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'W'
|
||||
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'X'
|
||||
[0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x06, 0x06, 0xfc, 0x00, 0x00, 0x00, 0x00], // 'Y'
|
||||
[0x00, 0x00, 0xfe, 0x06, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xc0, 0xfe, 0x00, 0x00, 0x00, 0x00], // 'Z'
|
||||
[0x00, 0x3e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3e, 0x00, 0x00, 0x00], // '['
|
||||
[0x00, 0xc0, 0xc0, 0x60, 0x60, 0x30, 0x30, 0x18, 0x18, 0x0c, 0x0c, 0x06, 0x06, 0x00, 0x00, 0x00], // '\\'
|
||||
[0x00, 0x7c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x7c, 0x00, 0x00, 0x00], // ']'
|
||||
[0x00, 0x10, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '^'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00], // '_'
|
||||
[0x00, 0x30, 0x18, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '`'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x06, 0x7e, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'a'
|
||||
[0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0x00, 0x00, 0x00, 0x00], // 'b'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'c'
|
||||
[0x00, 0x00, 0x06, 0x06, 0x06, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'd'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xfe, 0xc0, 0xc0, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'e'
|
||||
[0x00, 0x00, 0x1e, 0x30, 0x30, 0x30, 0x7c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00], // 'f'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x06, 0x06, 0xfc, 0x00], // 'g'
|
||||
[0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'h'
|
||||
[0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1c, 0x00, 0x00, 0x00, 0x00], // 'i'
|
||||
[0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x70, 0x00], // 'j'
|
||||
[0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xcc, 0xd8, 0xf0, 0xf0, 0xd8, 0xcc, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'k'
|
||||
[0x00, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1c, 0x00, 0x00, 0x00, 0x00], // 'l'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0xd6, 0xd6, 0xd6, 0xd6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'm'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'n'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00, 0x00, 0x00, 0x00], // 'o'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xfc, 0xc0, 0xc0, 0xc0, 0x00], // 'p'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0x06, 0x00], // 'q'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc6, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x00, 0x00], // 'r'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x7e, 0xc0, 0xc0, 0x7c, 0x06, 0x06, 0xfc, 0x00, 0x00, 0x00, 0x00], // 's'
|
||||
[0x00, 0x00, 0x30, 0x30, 0x30, 0x7c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x1e, 0x00, 0x00, 0x00, 0x00], // 't'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00, 0x00, 0x00, 0x00], // 'u'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00], // 'v'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xd6, 0xd6, 0xd6, 0x6e, 0x00, 0x00, 0x00, 0x00], // 'w'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0x6c, 0x38, 0x38, 0x6c, 0xc6, 0xc6, 0x00, 0x00, 0x00, 0x00], // 'x'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x06, 0x06, 0xfc, 0x00], // 'y'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xfe, 0x00, 0x00, 0x00, 0x00], // 'z'
|
||||
[0x00, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x70, 0x70, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x00, 0x00, 0x00], // '{'
|
||||
[0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00], // '|'
|
||||
[0x00, 0x70, 0x18, 0x18, 0x18, 0x18, 0x0e, 0x0e, 0x18, 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00], // '}'
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x7e, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], // '~'
|
||||
];
|
||||
99
crates/servers/display-server/src/host.rs
Normal file
99
crates/servers/display-server/src/host.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
//! `Host` on the kernel: window buffers are regions of the display's own,
|
||||
//! made once per slot and reused (a region lives as long as its owner), so
|
||||
//! the display's memory has a fixed ceiling whatever apps do.
|
||||
|
||||
use libkos::syscall;
|
||||
use libkos::window::Window;
|
||||
use types::address::{ByteOffset, VirtualAddress};
|
||||
use types::badge::Badge;
|
||||
use types::bounded::{CapSlot, PageCount};
|
||||
use types::display::Color;
|
||||
use types::display::window::PIXEL_BYTES;
|
||||
use types::mapping::PagePerms;
|
||||
use types::permission::{Permission, PermissionSet};
|
||||
|
||||
use crate::server::Host;
|
||||
use crate::windows::MAX_WINDOWS;
|
||||
|
||||
/// Where slot `i`'s buffer is mapped in the display: 4 MiB apart, room for
|
||||
/// a 1280x800 window.
|
||||
const BUFFERS_VA: usize = 0x0080_0000;
|
||||
const BUFFER_SPAN: usize = 0x0040_0000;
|
||||
const PAGE: usize = 4096;
|
||||
|
||||
pub struct KernelHost {
|
||||
/// The display's own endpoint, which window capabilities are minted
|
||||
/// from.
|
||||
ep: CapSlot,
|
||||
input: Option<CapSlot>,
|
||||
buffers: [Option<(CapSlot, Window)>; MAX_WINDOWS],
|
||||
}
|
||||
|
||||
impl KernelHost {
|
||||
pub fn new(ep: CapSlot, input: Option<CapSlot>) -> Self {
|
||||
Self { ep, input, buffers: [None; MAX_WINDOWS] }
|
||||
}
|
||||
}
|
||||
|
||||
impl Host for KernelHost {
|
||||
fn buffer(&mut self, index: usize, pixels: usize) -> Option<CapSlot> {
|
||||
let slot = self.buffers.get_mut(index)?;
|
||||
if slot.is_none() {
|
||||
let bytes = (pixels * PIXEL_BYTES).min(BUFFER_SPAN);
|
||||
let pages = PageCount::new(u16::try_from(bytes.div_ceil(PAGE)).ok()?)?;
|
||||
let region = syscall::create_region(pages).ok()?;
|
||||
let va = VirtualAddress::from_raw(BUFFERS_VA + index * BUFFER_SPAN);
|
||||
match libkos::window::map(region, va, PagePerms::ReadWrite) {
|
||||
Ok(window) => *slot = Some((region, window)),
|
||||
Err(_) => {
|
||||
syscall::drop_cap(region);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
slot.as_ref().map(|(region, _)| *region)
|
||||
}
|
||||
|
||||
fn blank(&mut self, index: usize) {
|
||||
if let Some((_, window)) = self.buffers.get(index).and_then(Option::as_ref) {
|
||||
window.fill_u32(ByteOffset::ZERO, window.len() / PIXEL_BYTES, 0);
|
||||
}
|
||||
}
|
||||
|
||||
fn read(&self, index: usize, from: usize, out: &mut [Color]) {
|
||||
let Some((_, window)) = self.buffers.get(index).and_then(Option::as_ref) else { return };
|
||||
for (i, px) in out.iter_mut().enumerate() {
|
||||
let at = (from + i) * PIXEL_BYTES;
|
||||
if at + PIXEL_BYTES > window.len() {
|
||||
return;
|
||||
}
|
||||
*px = Color::from_raw(window.read::<u32>(ByteOffset::new(at)));
|
||||
}
|
||||
}
|
||||
|
||||
fn reclaim(&mut self, index: usize) {
|
||||
if let Some((region, _)) = self.buffers.get(index).and_then(Option::as_ref) {
|
||||
// Every copy handed out goes, and with it every mapping of one.
|
||||
let _ = syscall::revoke(*region);
|
||||
}
|
||||
}
|
||||
|
||||
fn mint(&mut self, badge: usize) -> Option<CapSlot> {
|
||||
let perms = PermissionSet::empty().with(Permission::Execute).with(Permission::Delegate);
|
||||
syscall::mint(self.ep, Badge::new(badge), perms).ok()
|
||||
}
|
||||
|
||||
fn revoke(&mut self, cap: CapSlot) {
|
||||
let _ = syscall::revoke(cap);
|
||||
syscall::drop_cap(cap);
|
||||
}
|
||||
|
||||
fn signal(&mut self, notify: CapSlot) {
|
||||
let _ = syscall::signal(notify);
|
||||
}
|
||||
|
||||
fn input(&mut self) -> Option<[usize; 4]> {
|
||||
let event = syscall::request(self.input?, [types::input::POLL_EVENT, 0, 0, 0]).ok()?;
|
||||
(event[0] != 0).then_some(event)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
#![no_std]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod focus;
|
||||
pub mod font;
|
||||
pub mod protocol;
|
||||
pub mod host;
|
||||
pub mod server;
|
||||
pub mod windows;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "tests/mod.rs"]
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
use server_framework::{Decode, Encode};
|
||||
use types::display::text::Text;
|
||||
use types::display::{Color, Dimension, ScreenCoord};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DisplayRequest {
|
||||
DrawRect { x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color },
|
||||
Clear { color: Color },
|
||||
Flush,
|
||||
GetDimensions,
|
||||
DrawText(Text),
|
||||
/// Moves the rows from `top`, `height` tall, up by `by`; the rows left
|
||||
/// at the bottom are filled with `fill`.
|
||||
Scroll { top: ScreenCoord, height: Dimension, by: Dimension, fill: Color },
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum DisplayResponse {
|
||||
Ok,
|
||||
Dimensions { width: Dimension, height: Dimension },
|
||||
Error,
|
||||
}
|
||||
|
||||
impl Encode for DisplayRequest {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
match self {
|
||||
DisplayRequest::DrawRect { x, y, w, h, color } => {
|
||||
let xy = (x.raw() as usize) | ((y.raw() as usize) << 16);
|
||||
let wh = (w.raw() as usize) | ((h.raw() as usize) << 16);
|
||||
[0, xy, wh, color.raw() as usize]
|
||||
}
|
||||
DisplayRequest::Clear { color } => [1, color.raw() as usize, 0, 0],
|
||||
DisplayRequest::Flush => [2, 0, 0, 0],
|
||||
DisplayRequest::GetDimensions => [3, 0, 0, 0],
|
||||
DisplayRequest::DrawText(t) => t.words(),
|
||||
DisplayRequest::Scroll { top, height, by, fill } => {
|
||||
let band = (top.raw() as usize) | ((height.raw() as usize) << 16);
|
||||
[types::display::SCROLL, band, by.raw() as usize, fill.raw() as usize]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for DisplayRequest {
|
||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||
match words[0] {
|
||||
0 => {
|
||||
let x = ScreenCoord::new((words[1] & 0xFFFF) as u16);
|
||||
let y = ScreenCoord::new(((words[1] >> 16) & 0xFFFF) as u16);
|
||||
let w = Dimension::new((words[2] & 0xFFFF) as u16);
|
||||
let h = Dimension::new(((words[2] >> 16) & 0xFFFF) as u16);
|
||||
let color = Color::from_raw(words[3] as u32);
|
||||
Some(DisplayRequest::DrawRect { x, y, w, h, color })
|
||||
}
|
||||
1 => Some(DisplayRequest::Clear { color: Color::from_raw(words[1] as u32) }),
|
||||
2 => Some(DisplayRequest::Flush),
|
||||
3 => Some(DisplayRequest::GetDimensions),
|
||||
types::display::SCROLL => Some(DisplayRequest::Scroll {
|
||||
top: ScreenCoord::new((words[1] & 0xFFFF) as u16),
|
||||
height: Dimension::new(((words[1] >> 16) & 0xFFFF) as u16),
|
||||
by: Dimension::new((words[2] & 0xFFFF) as u16),
|
||||
fill: Color::from_raw(words[3] as u32),
|
||||
}),
|
||||
_ => Text::from_words(words).map(DisplayRequest::DrawText),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for DisplayResponse {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
match self {
|
||||
DisplayResponse::Ok => [0, 0, 0, 0],
|
||||
DisplayResponse::Dimensions { width, height } => {
|
||||
[1, width.raw() as usize, height.raw() as usize, 0]
|
||||
}
|
||||
DisplayResponse::Error => [2, 0, 0, 0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decode for DisplayResponse {
|
||||
fn decode(words: [usize; 4]) -> Option<Self> {
|
||||
match words[0] {
|
||||
0 => Some(DisplayResponse::Ok),
|
||||
1 => Some(DisplayResponse::Dimensions {
|
||||
width: Dimension::new(words[1] as u16),
|
||||
height: Dimension::new(words[2] as u16),
|
||||
}),
|
||||
2 => Some(DisplayResponse::Error),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +1,285 @@
|
||||
//! The display server (see the vision's Display System note): it alone
|
||||
//! holds the screen. It draws the OS bar along the top (the windows, which
|
||||
//! one is in front, who is signed in) and shows the window in front below
|
||||
//! it, copied from that window's pixel buffer as its app presents changes.
|
||||
//! Apps draw only into their own buffer; the bar is the display's, so no
|
||||
//! app can pose as another. The kernel side (buffers, capabilities, the
|
||||
//! input server) is behind `Host`.
|
||||
|
||||
use libkos::syscall::Give;
|
||||
use port_framebuffer::Framebuffer;
|
||||
use raster::font;
|
||||
use server_framework::{Decode, Encode, MALFORMED, Server};
|
||||
use types::bounded::CapSlot;
|
||||
use types::display::window::{self as w, NAME_BYTES};
|
||||
use types::display::{Color, Dimension, ScreenCoord};
|
||||
|
||||
use crate::protocol::{DisplayRequest, DisplayResponse};
|
||||
use port_framebuffer::Framebuffer;
|
||||
use types::display::text::{TEXT_BYTES, Text};
|
||||
use types::display::{Dimension, ScreenCoord};
|
||||
use crate::windows::{MAX_WINDOWS, Windows};
|
||||
|
||||
use crate::focus::Focus;
|
||||
use crate::font;
|
||||
/// The OS bar: one row of text.
|
||||
pub const BAR_HEIGHT: u16 = font::HEIGHT;
|
||||
const BAR_BG: Color = Color::rgb(24, 24, 40);
|
||||
const BAR_FG: Color = Color::rgb(170, 170, 190);
|
||||
const FRONT_BG: Color = Color::rgb(90, 90, 150);
|
||||
const FRONT_FG: Color = Color::rgb(255, 255, 255);
|
||||
/// Pixels copied per row at a time.
|
||||
const RUN: usize = 256;
|
||||
|
||||
pub struct DisplayServer<F: Framebuffer> {
|
||||
fb: F,
|
||||
/// The input server, which signals this server's bound notification.
|
||||
input: Option<CapSlot>,
|
||||
focus: Focus<CapSlot>,
|
||||
/// What the display needs from the kernel.
|
||||
pub trait Host {
|
||||
/// Window slot `index`'s pixel buffer of `pixels` pixels, made the
|
||||
/// first time; the capability its window's holder maps.
|
||||
fn buffer(&mut self, index: usize, pixels: usize) -> Option<CapSlot>;
|
||||
/// Blanks slot `index`'s buffer, so a new window shows nothing of the
|
||||
/// last one's.
|
||||
fn blank(&mut self, index: usize);
|
||||
/// Pixels of slot `index`'s buffer from pixel `from` on, into `out`.
|
||||
fn read(&self, index: usize, from: usize, out: &mut [Color]);
|
||||
/// Takes back every copy of slot `index`'s buffer handed out.
|
||||
fn reclaim(&mut self, index: usize);
|
||||
/// A copy of the display's endpoint carrying `badge`.
|
||||
fn mint(&mut self, badge: usize) -> Option<CapSlot>;
|
||||
/// Deletes `cap` and every copy made from it.
|
||||
fn revoke(&mut self, cap: CapSlot);
|
||||
fn signal(&mut self, notify: CapSlot);
|
||||
/// The input server's next event, if one is waiting.
|
||||
fn input(&mut self) -> Option<[usize; 4]>;
|
||||
}
|
||||
|
||||
impl<F: Framebuffer> DisplayServer<F> {
|
||||
pub fn new(fb: F) -> Self {
|
||||
Self { fb, input: None, focus: Focus::new() }
|
||||
}
|
||||
/// Nothing goes through `handle`: every request needs its badge.
|
||||
pub struct Never;
|
||||
|
||||
/// 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) }
|
||||
impl Decode for Never {
|
||||
fn decode(_: [usize; 4]) -> Option<Self> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Encode for Never {
|
||||
fn encode(&self) -> [usize; 4] {
|
||||
[MALFORMED, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DisplayServer<F: Framebuffer, H: Host> {
|
||||
fb: F,
|
||||
host: H,
|
||||
windows: Windows<CapSlot>,
|
||||
/// Each open window's capability, as minted.
|
||||
caps: [Option<CapSlot>; MAX_WINDOWS],
|
||||
user: [u8; NAME_BYTES],
|
||||
user_len: usize,
|
||||
}
|
||||
|
||||
impl<F: Framebuffer, H: Host> DisplayServer<F, H> {
|
||||
pub fn new(fb: F, host: H) -> Self {
|
||||
let mut s = Self { fb, host, windows: Windows::new(), caps: [None; MAX_WINDOWS], user: [0; NAME_BYTES], user_len: 0 };
|
||||
s.repaint();
|
||||
s
|
||||
}
|
||||
|
||||
pub fn framebuffer(&self) -> &F {
|
||||
&self.fb
|
||||
}
|
||||
|
||||
/// Draws each cell whole, background too, so text overwrites what was
|
||||
/// there; cells past the screen edge are skipped.
|
||||
fn draw_text(&mut self, t: &Text) {
|
||||
let (fw, fh) = (self.fb.width().raw(), self.fb.height().raw());
|
||||
let (x0, y0) = (t.x.raw(), t.y.raw());
|
||||
if y0.saturating_add(font::HEIGHT) > fh {
|
||||
pub fn host(&self) -> &H {
|
||||
&self.host
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn host_mut_for_test(&mut self) -> &mut H {
|
||||
&mut self.host
|
||||
}
|
||||
|
||||
/// A window's size: the screen below the bar.
|
||||
fn window_size(&self) -> (Dimension, Dimension) {
|
||||
(self.fb.width(), Dimension::new(self.fb.height().raw().saturating_sub(BAR_HEIGHT)))
|
||||
}
|
||||
|
||||
fn pixels(&self) -> usize {
|
||||
let (width, height) = self.window_size();
|
||||
width.raw() as usize * height.raw() as usize
|
||||
}
|
||||
|
||||
fn open(&mut self, opener: usize, name: &[u8]) -> ([usize; 4], Option<Give>) {
|
||||
let Some((index, badge)) = self.windows.open(opener, name) else {
|
||||
return ([MALFORMED, 0, 0, 0], None);
|
||||
};
|
||||
let pixels = self.pixels();
|
||||
let made = self.host.buffer(index, pixels).is_some();
|
||||
let cap = made.then(|| self.host.mint(badge)).flatten();
|
||||
let Some(cap) = cap else {
|
||||
self.windows.close(index);
|
||||
return ([MALFORMED, 0, 0, 0], None);
|
||||
};
|
||||
self.host.blank(index);
|
||||
self.caps[index] = Some(cap);
|
||||
self.repaint();
|
||||
let (width, height) = self.window_size();
|
||||
// The launcher passes it on to the app.
|
||||
([w::OK, width.raw() as usize, height.raw() as usize, 0], Some(Give::keeping_delegate(cap)))
|
||||
}
|
||||
|
||||
fn close(&mut self, index: usize) {
|
||||
if let Some(cap) = self.caps.get_mut(index).and_then(Option::take) {
|
||||
self.host.revoke(cap);
|
||||
}
|
||||
self.host.reclaim(index);
|
||||
self.windows.close(index);
|
||||
self.repaint();
|
||||
}
|
||||
|
||||
/// Copies a rectangle of window `index` to the screen, if it is in
|
||||
/// front; clipped to the window.
|
||||
fn present(&mut self, index: usize, x: ScreenCoord, y: ScreenCoord, width: Dimension, height: Dimension) {
|
||||
if self.windows.front() != Some(index) {
|
||||
return;
|
||||
}
|
||||
// Whole cells only.
|
||||
let fit = (fw.saturating_sub(x0) / font::WIDTH) as usize;
|
||||
let chars = &t.chars()[..t.chars().len().min(fit)];
|
||||
// One run per pixel row across every cell, not a call per pixel.
|
||||
let mut line = [t.bg; TEXT_BYTES * font::WIDTH as usize];
|
||||
let line = &mut line[..chars.len() * font::WIDTH as usize];
|
||||
for row in 0..font::HEIGHT {
|
||||
for (cell, c) in line.chunks_mut(font::WIDTH as usize).zip(chars) {
|
||||
let bits = font::glyph(*c)[row as usize];
|
||||
for (col, px) in cell.iter_mut().enumerate() {
|
||||
*px = if bits & (0x80 >> col) != 0 { t.fg } else { t.bg };
|
||||
}
|
||||
let (ww, wh) = (self.window_size().0.raw(), self.window_size().1.raw());
|
||||
let (x0, y0) = (x.raw().min(ww), y.raw().min(wh));
|
||||
let (x1, y1) = (x0.saturating_add(width.raw()).min(ww), y0.saturating_add(height.raw()).min(wh));
|
||||
let mut run = [Color::from_raw(0); RUN];
|
||||
for row in y0..y1 {
|
||||
let mut col = x0;
|
||||
while col < x1 {
|
||||
let n = usize::from(x1 - col).min(RUN);
|
||||
self.host.read(index, row as usize * ww as usize + col as usize, &mut run[..n]);
|
||||
self.fb.write_row(ScreenCoord::new(col), ScreenCoord::new(row + BAR_HEIGHT), &run[..n]);
|
||||
col += n as u16;
|
||||
}
|
||||
}
|
||||
self.fb.flush();
|
||||
}
|
||||
|
||||
/// The bar, and the window in front (or nothing) below it.
|
||||
fn repaint(&mut self) {
|
||||
self.draw_bar();
|
||||
let (width, height) = self.window_size();
|
||||
match self.windows.front() {
|
||||
Some(index) => self.present(index, ScreenCoord::new(0), ScreenCoord::new(0), width, height),
|
||||
None => {
|
||||
self.fb.fill_rect(ScreenCoord::new(0), ScreenCoord::new(BAR_HEIGHT), width, height, Color::rgb(30, 30, 60));
|
||||
self.fb.flush();
|
||||
}
|
||||
self.fb.write_row(t.x, ScreenCoord::new(y0 + row), line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<libkos::syscall::Give>) {
|
||||
use types::display::input::{POLL, SUBSCRIBE};
|
||||
match (words[0], cap) {
|
||||
(SUBSCRIBE, Some(notify)) => {
|
||||
self.focus.subscribe(badge, notify);
|
||||
(DisplayResponse::Ok.encode(), None)
|
||||
/// Each window's name, the front one highlighted; who is signed in at
|
||||
/// the right.
|
||||
fn draw_bar(&mut self) {
|
||||
let width = self.fb.width();
|
||||
self.fb.fill_rect(ScreenCoord::new(0), ScreenCoord::new(0), width, Dimension::new(BAR_HEIGHT), BAR_BG);
|
||||
let mut x = 0u16;
|
||||
let mut label = [b' '; NAME_BYTES + 2];
|
||||
let entries: [Option<([u8; NAME_BYTES], usize, bool)>; MAX_WINDOWS] = {
|
||||
let mut out = [None; MAX_WINDOWS];
|
||||
for (slot, (_, name, front)) in out.iter_mut().zip(self.windows.list()) {
|
||||
let mut n = [0u8; NAME_BYTES];
|
||||
n[..name.len()].copy_from_slice(name);
|
||||
*slot = Some((n, name.len(), front));
|
||||
}
|
||||
(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),
|
||||
out
|
||||
};
|
||||
for (name, len, front) in entries.into_iter().flatten() {
|
||||
label[1..=len].copy_from_slice(&name[..len]);
|
||||
label[len + 1] = b' ';
|
||||
let text = &label[..len + 2];
|
||||
let (fg, bg) = if front { (FRONT_FG, FRONT_BG) } else { (BAR_FG, BAR_BG) };
|
||||
raster::draw_text(&mut self.fb, ScreenCoord::new(x), ScreenCoord::new(0), fg, bg, text);
|
||||
x = x.saturating_add(text.len() as u16 * font::WIDTH + font::WIDTH);
|
||||
}
|
||||
let user = &self.user[..self.user_len];
|
||||
let at = width.raw().saturating_sub((user.len() as u16 + 1) * font::WIDTH);
|
||||
raster::draw_text(&mut self.fb, ScreenCoord::new(at), ScreenCoord::new(0), BAR_FG, BAR_BG, user);
|
||||
}
|
||||
|
||||
/// A request through window `index`'s capability.
|
||||
fn window_call(&mut self, index: usize, badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<Give>) {
|
||||
match (words[0], cap) {
|
||||
(w::BUFFER, _) => match self.host.buffer(index, self.pixels()) {
|
||||
Some(buffer) => {
|
||||
let (width, height) = self.window_size();
|
||||
([w::OK, width.raw() as usize, height.raw() as usize, 0], Some(Give::from(buffer)))
|
||||
}
|
||||
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;
|
||||
(w::PRESENT, _) => {
|
||||
let (x, y, width, height) = w::unpack_rect([words[1], words[2]]);
|
||||
self.present(index, x, y, width, height);
|
||||
([w::OK, 0, 0, 0], None)
|
||||
}
|
||||
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 } => {
|
||||
// Clients choose every coordinate; clip to the screen so a
|
||||
// huge or off-screen rectangle costs nothing and coordinates
|
||||
// never overflow in the driver.
|
||||
let (fw, fh) = (self.fb.width().raw(), self.fb.height().raw());
|
||||
if x.raw() < fw && y.raw() < fh {
|
||||
let w = Dimension::new(w.raw().min(fw - x.raw()));
|
||||
let h = Dimension::new(h.raw().min(fh - y.raw()));
|
||||
self.fb.fill_rect(x, y, w, h, color);
|
||||
}
|
||||
DisplayResponse::Ok
|
||||
(w::SUBSCRIBE, Some(notify)) if self.windows.subscribe(badge, notify) => ([w::OK, 0, 0, 0], None),
|
||||
(w::POLL, _) => (self.windows.poll(badge).unwrap_or([0; 4]), None),
|
||||
(w::CLOSE, _) => {
|
||||
self.close(index);
|
||||
([w::OK, 0, 0, 0], None)
|
||||
}
|
||||
DisplayRequest::Clear { color } => {
|
||||
self.fb.clear(color);
|
||||
DisplayResponse::Ok
|
||||
}
|
||||
DisplayRequest::Flush => {
|
||||
self.fb.flush();
|
||||
DisplayResponse::Ok
|
||||
}
|
||||
DisplayRequest::Scroll { top, height, by, fill } => {
|
||||
// Clipped like a rectangle: only rows on the screen move.
|
||||
let fh = self.fb.height().raw();
|
||||
if top.raw() < fh {
|
||||
let height = Dimension::new(height.raw().min(fh - top.raw()));
|
||||
let by = Dimension::new(by.raw().min(height.raw()));
|
||||
self.fb.scroll_up(top, height, by, fill);
|
||||
}
|
||||
DisplayResponse::Ok
|
||||
}
|
||||
DisplayRequest::DrawText(t) => {
|
||||
self.draw_text(&t);
|
||||
DisplayResponse::Ok
|
||||
}
|
||||
DisplayRequest::GetDimensions => DisplayResponse::Dimensions {
|
||||
width: self.fb.width(),
|
||||
height: self.fb.height(),
|
||||
},
|
||||
_ => ([MALFORMED, 0, 0, 0], None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_switch_key(event: [usize; 4]) -> bool {
|
||||
use types::input::event::{FUNCTION_KEY, KEY_DOWN, KEY_UP};
|
||||
matches!(event[0], KEY_DOWN | KEY_UP) && event[1] == FUNCTION_KEY && event[2] == w::SWITCH_KEY
|
||||
}
|
||||
|
||||
impl<F: Framebuffer, H: Host> Server for DisplayServer<F, H> {
|
||||
type Request = Never;
|
||||
type Response = Never;
|
||||
|
||||
fn handle(&mut self, request: Never) -> Never {
|
||||
request
|
||||
}
|
||||
|
||||
fn handle_call(&mut self, badge: usize, words: [usize; 4], cap: Option<CapSlot>) -> ([usize; 4], Option<Give>) {
|
||||
if badge & w::BADGE != 0 {
|
||||
return match self.windows.slot(badge) {
|
||||
Some(index) => self.window_call(index, badge, words, cap),
|
||||
// Closed: its capability is on its way out.
|
||||
None => ([MALFORMED, 0, 0, 0], None),
|
||||
};
|
||||
}
|
||||
match words[0] {
|
||||
w::OPEN => {
|
||||
let (name, len) = w::unpack_name([words[1], words[2], words[3]]);
|
||||
self.open(badge, &name[..len])
|
||||
}
|
||||
w::SET_USER if badge == 0 => {
|
||||
let (name, len) = w::unpack_name([words[1], words[2], words[3]]);
|
||||
(self.user, self.user_len) = (name, len);
|
||||
self.repaint();
|
||||
([w::OK, 0, 0, 0], None)
|
||||
}
|
||||
w::FORGET if badge == 0 => {
|
||||
let mut gone = [None; MAX_WINDOWS];
|
||||
for (slot, index) in gone.iter_mut().zip(self.windows.opened_by(words[1])) {
|
||||
*slot = Some(index);
|
||||
}
|
||||
for index in gone.into_iter().flatten() {
|
||||
self.close(index);
|
||||
}
|
||||
([w::OK, 0, 0, 0], None)
|
||||
}
|
||||
_ => ([MALFORMED, 0, 0, 0], None),
|
||||
}
|
||||
}
|
||||
|
||||
/// The input server has events: the switch key is the display's, the
|
||||
/// rest go to the window in front.
|
||||
fn signal(&mut self, _bits: usize) {
|
||||
let mut told = None;
|
||||
while let Some(event) = self.host.input() {
|
||||
if is_switch_key(event) {
|
||||
if event[0] == types::input::event::KEY_DOWN && self.windows.cycle().is_some() {
|
||||
self.repaint();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
told = self.windows.push(event).or(told);
|
||||
}
|
||||
if let Some(notify) = told {
|
||||
self.host.signal(notify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
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,3 +1,2 @@
|
||||
mod focus;
|
||||
mod protocol;
|
||||
mod server;
|
||||
mod windows;
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
use crate::protocol::{DisplayRequest, DisplayResponse};
|
||||
use server_framework::{Decode, Encode};
|
||||
use types::display::{Color, Dimension, ScreenCoord};
|
||||
|
||||
#[test]
|
||||
fn draw_rect_roundtrip() {
|
||||
let req = DisplayRequest::DrawRect {
|
||||
x: ScreenCoord::new(10),
|
||||
y: ScreenCoord::new(20),
|
||||
w: Dimension::new(100),
|
||||
h: Dimension::new(50),
|
||||
color: Color::from_raw(0xFF0000),
|
||||
};
|
||||
let words = req.encode();
|
||||
let decoded = DisplayRequest::decode(words).unwrap();
|
||||
assert_eq!(decoded, req);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_roundtrip() {
|
||||
let req = DisplayRequest::Clear { color: Color::from_raw(0x00FF00) };
|
||||
let words = req.encode();
|
||||
let decoded = DisplayRequest::decode(words).unwrap();
|
||||
assert_eq!(decoded, req);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flush_roundtrip() {
|
||||
let words = DisplayRequest::Flush.encode();
|
||||
let decoded = DisplayRequest::decode(words).unwrap();
|
||||
assert_eq!(decoded, DisplayRequest::Flush);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_dimensions_roundtrip() {
|
||||
let words = DisplayRequest::GetDimensions.encode();
|
||||
let decoded = DisplayRequest::decode(words).unwrap();
|
||||
assert_eq!(decoded, DisplayRequest::GetDimensions);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dimensions_response_roundtrip() {
|
||||
let resp = DisplayResponse::Dimensions {
|
||||
width: Dimension::new(320),
|
||||
height: Dimension::new(240),
|
||||
};
|
||||
let words = resp.encode();
|
||||
let decoded = DisplayResponse::decode(words).unwrap();
|
||||
assert_eq!(decoded, resp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_request_returns_none() {
|
||||
assert!(DisplayRequest::decode([99, 0, 0, 0]).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scroll_roundtrip() {
|
||||
let req = DisplayRequest::Scroll {
|
||||
top: ScreenCoord::new(0),
|
||||
height: Dimension::new(800),
|
||||
by: Dimension::new(16),
|
||||
fill: Color::from_raw(0x101018),
|
||||
};
|
||||
assert_eq!(DisplayRequest::decode(req.encode()), Some(req));
|
||||
}
|
||||
@@ -1,184 +1,189 @@
|
||||
use crate::protocol::{DisplayRequest, DisplayResponse};
|
||||
use crate::server::DisplayServer;
|
||||
use driver_framebuffer_mock::MockFramebuffer;
|
||||
use server_framework::Server;
|
||||
use types::display::{Color, Dimension, ScreenCoord};
|
||||
extern crate std;
|
||||
|
||||
fn test_server() -> DisplayServer<MockFramebuffer> {
|
||||
DisplayServer::new(MockFramebuffer::new(
|
||||
Dimension::new(320),
|
||||
Dimension::new(240),
|
||||
))
|
||||
use crate::server::{BAR_HEIGHT, DisplayServer, Host};
|
||||
use driver_framebuffer_mock::GridFramebuffer;
|
||||
use server_framework::{MALFORMED, Server};
|
||||
use std::vec;
|
||||
use std::vec::Vec;
|
||||
use types::bounded::CapSlot;
|
||||
use types::display::Color;
|
||||
use types::display::window::{self as w, BADGE};
|
||||
|
||||
const W: usize = 32;
|
||||
const H: usize = 16 + 8;
|
||||
|
||||
/// Buffers in memory; a capability is its slot number plus 40.
|
||||
#[derive(Default)]
|
||||
struct FakeHost {
|
||||
buffers: Vec<Option<Vec<Color>>>,
|
||||
minted: Vec<usize>,
|
||||
revoked: Vec<CapSlot>,
|
||||
reclaimed: Vec<usize>,
|
||||
signalled: Vec<CapSlot>,
|
||||
events: Vec<[usize; 4]>,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn draw_rect_writes_pixels() {
|
||||
let mut server = test_server();
|
||||
let resp = server.handle(DisplayRequest::DrawRect {
|
||||
x: ScreenCoord::new(0),
|
||||
y: ScreenCoord::new(0),
|
||||
w: Dimension::new(4),
|
||||
h: Dimension::new(3),
|
||||
color: Color::from_raw(0xFF),
|
||||
});
|
||||
assert_eq!(resp, DisplayResponse::Ok);
|
||||
assert_eq!(server.framebuffer().pixel_count(), 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_resets_pixels() {
|
||||
let mut server = test_server();
|
||||
server.handle(DisplayRequest::DrawRect {
|
||||
x: ScreenCoord::new(0),
|
||||
y: ScreenCoord::new(0),
|
||||
w: Dimension::new(10),
|
||||
h: Dimension::new(10),
|
||||
color: Color::from_raw(0xFF),
|
||||
});
|
||||
server.handle(DisplayRequest::Clear {
|
||||
color: Color::from_raw(0),
|
||||
});
|
||||
assert_eq!(server.framebuffer().pixel_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flush_sets_flag() {
|
||||
let mut server = test_server();
|
||||
assert!(!server.framebuffer().was_flushed());
|
||||
server.handle(DisplayRequest::Flush);
|
||||
assert!(server.framebuffer().was_flushed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_dimensions_returns_size() {
|
||||
let mut server = test_server();
|
||||
let resp = server.handle(DisplayRequest::GetDimensions);
|
||||
assert_eq!(
|
||||
resp,
|
||||
DisplayResponse::Dimensions {
|
||||
width: Dimension::new(320),
|
||||
height: Dimension::new(240),
|
||||
impl Host for FakeHost {
|
||||
fn buffer(&mut self, index: usize, pixels: usize) -> Option<CapSlot> {
|
||||
if self.buffers.len() <= index {
|
||||
self.buffers.resize(index + 1, None);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn draw_rect_correct_pixel_positions() {
|
||||
let mut server = test_server();
|
||||
server.handle(DisplayRequest::DrawRect {
|
||||
x: ScreenCoord::new(5),
|
||||
y: ScreenCoord::new(10),
|
||||
w: Dimension::new(2),
|
||||
h: Dimension::new(2),
|
||||
color: Color::from_raw(0xAB),
|
||||
});
|
||||
let fb = server.framebuffer();
|
||||
assert_eq!(fb.pixel_count(), 4);
|
||||
assert_eq!(
|
||||
fb.pixel_at(0),
|
||||
(
|
||||
ScreenCoord::new(5),
|
||||
ScreenCoord::new(10),
|
||||
Color::from_raw(0xAB)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
fb.pixel_at(1),
|
||||
(
|
||||
ScreenCoord::new(6),
|
||||
ScreenCoord::new(10),
|
||||
Color::from_raw(0xAB)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
fb.pixel_at(2),
|
||||
(
|
||||
ScreenCoord::new(5),
|
||||
ScreenCoord::new(11),
|
||||
Color::from_raw(0xAB)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
fb.pixel_at(3),
|
||||
(
|
||||
ScreenCoord::new(6),
|
||||
ScreenCoord::new(11),
|
||||
Color::from_raw(0xAB)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_huge_rect_is_clipped_to_the_screen() {
|
||||
let mut server = test_server();
|
||||
let resp = server.handle(DisplayRequest::DrawRect {
|
||||
x: ScreenCoord::new(310),
|
||||
y: ScreenCoord::new(230),
|
||||
w: Dimension::new(u16::MAX),
|
||||
h: Dimension::new(u16::MAX),
|
||||
color: Color::from_raw(0xFF),
|
||||
});
|
||||
assert_eq!(resp, DisplayResponse::Ok);
|
||||
assert_eq!(server.framebuffer().pixel_count(), 10 * 10);
|
||||
let (x, y, _) = server.framebuffer().pixel_at(99);
|
||||
assert_eq!((x.raw(), y.raw()), (319, 239));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_off_screen_rect_draws_nothing() {
|
||||
let mut server = test_server();
|
||||
for (x, y) in [(320, 0), (0, 240), (u16::MAX, u16::MAX)] {
|
||||
server.handle(DisplayRequest::DrawRect {
|
||||
x: ScreenCoord::new(x),
|
||||
y: ScreenCoord::new(y),
|
||||
w: Dimension::new(u16::MAX),
|
||||
h: Dimension::new(u16::MAX),
|
||||
color: Color::from_raw(0xFF),
|
||||
});
|
||||
self.buffers[index].get_or_insert_with(|| vec![Color::from_raw(0); pixels]);
|
||||
CapSlot::new(40 + index as u8)
|
||||
}
|
||||
assert_eq!(server.framebuffer().pixel_count(), 0);
|
||||
fn blank(&mut self, index: usize) {
|
||||
if let Some(b) = self.buffers[index].as_mut() {
|
||||
b.fill(Color::from_raw(0));
|
||||
}
|
||||
}
|
||||
fn read(&self, index: usize, from: usize, out: &mut [Color]) {
|
||||
let b = self.buffers[index].as_ref().unwrap();
|
||||
out.copy_from_slice(&b[from..from + out.len()]);
|
||||
}
|
||||
fn reclaim(&mut self, index: usize) {
|
||||
self.reclaimed.push(index);
|
||||
}
|
||||
fn mint(&mut self, badge: usize) -> Option<CapSlot> {
|
||||
self.minted.push(badge);
|
||||
CapSlot::new(20 + self.minted.len() as u8)
|
||||
}
|
||||
fn revoke(&mut self, cap: CapSlot) {
|
||||
self.revoked.push(cap);
|
||||
}
|
||||
fn signal(&mut self, notify: CapSlot) {
|
||||
self.signalled.push(notify);
|
||||
}
|
||||
fn input(&mut self) -> Option<[usize; 4]> {
|
||||
(!self.events.is_empty()).then(|| self.events.remove(0))
|
||||
}
|
||||
}
|
||||
|
||||
type Srv = DisplayServer<GridFramebuffer<W, H>, FakeHost>;
|
||||
|
||||
fn server() -> Srv {
|
||||
DisplayServer::new(GridFramebuffer::new(), FakeHost::default())
|
||||
}
|
||||
|
||||
/// Opens a window through launcher `opener`: its badge.
|
||||
fn open(s: &mut Srv, opener: usize, name: &[u8]) -> usize {
|
||||
let n = w::pack_name(name);
|
||||
let (reply, cap) = s.handle_call(opener, [w::OPEN, n[0], n[1], n[2]], None);
|
||||
assert_eq!(reply, [w::OK, W, H - BAR_HEIGHT as usize, 0]);
|
||||
assert!(cap.is_some_and(|g| g.keep_delegate), "the launcher may pass it on");
|
||||
*s.host().minted.last().unwrap()
|
||||
}
|
||||
|
||||
/// Paints window `slot`'s buffer one colour and presents all of it.
|
||||
fn paint(s: &mut Srv, badge: usize, slot: usize, color: Color) -> [usize; 4] {
|
||||
// The app writes its buffer directly; here, through the fake.
|
||||
s.host_mut_for_test().buffers[slot].as_mut().unwrap().fill(color);
|
||||
let rect = w::pack_rect(
|
||||
types::display::ScreenCoord::new(0),
|
||||
types::display::ScreenCoord::new(0),
|
||||
types::display::Dimension::new(W as u16),
|
||||
types::display::Dimension::new(H as u16),
|
||||
);
|
||||
s.handle_call(badge, [w::PRESENT, rect[0], rect[1], 0], None).0
|
||||
}
|
||||
|
||||
fn shown(s: &Srv) -> Option<Color> {
|
||||
s.framebuffer().pixel(W / 2, H - 1)
|
||||
}
|
||||
|
||||
const RED: Color = Color::rgb(200, 0, 0);
|
||||
const BLUE: Color = Color::rgb(0, 0, 200);
|
||||
|
||||
#[test]
|
||||
fn the_front_window_is_shown_below_the_bar() {
|
||||
let mut s = server();
|
||||
let t = open(&mut s, 1, b"Terminal");
|
||||
assert_eq!(paint(&mut s, t, 0, RED)[0], w::OK);
|
||||
assert_eq!(shown(&s), Some(RED));
|
||||
assert_ne!(s.framebuffer().pixel(W - 1, 0), Some(RED), "the bar is the display's");
|
||||
assert_eq!(s.framebuffer().pixel(0, BAR_HEIGHT as usize), Some(RED), "the window starts under it");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_draws_whole_glyph_cells() {
|
||||
use types::display::text::Text;
|
||||
let mut server = test_server();
|
||||
let (fg, bg) = (Color::rgb(255, 255, 255), Color::rgb(0, 0, 0));
|
||||
let t = Text::new(ScreenCoord::new(8), ScreenCoord::new(16), fg, bg, b"A");
|
||||
assert_eq!(server.handle(DisplayRequest::DrawText(t)), DisplayResponse::Ok);
|
||||
// One 8x16 cell, written row by row.
|
||||
assert_eq!(server.framebuffer().pixel_count(), 128);
|
||||
// 'A' row 2 is 0x7c: column 0 off, column 1 on.
|
||||
let at = |i: usize| server.framebuffer().pixel_at(i);
|
||||
assert_eq!(at(16), (ScreenCoord::new(8), ScreenCoord::new(18), bg));
|
||||
assert_eq!(at(17), (ScreenCoord::new(9), ScreenCoord::new(18), fg));
|
||||
fn a_window_behind_draws_nothing_until_it_is_in_front() {
|
||||
let mut s = server();
|
||||
let t = open(&mut s, 1, b"Terminal");
|
||||
paint(&mut s, t, 0, RED);
|
||||
let snake = open(&mut s, 2, b"Snake");
|
||||
assert_eq!(shown(&s), Some(Color::from_raw(0)), "a new window starts blank");
|
||||
paint(&mut s, t, 0, BLUE);
|
||||
assert_eq!(shown(&s), Some(Color::from_raw(0)), "the terminal is behind");
|
||||
paint(&mut s, snake, 1, RED);
|
||||
assert_eq!(shown(&s), Some(RED));
|
||||
// The switch key brings the terminal back, as it last drew.
|
||||
s.host_mut_for_test().events = vec![[1, 10, 1, 0], [2, 10, 1, 0]];
|
||||
s.signal(0);
|
||||
assert_eq!(shown(&s), Some(BLUE));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_past_the_edge_is_cut_not_wrapped() {
|
||||
use types::display::text::Text;
|
||||
let mut server = test_server();
|
||||
let c = Color::rgb(1, 1, 1);
|
||||
// 320 wide: from x=304 only two cells fit.
|
||||
let t = Text::new(ScreenCoord::new(304), ScreenCoord::new(0), c, c, b"abc");
|
||||
server.handle(DisplayRequest::DrawText(t));
|
||||
assert_eq!(server.framebuffer().pixel_count(), 256);
|
||||
let low = Text::new(ScreenCoord::new(0), ScreenCoord::new(230), c, c, b"a");
|
||||
server.handle(DisplayRequest::DrawText(low));
|
||||
assert_eq!(server.framebuffer().pixel_count(), 256);
|
||||
fn input_goes_to_the_front_window_and_the_switch_key_to_nobody() {
|
||||
let mut s = server();
|
||||
let t = open(&mut s, 1, b"Terminal");
|
||||
let notify = CapSlot::new(9).unwrap();
|
||||
assert_eq!(s.handle_call(t, [w::SUBSCRIBE, 0, 0, 0], Some(notify)).0[0], w::OK);
|
||||
s.host_mut_for_test().events = vec![[1, 9, b'a' as usize, 0], [1, 10, 1, 0]];
|
||||
s.signal(0);
|
||||
assert_eq!(s.host().signalled, [notify]);
|
||||
assert_eq!(s.handle_call(t, [w::POLL, 0, 0, 0], None).0, [1, 9, b'a' as usize, 0]);
|
||||
assert_eq!(s.handle_call(t, [w::POLL, 0, 0, 0], None).0, [0; 4], "F1 was the display's");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_scroll_off_the_screen_moves_nothing() {
|
||||
let mut s = test_server();
|
||||
let scroll = |top| DisplayRequest::Scroll {
|
||||
top: ScreenCoord::new(top),
|
||||
height: Dimension::new(u16::MAX),
|
||||
by: Dimension::new(u16::MAX),
|
||||
fill: Color::from_raw(0),
|
||||
};
|
||||
s.handle(scroll(u16::MAX));
|
||||
assert_eq!(s.framebuffer().scrolls(), 0);
|
||||
s.handle(scroll(0));
|
||||
assert_eq!(s.framebuffer().scrolls(), 1, "clipped, then done");
|
||||
fn only_launchers_open_windows_and_only_init_sets_the_user() {
|
||||
let mut s = server();
|
||||
let t = open(&mut s, 1, b"Terminal");
|
||||
let n = w::pack_name(b"Fake");
|
||||
assert_eq!(s.handle_call(t, [w::OPEN, n[0], n[1], n[2]], None).0[0], MALFORMED, "not through a window");
|
||||
assert_eq!(s.handle_call(1, [w::SET_USER, n[0], n[1], n[2]], None).0[0], MALFORMED);
|
||||
assert_eq!(s.handle_call(0, [w::SET_USER, n[0], n[1], n[2]], None).0[0], w::OK);
|
||||
assert_eq!(s.handle_call(BADGE | 12345, [w::POLL, 0, 0, 0], None).0[0], MALFORMED, "a made-up window");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_takes_the_window_and_its_buffer_back() {
|
||||
let mut s = server();
|
||||
let t = open(&mut s, 1, b"Terminal");
|
||||
let snake = open(&mut s, 2, b"Snake");
|
||||
paint(&mut s, t, 0, BLUE);
|
||||
assert_eq!(s.handle_call(snake, [w::CLOSE, 0, 0, 0], None).0[0], w::OK);
|
||||
assert_eq!(s.host().revoked, [CapSlot::new(22).unwrap()]);
|
||||
assert_eq!(s.host().reclaimed, [1]);
|
||||
assert_eq!(shown(&s), Some(BLUE), "the terminal is in front again");
|
||||
assert_eq!(s.handle_call(snake, [w::POLL, 0, 0, 0], None).0[0], MALFORMED, "closed");
|
||||
// Its buffer is reused, blank, for the next window.
|
||||
s.host_mut_for_test().buffers[1].as_mut().unwrap().fill(RED);
|
||||
open(&mut s, 2, b"Snake");
|
||||
assert!(s.host().buffers[1].as_ref().unwrap().iter().all(|c| *c == Color::from_raw(0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_launcher_gone_takes_its_windows_with_it() {
|
||||
let mut s = server();
|
||||
open(&mut s, 1, b"Terminal");
|
||||
open(&mut s, 2, b"Snake");
|
||||
open(&mut s, 2, b"Counter");
|
||||
assert_eq!(s.handle_call(3, [w::FORGET, 2, 0, 0], None).0[0], MALFORMED, "only init says so");
|
||||
assert_eq!(s.handle_call(0, [w::FORGET, 2, 0, 0], None).0[0], w::OK);
|
||||
assert_eq!(s.host().reclaimed, [1, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_present_outside_the_window_is_clipped() {
|
||||
let mut s = server();
|
||||
let t = open(&mut s, 1, b"Terminal");
|
||||
s.host_mut_for_test().buffers[0].as_mut().unwrap().fill(RED);
|
||||
let rect = w::pack_rect(
|
||||
types::display::ScreenCoord::new(30),
|
||||
types::display::ScreenCoord::new(5),
|
||||
types::display::Dimension::new(60000),
|
||||
types::display::Dimension::new(60000),
|
||||
);
|
||||
assert_eq!(s.handle_call(t, [w::PRESENT, rect[0], rect[1], 0], None).0[0], w::OK);
|
||||
assert_eq!(s.framebuffer().pixel(31, H - 1), Some(RED));
|
||||
assert_ne!(s.framebuffer().pixel(29, H - 1), Some(RED), "left of the rectangle is untouched");
|
||||
}
|
||||
|
||||
69
crates/servers/display-server/src/tests/windows.rs
Normal file
69
crates/servers/display-server/src/tests/windows.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
extern crate std;
|
||||
|
||||
use crate::windows::{MAX_WINDOWS, Windows};
|
||||
use std::string::String;
|
||||
use std::vec::Vec;
|
||||
|
||||
fn names(w: &Windows<u8>) -> Vec<(String, bool)> {
|
||||
w.list().map(|(_, n, front)| (String::from_utf8_lossy(n).into_owned(), front)).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_window_comes_to_the_front_and_the_key_cycles() {
|
||||
let mut w = Windows::<u8>::new();
|
||||
let (t, _) = w.open(1, b"Terminal").unwrap();
|
||||
let (s, _) = w.open(2, b"Snake").unwrap();
|
||||
assert_eq!(w.front(), Some(s));
|
||||
assert_eq!(names(&w), [("Terminal".into(), false), ("Snake".into(), true)]);
|
||||
assert_eq!(w.cycle(), Some(t));
|
||||
assert_eq!(w.cycle(), Some(s));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closing_the_front_window_shows_the_next_one() {
|
||||
let mut w = Windows::<u8>::new();
|
||||
let (t, _) = w.open(1, b"Terminal").unwrap();
|
||||
let (s, _) = w.open(2, b"Snake").unwrap();
|
||||
assert!(w.close(s), "the front changed");
|
||||
assert_eq!(w.front(), Some(t));
|
||||
assert!(w.close(t));
|
||||
assert_eq!(w.front(), None);
|
||||
assert_eq!(w.cycle(), None, "nothing to cycle to");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_the_front_window_gets_input_and_only_its_own() {
|
||||
let mut w = Windows::<u8>::new();
|
||||
let (_, tb) = w.open(1, b"Terminal").unwrap();
|
||||
assert!(w.subscribe(tb, 7));
|
||||
let (_, sb) = w.open(2, b"Snake").unwrap();
|
||||
assert!(w.subscribe(sb, 9));
|
||||
assert_eq!(w.push([1, 9, 0, 0]), Some(9), "snake is in front");
|
||||
assert_eq!(w.poll(tb), None, "the terminal sees none of it");
|
||||
assert_eq!(w.poll(sb), Some([1, 9, 0, 0]));
|
||||
w.cycle();
|
||||
assert_eq!(w.push([1, 4, 0, 0]), Some(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn badges_are_never_reused_and_room_runs_out() {
|
||||
let mut w = Windows::<u8>::new();
|
||||
let (i, first) = w.open(1, b"a").unwrap();
|
||||
w.close(i);
|
||||
let (_, second) = w.open(1, b"a").unwrap();
|
||||
assert_ne!(first, second);
|
||||
assert_eq!(w.slot(first), None, "a closed window's badge reaches nothing");
|
||||
for _ in 1..MAX_WINDOWS {
|
||||
assert!(w.open(1, b"x").is_some());
|
||||
}
|
||||
assert!(w.open(1, b"x").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_launchers_windows_are_found_by_its_badge() {
|
||||
let mut w = Windows::<u8>::new();
|
||||
let (a, _) = w.open(5, b"one").unwrap();
|
||||
w.open(6, b"other").unwrap();
|
||||
let (b, _) = w.open(5, b"two").unwrap();
|
||||
assert_eq!(w.opened_by(5).collect::<Vec<_>>(), [a, b]);
|
||||
}
|
||||
156
crates/servers/display-server/src/windows.rs
Normal file
156
crates/servers/display-server/src/windows.rs
Normal file
@@ -0,0 +1,156 @@
|
||||
//! Which windows there are and which is in front (see `types::display::
|
||||
//! window`). Every window fills the screen below the OS bar; one is in
|
||||
//! front at a time, and only it is shown and gets input. A new window comes
|
||||
//! to the front; a key cycles through them. Pure: the server keeps the
|
||||
//! pixels.
|
||||
|
||||
use types::display::window::{BADGE, NAME_BYTES};
|
||||
|
||||
pub const MAX_WINDOWS: usize = 4;
|
||||
/// Events received and not yet polled by the window in front.
|
||||
const QUEUED: usize = 32;
|
||||
|
||||
struct Queue {
|
||||
events: [[usize; 4]; QUEUED],
|
||||
head: usize,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
const fn new() -> Self {
|
||||
Self { events: [[0; 4]; QUEUED], head: 0, len: 0 }
|
||||
}
|
||||
|
||||
/// The oldest is dropped when it is full.
|
||||
fn push(&mut self, event: [usize; 4]) {
|
||||
if self.len == QUEUED {
|
||||
self.head = (self.head + 1) % QUEUED;
|
||||
self.len -= 1;
|
||||
}
|
||||
self.events[(self.head + self.len) % QUEUED] = event;
|
||||
self.len += 1;
|
||||
}
|
||||
|
||||
fn pop(&mut self) -> Option<[usize; 4]> {
|
||||
if self.len == 0 {
|
||||
return None;
|
||||
}
|
||||
let event = self.events[self.head];
|
||||
self.head = (self.head + 1) % QUEUED;
|
||||
self.len -= 1;
|
||||
Some(event)
|
||||
}
|
||||
}
|
||||
|
||||
struct Window<N> {
|
||||
badge: usize,
|
||||
/// The launcher's badge: its windows go when it does.
|
||||
opener: usize,
|
||||
name: [u8; NAME_BYTES],
|
||||
name_len: usize,
|
||||
notify: Option<N>,
|
||||
queue: Queue,
|
||||
}
|
||||
|
||||
pub struct Windows<N: Copy> {
|
||||
slots: [Option<Window<N>>; MAX_WINDOWS],
|
||||
front: Option<usize>,
|
||||
/// Makes each window's badge new, so a closed window's never returns.
|
||||
opened: usize,
|
||||
}
|
||||
|
||||
impl<N: Copy> Windows<N> {
|
||||
pub const fn new() -> Self {
|
||||
Self { slots: [const { None }; MAX_WINDOWS], front: None, opened: 0 }
|
||||
}
|
||||
|
||||
/// A window for `name`, opened through `opener`'s capability, in
|
||||
/// front: its slot and its badge. `None` if there is no room.
|
||||
pub fn open(&mut self, opener: usize, name: &[u8]) -> Option<(usize, usize)> {
|
||||
let index = self.slots.iter().position(Option::is_none)?;
|
||||
self.opened += 1;
|
||||
let badge = BADGE | self.opened << 8 | index;
|
||||
let mut n = [0u8; NAME_BYTES];
|
||||
let name_len = name.len().min(NAME_BYTES);
|
||||
n[..name_len].copy_from_slice(&name[..name_len]);
|
||||
self.slots[index] = Some(Window { badge, opener, name: n, name_len, notify: None, queue: Queue::new() });
|
||||
self.front = Some(index);
|
||||
Some((index, badge))
|
||||
}
|
||||
|
||||
/// The slot of the window behind `badge`, if it is open.
|
||||
pub fn slot(&self, badge: usize) -> Option<usize> {
|
||||
self.slots.iter().position(|w| w.as_ref().is_some_and(|w| w.badge == badge))
|
||||
}
|
||||
|
||||
/// Closes the window in `index`; whether the front changed.
|
||||
pub fn close(&mut self, index: usize) -> bool {
|
||||
if self.slots.get_mut(index).and_then(Option::take).is_none() {
|
||||
return false;
|
||||
}
|
||||
if self.front != Some(index) {
|
||||
return false;
|
||||
}
|
||||
self.front = None;
|
||||
self.front = self.after(index);
|
||||
true
|
||||
}
|
||||
|
||||
/// The slots of every window `opener` opened.
|
||||
pub fn opened_by(&self, opener: usize) -> impl Iterator<Item = usize> + '_ {
|
||||
self.slots.iter().enumerate().filter_map(move |(i, w)| w.as_ref().filter(|w| w.opener == opener).map(|_| i))
|
||||
}
|
||||
|
||||
/// The next open window after `index`, round, if any.
|
||||
fn after(&self, index: usize) -> Option<usize> {
|
||||
(1..=MAX_WINDOWS).map(|step| (index + step) % MAX_WINDOWS).find(|i| self.slots[*i].is_some())
|
||||
}
|
||||
|
||||
/// Brings the next window to the front; the new front if it changed.
|
||||
pub fn cycle(&mut self) -> Option<usize> {
|
||||
let next = self.after(self.front?)?;
|
||||
(Some(next) != self.front).then(|| {
|
||||
self.front = Some(next);
|
||||
next
|
||||
})
|
||||
}
|
||||
|
||||
pub fn front(&self) -> Option<usize> {
|
||||
self.front
|
||||
}
|
||||
|
||||
/// Tells `notify` when input for the window `badge` arrives.
|
||||
pub fn subscribe(&mut self, badge: usize, notify: N) -> bool {
|
||||
let Some(w) = self.slot(badge).and_then(|i| self.slots[i].as_mut()) else { return false };
|
||||
w.notify = Some(notify);
|
||||
true
|
||||
}
|
||||
|
||||
/// Queues an event for the window in front; whom to tell, if anyone.
|
||||
pub fn push(&mut self, event: [usize; 4]) -> Option<N> {
|
||||
let w = self.front.and_then(|i| self.slots[i].as_mut())?;
|
||||
w.queue.push(event);
|
||||
w.notify
|
||||
}
|
||||
|
||||
/// The window `badge`'s next event.
|
||||
pub fn poll(&mut self, badge: usize) -> Option<[usize; 4]> {
|
||||
let i = self.slot(badge)?;
|
||||
self.slots[i].as_mut()?.queue.pop()
|
||||
}
|
||||
|
||||
/// Every open window, for the bar: its slot, its name, and whether it
|
||||
/// is in front.
|
||||
pub fn list(&self) -> impl Iterator<Item = (usize, &[u8], bool)> + '_ {
|
||||
self.slots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(move |(i, w)| w.as_ref().map(|w| (i, &w.name[..w.name_len], self.front == Some(i))))
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Copy> Default for Windows<N> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user