|
|
|
|
@@ -1,86 +1,188 @@
|
|
|
|
|
//! The window protocol's client (`types::display::window`). A launcher
|
|
|
|
|
//! opens a window for an app and passes it on; the app maps the window's
|
|
|
|
|
//! pixel buffer as a `Canvas`, draws into it (`raster` draws text) and
|
|
|
|
|
//! presents what changed; the display shows it while the window is in
|
|
|
|
|
//! front.
|
|
|
|
|
|
|
|
|
|
use crate::syscall;
|
|
|
|
|
use crate::window::Window;
|
|
|
|
|
use types::address::{ByteOffset, VirtualAddress};
|
|
|
|
|
use types::bounded::CapSlot;
|
|
|
|
|
use types::display::window::{self as w, PIXEL_BYTES};
|
|
|
|
|
use types::display::{Color, Dimension, ScreenCoord};
|
|
|
|
|
use types::mapping::PagePerms;
|
|
|
|
|
|
|
|
|
|
/// A failed call reads as an error reply (tag `usize::MAX`).
|
|
|
|
|
fn ipc(ep: CapSlot, w0: usize, w1: usize, w2: usize, w3: usize) -> (usize, usize, usize, usize) {
|
|
|
|
|
let r = syscall::request(ep, [w0, w1, w2, w3]).unwrap_or([usize::MAX, 0, 0, 0]);
|
|
|
|
|
(r[0], r[1], r[2], r[3])
|
|
|
|
|
/// Where an app's canvas is mapped.
|
|
|
|
|
const CANVAS_VA: VirtualAddress = VirtualAddress::from_raw(0x0100_0000);
|
|
|
|
|
|
|
|
|
|
/// A window just opened: its capability, to pass on, and its size.
|
|
|
|
|
pub struct Opened {
|
|
|
|
|
pub window: CapSlot,
|
|
|
|
|
pub width: Dimension,
|
|
|
|
|
pub height: Dimension,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn draw_rect(
|
|
|
|
|
ep: CapSlot,
|
|
|
|
|
x: ScreenCoord,
|
|
|
|
|
y: ScreenCoord,
|
|
|
|
|
w: Dimension,
|
|
|
|
|
h: Dimension,
|
|
|
|
|
color: Color,
|
|
|
|
|
) {
|
|
|
|
|
let xy = (x.raw() as usize) | ((y.raw() as usize) << 16);
|
|
|
|
|
let wh = (w.raw() as usize) | ((h.raw() as usize) << 16);
|
|
|
|
|
ipc(ep, 0, xy, wh, color.raw() as usize);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn clear(ep: CapSlot, color: Color) {
|
|
|
|
|
ipc(ep, 1, color.raw() as usize, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn flush(ep: CapSlot) {
|
|
|
|
|
ipc(ep, 2, 0, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_dimensions(ep: CapSlot) -> (Dimension, Dimension) {
|
|
|
|
|
let (tag, w, h, _) = ipc(ep, 3, 0, 0, 0);
|
|
|
|
|
if tag == 1 {
|
|
|
|
|
(Dimension::new(w as u16), Dimension::new(h as u16))
|
|
|
|
|
} else {
|
|
|
|
|
(Dimension::new(0), Dimension::new(0))
|
|
|
|
|
/// A launcher opens a window named `name` (what the OS bar shows).
|
|
|
|
|
pub fn open(display: CapSlot, name: &[u8]) -> Option<Opened> {
|
|
|
|
|
let n = w::pack_name(name);
|
|
|
|
|
match syscall::call(display, [w::OPEN, n[0], n[1], n[2]], None).ok()? {
|
|
|
|
|
syscall::Received::Message { words: [w::OK, width, height, _], cap: Some(window), .. } => {
|
|
|
|
|
Some(Opened { window, width: Dimension::new(width as u16), height: Dimension::new(height as u16) })
|
|
|
|
|
}
|
|
|
|
|
_ => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Draws `s` (up to `TEXT_BYTES` per call, more in several) from `(x, y)`.
|
|
|
|
|
pub fn draw_text(ep: CapSlot, x: ScreenCoord, y: ScreenCoord, fg: Color, bg: Color, s: &[u8]) {
|
|
|
|
|
use types::display::text::{TEXT_BYTES, Text};
|
|
|
|
|
for (i, chunk) in s.chunks(TEXT_BYTES).enumerate() {
|
|
|
|
|
let x = ScreenCoord::new(x.raw().saturating_add((i * TEXT_BYTES * CELL_WIDTH) as u16));
|
|
|
|
|
let _ = syscall::request(ep, Text::new(x, y, fg, bg, chunk).words());
|
|
|
|
|
}
|
|
|
|
|
/// Closes `window`: every copy of it, and its buffer, stop working.
|
|
|
|
|
pub fn close(window: CapSlot) {
|
|
|
|
|
let _ = syscall::request(window, [w::CLOSE, 0, 0, 0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Moves the rows from `top`, `height` tall, up by `by`, filling the rows
|
|
|
|
|
/// left at the bottom with `fill`.
|
|
|
|
|
pub fn scroll(ep: CapSlot, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color) {
|
|
|
|
|
let band = (top.raw() as usize) | ((height.raw() as usize) << 16);
|
|
|
|
|
let _ = syscall::request(ep, [types::display::SCROLL, band, by.raw() as usize, fill.raw() as usize]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The display server's character cell, in pixels.
|
|
|
|
|
pub const CELL_WIDTH: usize = 8;
|
|
|
|
|
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;
|
|
|
|
|
let notify = syscall::create_notification().ok()?;
|
|
|
|
|
subscribe(ep, notify, Badge::NONE).then_some(notify)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Takes input focus, to be told through `notify` with `badge`'s bits.
|
|
|
|
|
pub fn subscribe(ep: CapSlot, notify: CapSlot, badge: types::badge::Badge) -> bool {
|
|
|
|
|
/// Takes input for `window`: `notify` is signalled with `badge`'s bits
|
|
|
|
|
/// when some arrives while it is in front; read it with `poll_input`.
|
|
|
|
|
pub fn subscribe(window: CapSlot, notify: CapSlot, badge: types::badge::Badge) -> bool {
|
|
|
|
|
use types::permission::{Permission, PermissionSet};
|
|
|
|
|
// Signal only (Delegate just to send it; the transfer strips it).
|
|
|
|
|
let signal = PermissionSet::empty().with(Permission::Write).with(Permission::Delegate);
|
|
|
|
|
let Ok(copy) = syscall::mint(notify, badge, signal) else {
|
|
|
|
|
return false;
|
|
|
|
|
};
|
|
|
|
|
let sent = syscall::call(ep, [types::display::input::SUBSCRIBE, 0, 0, 0], Some(copy.into()));
|
|
|
|
|
let sent = syscall::call(window, [w::SUBSCRIBE, 0, 0, 0], Some(copy.into()));
|
|
|
|
|
syscall::drop_cap(copy);
|
|
|
|
|
sent.is_ok_and(|r| r.words()[0] == 0)
|
|
|
|
|
sent.is_ok_and(|r| r.words()[0] == w::OK)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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()?;
|
|
|
|
|
/// `window`'s next input event, if one is waiting.
|
|
|
|
|
pub fn poll_input(window: CapSlot) -> Option<crate::input::InputEvent> {
|
|
|
|
|
let words = syscall::request(window, [w::POLL, 0, 0, 0]).ok()?;
|
|
|
|
|
crate::input::decode(words)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A changed area, as corners.
|
|
|
|
|
#[derive(Clone, Copy)]
|
|
|
|
|
struct Damage {
|
|
|
|
|
x0: u16,
|
|
|
|
|
y0: u16,
|
|
|
|
|
x1: u16,
|
|
|
|
|
y1: u16,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A window's pixels, mapped here. Draw with the `Framebuffer` methods (and
|
|
|
|
|
/// `raster`); `flush` presents everything drawn since the last one.
|
|
|
|
|
pub struct Canvas {
|
|
|
|
|
window: CapSlot,
|
|
|
|
|
pixels: Window,
|
|
|
|
|
width: Dimension,
|
|
|
|
|
height: Dimension,
|
|
|
|
|
damage: Option<Damage>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Canvas {
|
|
|
|
|
/// Maps `window`'s buffer.
|
|
|
|
|
pub fn attach(window: CapSlot) -> Option<Self> {
|
|
|
|
|
let (words, buffer) = match syscall::call(window, [w::BUFFER, 0, 0, 0], None).ok()? {
|
|
|
|
|
syscall::Received::Message { words, cap: Some(buffer), .. } if words[0] == w::OK => (words, buffer),
|
|
|
|
|
_ => return None,
|
|
|
|
|
};
|
|
|
|
|
let pixels = crate::window::map(buffer, CANVAS_VA, PagePerms::ReadWrite).ok()?;
|
|
|
|
|
let (width, height) = (Dimension::new(words[1] as u16), Dimension::new(words[2] as u16));
|
|
|
|
|
// The display says how big; the mapping says how much is there.
|
|
|
|
|
(width.raw() as usize * height.raw() as usize * PIXEL_BYTES <= pixels.len())
|
|
|
|
|
.then_some(Self { window, pixels, width, height, damage: None })
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn window(&self) -> CapSlot {
|
|
|
|
|
self.window
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn offset(&self, x: u16, y: u16) -> ByteOffset {
|
|
|
|
|
ByteOffset::new((y as usize * self.width.raw() as usize + x as usize) * PIXEL_BYTES)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Marks `x..x+w`, `y..y+h` (already clipped) as changed.
|
|
|
|
|
fn touch(&mut self, x: u16, y: u16, w: u16, h: u16) {
|
|
|
|
|
let (x1, y1) = (x + w, y + h);
|
|
|
|
|
self.damage = Some(match self.damage {
|
|
|
|
|
None => Damage { x0: x, y0: y, x1, y1 },
|
|
|
|
|
Some(d) => Damage { x0: d.x0.min(x), y0: d.y0.min(y), x1: d.x1.max(x1), y1: d.y1.max(y1) },
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The part of a rectangle inside the canvas, if any.
|
|
|
|
|
fn clip(&self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension) -> Option<(u16, u16, u16, u16)> {
|
|
|
|
|
let (cw, ch) = (self.width.raw(), self.height.raw());
|
|
|
|
|
let (x, y) = (x.raw(), y.raw());
|
|
|
|
|
(x < cw && y < ch).then(|| (x, y, w.raw().min(cw - x), h.raw().min(ch - y)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl port_framebuffer::Framebuffer for Canvas {
|
|
|
|
|
fn width(&self) -> Dimension {
|
|
|
|
|
self.width
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn height(&self) -> Dimension {
|
|
|
|
|
self.height
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
|
|
|
|
|
if let Some((x, y, _, _)) = self.clip(x, y, Dimension::new(1), Dimension::new(1)) {
|
|
|
|
|
self.pixels.write(self.offset(x, y), color.raw());
|
|
|
|
|
self.touch(x, y, 1, 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn write_row(&mut self, x: ScreenCoord, y: ScreenCoord, row: &[Color]) {
|
|
|
|
|
let Some((x, y, w, _)) = self.clip(x, y, Dimension::new(row.len().min(u16::MAX as usize) as u16), Dimension::new(1)) else { return };
|
|
|
|
|
let mut run = [0u32; 64];
|
|
|
|
|
for (i, chunk) in row[..w as usize].chunks(run.len()).enumerate() {
|
|
|
|
|
for (slot, c) in run.iter_mut().zip(chunk) {
|
|
|
|
|
*slot = c.raw();
|
|
|
|
|
}
|
|
|
|
|
self.pixels.write_u32s(self.offset(x + (i * 64) as u16, y), &run[..chunk.len()]);
|
|
|
|
|
}
|
|
|
|
|
self.touch(x, y, w, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) {
|
|
|
|
|
let Some((x, y, w, h)) = self.clip(x, y, w, h) else { return };
|
|
|
|
|
for row in y..y + h {
|
|
|
|
|
self.pixels.fill_u32(self.offset(x, row), w as usize, color.raw());
|
|
|
|
|
}
|
|
|
|
|
self.touch(x, y, w, h);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn clear(&mut self, color: Color) {
|
|
|
|
|
let (w, h) = (self.width, self.height);
|
|
|
|
|
self.fill_rect(ScreenCoord::new(0), ScreenCoord::new(0), w, h, color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Presents what changed since the last flush.
|
|
|
|
|
fn flush(&mut self) {
|
|
|
|
|
let Some(d) = self.damage.take() else { return };
|
|
|
|
|
let rect = w::pack_rect(ScreenCoord::new(d.x0), ScreenCoord::new(d.y0), Dimension::new(d.x1 - d.x0), Dimension::new(d.y1 - d.y0));
|
|
|
|
|
let _ = syscall::request(self.window, [w::PRESENT, rect[0], rect[1], 0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn scroll_up(&mut self, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color) {
|
|
|
|
|
let Some((_, top, _, height)) = self.clip(ScreenCoord::new(0), top, self.width, height) else { return };
|
|
|
|
|
let by = by.raw().min(height);
|
|
|
|
|
let row = self.width.raw() as usize * PIXEL_BYTES;
|
|
|
|
|
let kept = (height - by) as usize * row;
|
|
|
|
|
self.pixels.copy_within(self.offset(0, top + by), self.offset(0, top), kept);
|
|
|
|
|
let width = self.width;
|
|
|
|
|
self.fill_rect(ScreenCoord::new(0), ScreenCoord::new(top + height - by), width, Dimension::new(by), fill);
|
|
|
|
|
self.touch(0, top, width.raw(), height);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// From init only: who is signed in, shown in the OS bar.
|
|
|
|
|
pub fn set_user_words(name: &[u8]) -> [usize; 4] {
|
|
|
|
|
let n = w::pack_name(name);
|
|
|
|
|
[w::SET_USER, n[0], n[1], n[2]]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// From init only: the launcher behind `badge` is gone, and its windows.
|
|
|
|
|
pub fn forget_words(badge: usize) -> [usize; 4] {
|
|
|
|
|
[w::FORGET, badge, 0, 0]
|
|
|
|
|
}
|
|
|
|
|
|