danos/library/client/display/display-client.zig

166 lines
7.1 KiB
Zig
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! User-space display client: talk to the display service (query the mode, and — from D3
//! — create layers, draw, and present) without hand-rolling the IPC. The `runtime.block`
//! shape: a cached `/protocol/display` open with a boot-race retry, then extern-struct request/
//! reply marshalling. See system/services/display/ and docs/display.md.
const std = @import("std");
const channel = @import("channel");
const envelope = @import("envelope");
const ipc = @import("ipc");
const time = @import("time");
const display_protocol = @import("display-protocol");
const Protocol = display_protocol.Protocol;
/// The display's current mode, as `info()` reports it.
pub const Info = struct {
width: u32,
height: u32,
pitch: u32, // bytes per row (may exceed width*4; see docs/framebuffer.md)
format: u32, // a device-abi DisplayFormat value (0 = rgbx, 1 = bgrx)
};
/// The service endpoint, looked up once and cached.
var handle: ?ipc.Handle = null;
/// Open `/protocol/display`, retrying while it comes up (a client races the
/// service's bind at boot). Returns the endpoint, or null if it never appears.
fn service() ?ipc.Handle {
if (handle) |h| return h;
var attempts: usize = 0;
while (attempts < 100) : (attempts += 1) {
if (channel.openEndpoint("display")) |h| {
handle = h;
return h;
}
time.sleepMillis(50);
}
return null;
}
/// A reply the compositor answered with, kept whole so the caller can decode the
/// verb's own fixed part out of it.
const Answered = struct {
packet: [display_protocol.message_maximum]u8,
len: usize,
fn bytes(self: *const Answered) []const u8 {
return self.packet[0..self.len];
}
};
/// Send one request (`target` addresses a layer, or 0 for the compositor itself)
/// and keep the reply. Null when the transport failed or the compositor refused.
fn transact(
comptime operation: Protocol.Operation,
target: u64,
request: Protocol.RequestOf(operation),
tail: []const u8,
) ?Answered {
const h = service() orelse return null;
var packet: [display_protocol.message_maximum]u8 = undefined;
const framed = Protocol.encodeRequest(operation, target, request, tail, &packet) orelse return null;
var answered: Answered = .{ .packet = undefined, .len = 0 };
answered.len = ipc.call(h, framed, &answered.packet) catch return null;
const status = envelope.statusOf(answered.bytes()) orelse return null;
if (status.status != 0) return null;
return answered;
}
/// The display's current mode, or null if the service never came up.
pub fn info() ?Info {
const answered = transact(.info, 0, {}, &.{}) orelse return null;
const reply = Protocol.decodeReply(.info, answered.bytes()) orelse return null;
return .{ .width = reply.width, .height = reply.height, .pitch = reply.pitch, .format = reply.format };
}
/// Composite the dirty layers and flush the frame to the screen.
pub fn present() bool {
return transact(.present, 0, {}, &.{}) != null;
}
/// One selectable display mode.
pub const Mode = display_protocol.Mode;
/// Fill `out` with the resolutions the display can switch to; returns how many were written
/// (zero on the GOP floor, or if the service never came up).
pub fn modes(out: []Mode) usize {
const answered = transact(.get_modes, 0, {}, &.{}) orelse return 0;
const offered = Protocol.decodeReply(.get_modes, answered.bytes()) orelse return 0;
const count = @min(@min(offered.count, display_protocol.max_modes), out.len);
for (0..count) |i| out[i] = offered.modes[i];
return count;
}
/// Change the display resolution. Only a native backend that supports mode-setting honours it
/// (on the GOP floor it returns false); on success the display's `info()` reports the new mode.
pub fn setMode(width: u32, height: u32) bool {
const changed = transact(.set_mode, 0, .{ .width = width, .height = height }, &.{}) != null;
if (changed) mode = null; // the cached mode is stale now
return changed;
}
/// The mode, cached after the first `info()` so `color()` doesn't round-trip per pixel.
var mode: ?Info = null;
fn cachedInfo() ?Info {
if (mode) |m| return m;
const i = info() orelse return null;
mode = i;
return i;
}
/// The native pixel value for an 8-bit-per-channel colour, in the display's format. A
/// client packs colours through this so it never has to know the byte order itself.
pub fn color(r: u8, g: u8, b: u8) u32 {
const format = if (cachedInfo()) |i| i.format else 0;
return display_protocol.pack(format, r, g, b);
}
/// A handle to a server-owned layer: a positioned, z-ordered surface the client draws
/// into by command. Create with `createLayer`; drawing and moves take effect on the next
/// `present`. Coordinates are signed (a layer may sit partly off-screen).
///
/// The id is the packet header's `target` on every call below, so it is named once
/// per request rather than repeated inside one.
pub const Layer = struct {
id: u32,
/// Fill a rectangle of this layer (layer-local coordinates) with a native `colour`.
pub fn fill(self: Layer, x: i32, y: i32, w: u32, h: u32, colour: u32) bool {
return transact(.fill_rect, self.id, .{ .x = x, .y = y, .width = w, .height = h, .colour = colour }, &.{}) != null;
}
/// Copy a `w`×`h` tile of native pixels (row-major, little-endian bytes) into this
/// layer at (`x`, `y`). The tile rides inline as the request's tail, so `w*h*4` must
/// fit `display_protocol.maximum_payload` — the bound the protocol derives from this
/// verb's own fixed part, so the check here can never drift from what fits.
pub fn blitTile(self: Layer, x: i32, y: i32, w: u32, h: u32, pixels: []const u8) bool {
if (pixels.len > display_protocol.maximum_payload) return false;
return transact(.blit_tile, self.id, .{ .x = x, .y = y, .width = w, .height = h }, pixels) != null;
}
/// Move / restack / show or hide the layer.
pub fn configure(self: Layer, x: i32, y: i32, z: u32, visible: bool) bool {
return transact(.configure_layer, self.id, .{ .x = x, .y = y, .z = z, .visible = if (visible) 1 else 0 }, &.{}) != null;
}
/// Mark a rectangle of this layer (layer-local) dirty for the next present — for when
/// the layer's pixels changed without a drawing call the compositor already tracked.
pub fn damage(self: Layer, x: i32, y: i32, w: u32, h: u32) bool {
return transact(.damage, self.id, .{ .x = x, .y = y, .width = w, .height = h }, &.{}) != null;
}
/// Release the layer and its surface.
pub fn destroy(self: Layer) bool {
return transact(.destroy_layer, self.id, {}, &.{}) != null;
}
};
/// Create a server-owned layer of `w`×`h` pixels at screen (`x`, `y`) with stacking order
/// `z` (higher is nearer the front), initially visible. Returns a handle, or null.
pub fn createLayer(x: i32, y: i32, w: u32, h: u32, z: u32) ?Layer {
const answered = transact(.create_layer, 0, .{ .x = x, .y = y, .width = w, .height = h, .z = z, .visible = 1 }, &.{}) orelse return null;
return .{ .id = (Protocol.decodeReply(.create_layer, answered.bytes()) orelse return null).layer };
}