//! 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 ipc = @import("ipc"); const time = @import("time"); const display_protocol = @import("display-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; } /// Send one request, receive its reply; true on a zero status. `out` receives the reply /// so callers can read `info`/`layer` fields on success. fn transact(request: display_protocol.Request, out: *display_protocol.Reply) bool { const h = service() orelse return false; var req = request; var reply: [display_protocol.reply_size]u8 = undefined; const len = ipc.call(h, std.mem.asBytes(&req), &reply) catch return false; if (len < display_protocol.reply_size) return false; out.* = std.mem.bytesToValue(display_protocol.Reply, reply[0..display_protocol.reply_size]); return out.status == 0; } /// The display's current mode, or null if the service never came up. pub fn info() ?Info { var reply: display_protocol.Reply = undefined; if (!transact(.{ .operation = @intFromEnum(display_protocol.Operation.info) }, &reply)) 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 { var reply: display_protocol.Reply = undefined; return transact(.{ .operation = @intFromEnum(display_protocol.Operation.present) }, &reply); } /// 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 h = service() orelse return 0; var request = display_protocol.Request{ .operation = @intFromEnum(display_protocol.Operation.get_modes) }; var reply: [display_protocol.modes_reply_size]u8 = undefined; const len = ipc.call(h, std.mem.asBytes(&request), &reply) catch return 0; if (len < display_protocol.modes_reply_size) return 0; const answer = std.mem.bytesToValue(display_protocol.ModesReply, reply[0..display_protocol.modes_reply_size]); if (answer.status != 0) return 0; const count = @min(@min(answer.count, display_protocol.max_modes), out.len); for (0..count) |i| out[i] = answer.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 { var reply: display_protocol.Reply = undefined; const changed = transact(.{ .operation = @intFromEnum(display_protocol.Operation.set_mode), .width = width, .height = height }, &reply); 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). 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 { var reply: display_protocol.Reply = undefined; return transact(.{ .operation = @intFromEnum(display_protocol.Operation.fill_rect), .layer = self.id, .x = @bitCast(x), .y = @bitCast(y), .width = w, .height = h, .colour = colour, }, &reply); } /// Copy a `w`×`h` tile of native pixels (row-major, little-endian bytes) into this /// layer at (`x`, `y`). The tile rides inline in the request, so `w*h*4` must fit /// `display_protocol.maximum_payload`. pub fn blitTile(self: Layer, x: i32, y: i32, w: u32, h: u32, pixels: []const u8) bool { var request = display_protocol.Request{ .operation = @intFromEnum(display_protocol.Operation.blit_tile), .layer = self.id, .x = @bitCast(x), .y = @bitCast(y), .width = w, .height = h, }; const header = std.mem.asBytes(&request); if (header.len + pixels.len > display_protocol.message_maximum) return false; var buffer: [display_protocol.message_maximum]u8 = undefined; @memcpy(buffer[0..header.len], header); @memcpy(buffer[header.len..][0..pixels.len], pixels); const h_svc = service() orelse return false; var reply: [display_protocol.reply_size]u8 = undefined; const len = ipc.call(h_svc, buffer[0 .. header.len + pixels.len], &reply) catch return false; if (len < display_protocol.reply_size) return false; return std.mem.bytesToValue(display_protocol.Reply, reply[0..display_protocol.reply_size]).status == 0; } /// Move / restack / show or hide the layer. pub fn configure(self: Layer, x: i32, y: i32, z: u32, visible: bool) bool { var reply: display_protocol.Reply = undefined; return transact(.{ .operation = @intFromEnum(display_protocol.Operation.configure_layer), .layer = self.id, .x = @bitCast(x), .y = @bitCast(y), .z = z, .visible = if (visible) 1 else 0, }, &reply); } /// 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 { var reply: display_protocol.Reply = undefined; return transact(.{ .operation = @intFromEnum(display_protocol.Operation.damage), .layer = self.id, .x = @bitCast(x), .y = @bitCast(y), .width = w, .height = h, }, &reply); } /// Release the layer and its surface. pub fn destroy(self: Layer) bool { var reply: display_protocol.Reply = undefined; return transact(.{ .operation = @intFromEnum(display_protocol.Operation.destroy_layer), .layer = self.id }, &reply); } }; /// 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 { var reply: display_protocol.Reply = undefined; if (!transact(.{ .operation = @intFromEnum(display_protocol.Operation.create_layer), .x = @bitCast(x), .y = @bitCast(y), .width = w, .height = h, .z = z, .visible = 1, }, &reply)) return null; return .{ .id = reply.layer }; }