shithub: libdraw-zig

Download patch

ref: 7b862d8b0b26827d4b3ac55b459002a9e5d8b2e1
parent: 467a840b86b9ef31e44897566f789b78e3d051f8
author: Jacob G-W <jacoblevgw@gmail.com>
date: Sun Jul 16 17:39:47 EDT 2023

add build.sh script until x86_64 backend can compile build.zig

diff: cannot open b/examples//null: file does not exist: 'b/examples//null' diff: cannot open b/src//null: file does not exist: 'b/src//null'
--- a/.gitignore
+++ b/.gitignore
@@ -1,1 +1,2 @@
-test
+zig-*/
+main
--- /dev/null
+++ b/build.sh
@@ -1,0 +1,3 @@
+#!/usr/bin/env bash 
+# just a temporary fix until the x86_64 backend supports build.zig
+~/dev/zig/build/debug/bin/zig build-exe examples/main.zig --main-pkg-path . -target x86_64-plan9-none -freference-trace -fsingle-threaded --zig-lib-dir ~/dev/zig/lib
--- /dev/null
+++ b/build.zig
@@ -1,0 +1,36 @@
+const std = @import("std");
+
+pub fn build(b: *std.Build) void {
+    // Standard target options allows the person running `zig build` to choose
+    // what target to build for. Here we do not override the defaults, which
+    // means any target is allowed, and the default is native. Other options
+    // for restricting supported target set are available.
+    const target = b.standardTargetOptions(.{ .default_target = .{ .os_tag = .plan9 } });
+
+    // Standard optimization options allow the person running `zig build` to select
+    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
+    // set a preferred release mode, allowing the user to decide how to optimize.
+    const optimize = b.standardOptimizeOption(.{});
+
+    const lib = b.addStaticLibrary(.{
+        .name = "libdraw-zig",
+        // In this case the main source file is merely a path, however, in more
+        // complicated build scripts, this could be a generated file.
+        .root_source_file = .{ .path = "src/libdraw.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    // This declares intent for the library to be installed into the standard
+    // location when the user invokes the "install" step (the default step when
+    // running `zig build`).
+    b.installArtifact(lib);
+
+    const ex = b.addExecutable(.{ .name = "ldz" });
+
+    // This creates a build step. It will be visible in the `zig build --help` menu,
+    // and can be selected like this: `zig build test`
+    // This will evaluate the `test` step rather than the default, which is "install".
+    const example = b.step("example", "Run main example");
+    example.dependOn(&ex.step);
+}
--- /dev/null
+++ b/examples/main.zig
@@ -1,0 +1,14 @@
+const std = @import("std");
+const ld = @import("../src/libdraw.zig");
+pub fn main() !void {
+    const ally = std.heap.page_allocator;
+    const d = ld.initDraw(ally, null, "bruh") catch |e| {
+        std.debug.print("errstr: {s}\n", .{std.os.plan9.errstr()});
+        return e;
+    };
+    const screen = d.getScreen();
+    var buf: [128]u8 = undefined;
+    _ = buf;
+    try screen.draw(screen.r, d.white, null, ld.Point.Zero);
+    try d.flushImage(true);
+}
--- a/fbuild.sh
+++ /dev/null
@@ -1,2 +1,0 @@
-#!/usr/bin/env bash
- ~/dev/zig/build/debug/bin/zig build-exe test.zig -target x86_64-plan9-none -fsingle-threaded --zig-lib-dir ~/dev/zig/lib $@
--- /dev/null
+++ b/src/libdraw.zig
@@ -1,0 +1,780 @@
+const std = @import("std");
+
+pub fn parseIntSkipPreceedingSpaces(comptime T: type, buf: []const u8) !T {
+    var i: u32 = 0;
+    while (buf[i] == ' ') i += 1;
+    const int = try std.fmt.parseInt(T, buf[i..], 10);
+    return int;
+}
+
+pub const Image = struct {
+    display: *Display, // display holding data
+    id: u32, // id of system-held Image
+    r: Rectangle, // rectangle in data area, local coords
+    clipr: Rectangle, // clipping region
+    depth: u32, // number of bits per pixel
+    chan: Chan,
+    repl: bool, // flag: data replicates to tile clipr
+    screen: ?*Screen, // 0 if not a window
+    next: ?*Image, // next in list of windows
+    fn allocScreen(image: *Image, fill: *Image, public: bool) !*Screen {
+        const d = image.display;
+        if (d != fill.display)
+            return error.ImageAndFillOnDifferentDisplays;
+        var s = try d.ally.create(Screen);
+        errdefer d.ally.destroy(s);
+        if (screenid == 0) {
+            screenid = std.os.plan9.getpid();
+        }
+        var id: u32 = 0;
+        var trys: usize = 0;
+        while (trys < 25) : (trys += 1) {
+            var bs = try d.bufImage(1 + 4 + 4 + 4 + 1);
+            var a = bs.writer();
+            screenid += 1;
+            id = screenid & 0xffff; // old devdraw bug
+            a.writeByte('A') catch unreachable;
+            a.writeIntLittle(u32, id) catch unreachable;
+            a.writeIntLittle(u32, image.id) catch unreachable;
+            a.writeIntLittle(u32, fill.id) catch unreachable;
+            a.writeByte(@intFromBool(public)) catch unreachable;
+            try d.flushImage(false);
+        }
+        s.display = d;
+        s.id = id;
+        s.image = image;
+        s.fill = fill;
+        return s;
+    }
+    pub fn free(self: *Image) !void {
+        try Display.freeImage1(self);
+        self.display.ally.destroy(self);
+    }
+    pub fn line(dest: *Image, p0: Point, p1: Point, end0: u32, end1: u32, radius: u32, src: *Image, sp: Point) !void {
+        return dest.lineop(p0, p1, end0, end1, radius, src, sp, DrawOp.SoverD);
+    }
+    pub fn lineop(dst: *Image, p0: Point, p1: Point, end0: u32, end1: u32, radius: u32, src: *Image, sp: Point, op: DrawOp) !void {
+        const d = dst.display;
+        try d.setDrawOp(op);
+        var bs = try d.bufImage(1 + 4 + 2 * 4 + 2 * 4 + 4 + 4 + 4 + 4 + 2 * 4);
+        var a = bs.writer();
+        a.writeByte('L') catch unreachable;
+        a.writeIntLittle(u32, dst.id) catch unreachable;
+        a.writeIntLittle(u32, p0.x) catch unreachable;
+        a.writeIntLittle(u32, p0.y) catch unreachable;
+        a.writeIntLittle(u32, p1.x) catch unreachable;
+        a.writeIntLittle(u32, p1.y) catch unreachable;
+        a.writeIntLittle(u32, end0) catch unreachable;
+        a.writeIntLittle(u32, end1) catch unreachable;
+        a.writeIntLittle(u32, radius) catch unreachable;
+        a.writeIntLittle(u32, src.id) catch unreachable;
+        a.writeIntLittle(u32, sp.x) catch unreachable;
+        a.writeIntLittle(u32, sp.y) catch unreachable;
+    }
+    pub fn draw1(dst: *Image, r: Rectangle, src: ?*Image, p0: Point, mask: ?*Image, p1: Point, op: DrawOp) !void {
+        const d = dst.display;
+        try d.setDrawOp(op);
+
+        var bs = try d.bufImage(1 + 4 + 4 + 4 + 4 * 4 + 2 * 4 + 2 * 4);
+        var a = bs.writer();
+        const s = src orelse d.black;
+        const m = mask orelse d.@"opaque";
+        a.writeByte('d') catch unreachable;
+        a.writeIntLittle(u32, dst.id) catch unreachable;
+        a.writeIntLittle(u32, s.id) catch unreachable;
+        a.writeIntLittle(u32, m.id) catch unreachable;
+        a.writeIntLittle(u32, @as(u32, @bitCast(r.min.x))) catch unreachable;
+        a.writeIntLittle(u32, @as(u32, @bitCast(r.min.y))) catch unreachable;
+        a.writeIntLittle(u32, @as(u32, @bitCast(r.max.x))) catch unreachable;
+        a.writeIntLittle(u32, @as(u32, @bitCast(r.max.y))) catch unreachable;
+        a.writeIntLittle(u32, @as(u32, @bitCast(p0.x))) catch unreachable;
+        a.writeIntLittle(u32, @as(u32, @bitCast(p0.y))) catch unreachable;
+        a.writeIntLittle(u32, @as(u32, @bitCast(p1.x))) catch unreachable;
+        a.writeIntLittle(u32, @as(u32, @bitCast(p1.y))) catch unreachable;
+    }
+
+    pub fn draw(dst: *Image, r: Rectangle, src: *Image, mask: ?*Image, p1: Point) !void {
+        return draw1(dst, r, src, p1, mask, p1, .soverD);
+    }
+
+    pub fn drawop(dst: *Image, r: Rectangle, src: ?*Image, mask: ?*Image, p1: Point, op: DrawOp) !void {
+        return draw1(dst, r, src, p1, mask, p1, op);
+    }
+
+    pub fn gendraw(dst: *Image, r: Rectangle, src: ?*Image, p0: Point, mask: ?*Image, p1: Point) !void {
+        return draw1(dst, r, src, p0, mask, p1, .soverD);
+    }
+
+    pub fn gendrawop(dst: *Image, r: Rectangle, src: ?*Image, p0: Point, mask: ?*Image, p1: Point, op: DrawOp) !void {
+        return draw1(dst, r, src, p0, mask, p1, op);
+    }
+};
+/// Porter-Duff compositing operators
+const DrawOp = enum(u8) {
+    pub const Clear = 0;
+
+    pub const SinD = 8;
+    pub const DinS = 4;
+    pub const SoutD = 2;
+    pub const DoutS = 1;
+
+    pub const S = SinD | SoutD;
+    pub const SoverD = SinD | SoutD | DoutS;
+    pub const SatopD = SinD | DoutS;
+    pub const SxorD = SoutD | DoutS;
+
+    pub const D = DinS | DoutS;
+    pub const DoverS = DinS | DoutS | SoutD;
+    pub const DatopS = DinS | SoutD;
+    pub const DxorS = DoutS | SoutD; // == SxorD
+
+    pub const Ncomp = 12;
+
+    sinD = SinD,
+    dinS = DinS,
+    soutD = SoutD,
+    doutS = DoutS,
+
+    s = S,
+    soverD = SoverD,
+    satopD = SatopD,
+    sxorD = SxorD,
+
+    d = D,
+    doverS = DoverS,
+    datopS = DatopS,
+    // dxorS = DxorS, // == SxorD TODO have multiple enum vals with the same name
+
+};
+var screenid: u32 = 0;
+pub const Point = struct {
+    x: i32,
+    y: i32,
+    pub const Zero: Point = .{ .x = 0, .y = 0 };
+};
+pub const Rectangle = struct {
+    min: Point,
+    max: Point,
+    pub const Zero: Rectangle = .{ .min = Point.Zero, .max = Point.Zero };
+    pub fn init(min_x: i32, min_y: i32, max_x: i32, max_y: i32) Rectangle {
+        return .{
+            .min = .{
+                .x = min_x,
+                .y = min_y,
+            },
+            .max = .{
+                .x = max_x,
+                .y = max_y,
+            },
+        };
+    }
+    pub fn isBad(self: Rectangle) bool {
+        const x = self.dX();
+        const y = self.dY();
+        if (x > 0 and y > 0) {
+            const z = x * y;
+            if (@divFloor(z, x) == y and z < 0x10000000) return false;
+        }
+        return true;
+    }
+    pub fn dX(self: Rectangle) i64 {
+        return self.max.x - self.min.x;
+    }
+    pub fn dY(self: Rectangle) i64 {
+        return self.max.y - self.min.y;
+    }
+    pub fn inset(self: Rectangle, n: i32) Rectangle {
+        var r = self;
+        r.min.x += n;
+        r.min.y += n;
+        r.max.x -= n;
+        r.max.y -= n;
+        return r;
+    }
+};
+pub const Screen = struct {
+    display: *Display, // display holding data
+    id: u32, // id of system-held Screen
+    image: *Image, // unused; for reference only
+    fill: *Image, // color to paint behind windows
+    fn free(self: *Screen) !void {
+        const d = self.display;
+        try d.freeRemote(self.id, .screen);
+        d.ally.destroy(self);
+    }
+};
+pub const Display = struct {
+    ally: std.mem.Allocator,
+    qlock: void, // some sort of mutex???
+    locking: bool, // program is using lockdisplay
+    dirno: u32, // the window id
+    fd: std.fs.File,
+    reffd: std.fs.File,
+    ctlfd: std.fs.File,
+    imageid: u32 = 0,
+    local: u32,
+    @"error": void, //  void		(*error)(Display*, char*);
+    devdir: []const u8 = "/dev",
+    windir: []const u8 = "/dev",
+    oldlabel: [64]u8,
+    dataqid: u64,
+    white: *Image,
+    black: *Image,
+    @"opaque": *Image,
+    transparent: *Image,
+    image: ?*Image,
+    buf: []u8,
+    bufsize: u32,
+    bufp: [*]u8,
+    defaultfont: void, // TODO deal with this
+    subfont: void, // TODO deal with this
+    windows: ?*Image,
+    screenimage: ?*Image,
+    _isnewdisplay: bool,
+    screen: ?*Image = null,
+    _screen: ?*Screen = null,
+    pub fn init(ally: std.mem.Allocator, options: struct { devdir: []const u8 = "/dev", windir: []const u8 = "/dev" }) !*Display {
+        const NINFO = 12 * 12;
+        var info: [NINFO + 1]u8 = undefined;
+        var buf: [512]u8 = undefined;
+        var image: ?*Image = null;
+        const ctlfd = try std.fs.openFileAbsolute(try std.fmt.bufPrint(&buf, "{s}/draw/new", .{options.devdir}), .{ .mode = .read_write });
+        errdefer ctlfd.close();
+        var n = try ctlfd.read(&info);
+        if (n < 12) {
+            return error.InvalidReadFromDrawCtl;
+        }
+        if (n == NINFO + 1) n = NINFO;
+        info[n] = 0;
+        const infoslice = info[0..n];
+        var isnew: bool = false;
+        if (n < NINFO) isnew = true;
+        const winnum = try parseIntSkipPreceedingSpaces(u32, infoslice[0 .. 1 * 12 - 1]);
+        const datafd = try std.fs.openFileAbsolute(try std.fmt.bufPrint(&buf, "{s}/draw/{d}/data", .{ options.devdir, winnum }), .{ .mode = .read_write });
+        errdefer datafd.close();
+        const reffd = try std.fs.openFileAbsolute(try std.fmt.bufPrint(&buf, "{s}/draw/{d}/refresh", .{ options.devdir, winnum }), .{});
+        errdefer reffd.close();
+        const disp = try ally.create(Display);
+        disp.ally = ally;
+        if (n >= NINFO) {
+            image = try ally.create(Image);
+            errdefer ally.destroy(image.?);
+
+            const chan = Chan.fromString(infoslice[2 * 12 .. 3 * 12 - 1]);
+            image.?.* = .{
+                .display = disp,
+                .id = 0,
+                .chan = chan,
+                .depth = chan.depth(),
+                .repl = try parseIntSkipPreceedingSpaces(u32, infoslice[3 * 12 .. 4 * 12 - 1]) != 0,
+                .r = .{
+                    .min = .{
+                        .x = try parseIntSkipPreceedingSpaces(i32, infoslice[4 * 12 .. 5 * 12 - 1]),
+                        .y = try parseIntSkipPreceedingSpaces(i32, infoslice[5 * 12 .. 6 * 12 - 1]),
+                    },
+                    .max = .{
+                        .x = try parseIntSkipPreceedingSpaces(i32, infoslice[6 * 12 .. 7 * 12 - 1]),
+                        .y = try parseIntSkipPreceedingSpaces(i32, infoslice[7 * 12 .. 8 * 12 - 1]),
+                    },
+                },
+                .clipr = .{
+                    .min = .{
+                        .x = try parseIntSkipPreceedingSpaces(i32, infoslice[8 * 12 .. 9 * 12 - 1]),
+                        .y = try parseIntSkipPreceedingSpaces(i32, infoslice[9 * 12 .. 10 * 12 - 1]),
+                    },
+                    .max = .{
+                        .x = try parseIntSkipPreceedingSpaces(i32, infoslice[10 * 12 .. 11 * 12 - 1]),
+                        .y = try parseIntSkipPreceedingSpaces(i32, infoslice[11 * 12 .. 12 * 12 - 1]),
+                    },
+                },
+                .screen = null,
+                .next = null,
+            };
+        }
+        // TODO refactor this into a disp.* = .{ ... } expression
+        const bufsize_iounit = iounit(datafd);
+        const bufsz = if (bufsize_iounit == 0) 8000 else if (disp.bufsize < 512) return error.IounitTooSmall else bufsize_iounit;
+        disp.* = .{
+            .ally = ally,
+            .dirno = winnum,
+            .fd = datafd,
+            .reffd = reffd,
+            .ctlfd = ctlfd,
+            .imageid = 0,
+            .local = 0,
+            .devdir = options.devdir,
+            .windir = options.windir,
+            .oldlabel = .{0} ** 64,
+            .dataqid = 0,
+            .white = undefined, // filled in later
+            .black = undefined, // filled in later
+            .@"opaque" = undefined, // filled in later
+            .transparent = undefined, // filled in later
+            .buf = undefined, // filled in later
+            .bufsize = bufsz,
+            .bufp = undefined, // filled in later
+            .windows = null,
+            .screenimage = null,
+            ._isnewdisplay = isnew,
+            .qlock = {}, // TODO make this an actual lock
+            .locking = false,
+            .@"error" = {}, // TODO audit if we need this
+            .image = image,
+            .defaultfont = {},
+            .subfont = {},
+        };
+        disp.buf = try ally.alloc(u8, bufsz + 5); // +5 for flush message;
+        errdefer ally.free(disp.buf);
+        disp.bufp = disp.buf.ptr;
+        disp.white = try disp.allocImage(Rectangle.init(0, 0, 1, 1), .grey1, true, DColor.White);
+        disp.black = try disp.allocImage(Rectangle.init(0, 0, 1, 1), .grey1, true, DColor.Black);
+        // disp.error = error;
+        disp.windir = try ally.dupe(u8, options.windir);
+        errdefer ally.free(disp.windir);
+        disp.devdir = try ally.dupe(u8, options.devdir);
+        errdefer ally.free(disp.devdir);
+        // qlock(&disp.qlock)
+        disp.@"opaque" = disp.white;
+        disp.transparent = disp.black;
+        return disp;
+    }
+    pub fn allocImage(self: *Display, r: Rectangle, chan: Chan, repl: bool, col: u32) !*Image {
+        return self._allocImage(null, r, chan, repl, col, 0, .backup);
+    }
+    fn _allocImage(self: *Display, ai: ?*Image, r: Rectangle, chan: Chan, repl: bool, col: u32, _screenid: u32, refresh: Refresh) !*Image {
+        if (r.isBad()) {
+            return error.BadRect;
+        }
+        if (@intFromEnum(chan) == 0) {
+            return error.BadChanDesc;
+        }
+        const depth = chan.depth();
+        if (depth == 0) {
+            return error.BadChanDesc;
+        }
+        var bs = try self.bufImage(1 + 4 + 4 + 1 + 4 + 1 + 4 * 4 + 4 * 4 + 4);
+        var a = bs.writer();
+        self.imageid += 1;
+        const id = self.imageid;
+        // start writing the protocol
+        // everything is little endian
+        a.writeByte('b') catch unreachable;
+        a.writeIntLittle(u32, id) catch unreachable;
+        a.writeIntLittle(u32, _screenid) catch unreachable;
+        a.writeByte(@intFromEnum(refresh)) catch unreachable;
+        a.writeIntLittle(u32, @intFromEnum(chan)) catch unreachable;
+        a.writeByte(@intFromBool(repl)) catch unreachable;
+        a.writeIntLittle(i32, r.min.x) catch unreachable;
+        a.writeIntLittle(i32, r.min.y) catch unreachable;
+        a.writeIntLittle(i32, r.max.x) catch unreachable;
+        a.writeIntLittle(i32, r.max.y) catch unreachable;
+        const clipr = if (!repl)
+            Rectangle.init(-0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF)
+        else
+            r;
+        a.writeIntLittle(i32, clipr.min.x) catch unreachable;
+        a.writeIntLittle(i32, clipr.min.y) catch unreachable;
+        a.writeIntLittle(i32, clipr.max.x) catch unreachable;
+        a.writeIntLittle(i32, clipr.max.y) catch unreachable;
+        a.writeIntLittle(u32, col) catch unreachable;
+        var i: *Image = undefined;
+        if (ai) |image| {
+            i = image;
+        } else {
+            i = self.ally.create(Image) catch {
+                try self.freeRemote(id, .image);
+                return error.OutOfMemory;
+            };
+            errdefer self.ally.destroy(i);
+        }
+        i.* = .{ .display = self, .id = id, .depth = depth, .chan = chan, .r = r, .clipr = clipr, .repl = repl, .screen = null, .next = null };
+        return i;
+    }
+    pub fn namedImage(self: *Display, name: []const u8) !*Image {
+        if (name.len > 256) {
+            return error.ImageNameTooLong;
+        }
+        self.flushImage(false) catch {};
+        var bs = try self.bufImage(1 + 4 + 1 + name.len);
+        var a = bs.writer();
+        self.imageid += 1;
+        const id = self.imageid;
+        a.writeByte('n') catch unreachable;
+        a.writeIntLittle(u32, id) catch unreachable;
+        a.writeByte(@intCast(name.len)) catch unreachable;
+        a.writeAll(name) catch unreachable;
+        try self.flushImage(false);
+        var buf: [12 * 12 + 1]u8 = undefined;
+        if (try self.ctlfd.pread(&buf, 0) < 12 * 12) {
+            return error.CtlReadTooShort;
+        }
+        buf[12 * 12] = 0;
+        var i = self.ally.create(Image) catch {
+            try self.freeRemote(id, .image);
+            try self.flushImage(false);
+            return error.OutOfMemory;
+        };
+        errdefer self.ally.destroy(i);
+        const chan = Chan.fromString(buf[2 * 12 .. 3 * 12 - 1]);
+        i.* = .{
+            .display = self,
+            .id = id,
+            .chan = chan,
+            .depth = chan.depth(),
+            .repl = try parseIntSkipPreceedingSpaces(u32, buf[3 * 12 .. 4 * 12 - 1]) != 0,
+            .r = .{
+                .min = .{
+                    .x = try parseIntSkipPreceedingSpaces(i32, buf[4 * 12 .. 5 * 12 - 1]),
+                    .y = try parseIntSkipPreceedingSpaces(i32, buf[5 * 12 .. 6 * 12 - 1]),
+                },
+                .max = .{
+                    .x = try parseIntSkipPreceedingSpaces(i32, buf[6 * 12 .. 7 * 12 - 1]),
+                    .y = try parseIntSkipPreceedingSpaces(i32, buf[7 * 12 .. 8 * 12 - 1]),
+                },
+            },
+            .clipr = .{
+                .min = .{
+                    .x = try parseIntSkipPreceedingSpaces(i32, buf[8 * 12 .. 9 * 12 - 1]),
+                    .y = try parseIntSkipPreceedingSpaces(i32, buf[9 * 12 .. 10 * 12 - 1]),
+                },
+                .max = .{
+                    .x = try parseIntSkipPreceedingSpaces(i32, buf[10 * 12 .. 11 * 12 - 1]),
+                    .y = try parseIntSkipPreceedingSpaces(i32, buf[11 * 12 .. 12 * 12 - 1]),
+                },
+            },
+            .screen = null,
+            .next = null,
+        };
+        return i;
+    }
+    fn _allocWindow(self: *Display, i: ?*Image, s: *Screen, r: Rectangle, ref: Refresh, col: u32) !*Image {
+        var im = try self._allocImage(i, r, self.screenimage.?.chan, false, col, s.id, ref);
+        im.screen = s;
+        im.next = self.windows;
+        self.windows = im;
+        return im;
+    }
+    fn freeRemote(self: *Display, id: u32, t: enum { image, screen }) !void {
+        var bs = try self.bufImage(1 + 4);
+        const a = bs.writer();
+        const c: u8 = if (t == .image) 'f' else 'F';
+        a.writeByte(c) catch unreachable;
+        a.writeIntLittle(u32, id) catch unreachable;
+    }
+    fn freeImage1(image: *Image) !void {
+        const d = image.display;
+        if (image.screen != null) {
+            var w: ?*Image = d.windows;
+            if (w.? == image) {
+                d.windows = image.next;
+            } else {
+                while (w != null) {
+                    if (w.?.next == image) {
+                        w.?.next = image.next;
+                        break;
+                    }
+                    w = w.?.next;
+                }
+            }
+        }
+        try d.freeRemote(image.id, .image);
+    }
+    pub fn bufImage(self: *Display, n: usize) !std.io.FixedBufferStream([]u8) {
+        if (n > self.bufsize) {
+            return error.BadCountBufSize;
+        }
+        if (@intFromPtr(self.bufp + n) > @intFromPtr(self.buf.ptr + self.bufsize)) {
+            try self.flush();
+        }
+        const p = self.bufp;
+        self.bufp += n;
+        return std.io.fixedBufferStream(p[0..n]);
+    }
+    pub fn flush(self: *Display) !void {
+        const n: i64 = @intCast(@intFromPtr(self.bufp) - @intFromPtr(self.buf.ptr));
+        if (n <= 0) return error.UnableToFlushInvalidN;
+        // std.debug.print("about to flush: {}\n{s}\n", .{ std.fmt.fmtSliceHexLower(self.buf[0..@intCast(n)]), self.buf[0..@intCast(n)] });
+        if ((self.fd.write(self.buf[0..@intCast(n)]) catch return error.UnableToFlushWrite) != n) {
+            self.bufp = self.buf.ptr; // might as well; chance of continuing
+            return error.UnableToFlushN;
+        }
+        self.bufp = self.buf.ptr;
+    }
+    pub fn flushImage(self: *Display, visible: bool) !void {
+        if (visible) {
+            self.bufp[0] = 'v';
+            self.bufp += 1;
+            if (self._isnewdisplay) {
+                std.mem.writeIntLittle(u32, self.bufp[0..4], self.screenimage.?.id);
+            }
+        }
+        return self.flush();
+    }
+    pub fn genGetWindow(self: *Display, winname: []const u8, winp: *?*Image, scrp: *?*Screen, ref: Refresh) !void {
+        var buf: [64 + 1]u8 = undefined;
+        var obuf: [64 + 1]u8 = undefined;
+        var image: ?*Image = null;
+        obuf[0] = 0;
+        while (true) {
+            const fd = std.fs.openFileAbsolute(winname, .{}) catch {
+                std.mem.copyForwards(u8, &buf, "noborder");
+                image = self.image;
+                break;
+            };
+            var n: ?usize = fd.read(buf[0..64]) catch null;
+            if (n == 0) n = null; // TODO do I need this?
+            if (n == null) {
+                fd.close();
+                std.mem.copyForwards(u8, &buf, "noborder");
+                image = self.image;
+                break;
+            }
+            // we correctly read in to buf
+            fd.close();
+            image = self.namedImage(buf[0..n.?]) catch |err| {
+                std.debug.print("namedImage: {}\n", .{err});
+                if (!std.mem.eql(u8, buf[0..n.?], obuf[0..n.?])) {
+                    std.debug.print("trying to fix the race\n", .{});
+                    std.mem.copyForwards(u8, obuf[0..n.?], buf[0..n.?]);
+                    continue;
+                }
+                break;
+            };
+            break;
+        }
+        if (winp.*) |i| {
+            try freeImage1(i);
+            if (scrp.*.?.image != self.image)
+                try scrp.*.?.image.free();
+            try scrp.*.?.free();
+            scrp.* = null;
+        }
+        if (image == null) {
+            winp.* = null;
+            self.screenimage = null;
+            return error.CouldNotGetImage; // TODO audit this error
+        }
+        self.screenimage = image.?;
+        scrp.* = image.?.allocScreen(self.white, false) catch |err| {
+            winp.* = null;
+            self.screenimage = null;
+            if (image != self.image) {
+                if (image) |i| try i.free();
+            }
+            return err;
+        };
+        const i = image.?;
+        var r = i.r;
+        if (!std.mem.eql(u8, buf[0..8], "noborder")) {
+            r = r.inset(Borderwidth);
+        }
+        winp.* = self._allocWindow(winp.*, scrp.*.?, r, ref, DColor.White) catch |err| {
+            std.debug.print("could not alloc window {}\n", .{err});
+            try scrp.*.?.free();
+            scrp.* = null;
+            self.screenimage = null;
+            if (image != self.image)
+                if (image) |im|
+                    try im.free();
+            return err;
+        };
+        self.screenimage = winp.*;
+    }
+    pub fn setDrawOp(self: *Display, op: DrawOp) !void {
+        if (op != .soverD) {
+            var bs = try self.bufImage(1 + 1);
+            var a = bs.writer();
+            a.writeByte('O') catch unreachable;
+            a.writeByte(@intFromEnum(op)) catch unreachable;
+        }
+    }
+    // asserts self.screen != null
+    pub fn getScreen(self: Display) *Image {
+        return self.screen.?;
+    }
+};
+fn iounit(file: std.fs.File) u32 {
+    var buf: [128]u8 = undefined;
+    const f = std.fmt.bufPrint(&buf, "/fd/{d}ctl", .{file.handle}) catch unreachable;
+    const cfd = std.fs.openFileAbsolute(f, .{}) catch return 0;
+    defer cfd.close();
+    const i = cfd.read(&buf) catch 0;
+    if (i == 0)
+        return 0;
+    const str = buf[0..i];
+    var toks = std.mem.tokenizeSequence(u8, str, " ");
+    var j: usize = 0;
+    // skip the first 7
+    while (j < 7) : (j += 1) _ = toks.next() orelse return 0;
+    const iounit_str = toks.next() orelse return 0;
+    return std.fmt.parseInt(u32, iounit_str, 10) catch return 0;
+}
+pub const Chan = enum(u32) {
+    pub const Color = struct {
+        const Red = 0;
+        const Green = 1;
+        const Blue = 2;
+        const Grey = 3;
+        const Alpha = 4;
+        const Map = 5;
+        const Ignore = 6;
+    };
+    pub const NChan = 7;
+    grey1 = chan1(Color.Grey, 1),
+    grey2 = chan1(Color.Grey, 2),
+    grey4 = chan1(Color.Grey, 4),
+    grey8 = chan1(Color.Grey, 8),
+    cmap8 = chan1(Color.Map, 8),
+    rgb15 = chan4(Color.Ignore, 1, Color.Red, 5, Color.Green, 5, Color.Blue, 5),
+    rgb16 = chan3(Color.Red, 5, Color.Green, 6, Color.Blue, 5),
+    rgb24 = chan3(Color.Red, 8, Color.Green, 8, Color.Blue, 8),
+    rgba32 = chan4(Color.Red, 8, Color.Green, 8, Color.Blue, 8, Color.Alpha, 8),
+    argb32 = chan4(Color.Alpha, 8, Color.Red, 8, Color.Green, 8, Color.Blue, 8),
+    xrgb32 = chan4(Color.Ignore, 8, Color.Red, 8, Color.Green, 8, Color.Blue, 8),
+    bgr24 = chan3(Color.Blue, 8, Color.Green, 8, Color.Red, 8),
+    abgr32 = chan4(Color.Alpha, 8, Color.Blue, 8, Color.Green, 8, Color.Red, 8),
+    xbgr32 = chan4(Color.Ignore, 8, Color.Blue, 8, Color.Green, 8, Color.Red, 8),
+    _,
+    const channames: []const u8 = "rgbkamx";
+    fn TYPE(self: u32) u32 {
+        return (self >> 4) & 15;
+    }
+    fn NBITS(self: u32) u32 {
+        return self & 15;
+    }
+    pub fn fromString(str: []const u8) Chan {
+        // strip str
+        const spaces: []const u8 = &.{ ' ', '\t', '\r', '\n' };
+        const pos = std.mem.indexOfNone(u8, str, spaces).?;
+        const s = str[pos..];
+
+        var d: u32 = 0;
+        var chan: u32 = 0;
+        var i: usize = 0;
+        const chan_ = blk: {
+            while (i < s.len) : (i += 2) {
+                if (std.ascii.isWhitespace(s[i])) break;
+                if (std.mem.indexOfScalar(u8, channames, s[i])) |ty| {
+                    const n = std.fmt.parseInt(u8, s[i + 1 .. i + 2], 10) catch break :blk 0;
+                    d += n;
+                    chan <<= 8;
+                    chan |= dc(@intCast(ty), @intCast(n));
+                } else break :blk 0;
+            }
+            if (d == 0 or (d > 8 and d % 8 != 0) or (d < 8 and 8 % d != 0)) break :blk 0;
+            break :blk chan;
+        };
+
+        return @enumFromInt(chan_);
+    }
+    pub fn toString(self: Chan, buf: []u8) ![]const u8 {
+        if (self.depth() == 0) {
+            return error.ChanDepthIsZero;
+        }
+        var rc: u32 = 0;
+        var c = @intFromEnum(self);
+        while (c != 0) : (c >>= 8) {
+            rc <<= 8;
+            rc |= c & 0xff;
+        }
+        var i: usize = 0;
+        c = rc;
+        while (c != 0) : (c >>= 8) {
+            buf[i] = channames[TYPE(c)];
+            i += 1;
+            buf[i] = @intCast('0' + NBITS(c));
+            i += 1;
+        }
+        return buf[0..i];
+    }
+    pub fn depth(self: Chan) u32 {
+        var d: u32 = 0;
+        var c: u32 = @intFromEnum(self);
+        while (c != 0) : (c >>= 8) {
+            d += cdepth(c);
+        }
+        if (d == 0 or (d > 8 and d % 8 != 0) or (d < 8 and 8 % d != 0)) return 0;
+        return d;
+    }
+    fn dc(ty: u32, nbit: u32) u32 {
+        return ((ty & 15) << 4) | (nbit & 15);
+    }
+    fn cdepth(c: u32) u32 {
+        return c & 0xf;
+    }
+    pub fn chan1(a: u32, b: u32) u32 {
+        return dc(a, b);
+    }
+    pub fn chan2(a: u32, b: u32, c: u32, d: u32) u32 {
+        return chan1(a, b) << 8 | dc(c, d);
+    }
+    pub fn chan3(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) u32 {
+        return chan2(a, b, c, d) << 8 | dc(e, f);
+    }
+    pub fn chan4(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32, g: u32, h: u32) u32 {
+        return chan3(a, b, c, d, e, f) << 8 | dc(g, h);
+    }
+};
+pub const DColor = struct {
+    pub const Opaque = 0xFFFFFFFF;
+    pub const Transparent = 0x00000000; // only useful for allocimage, memfillcolor
+    pub const Black = 0x000000FF;
+    pub const White = 0xFFFFFFFF;
+    pub const Red = 0xFF0000FF;
+    pub const Green = 0x00FF00FF;
+    pub const Blue = 0x0000FFFF;
+    pub const Cyan = 0x00FFFFFF;
+    pub const Magenta = 0xFF00FFFF;
+    pub const Yellow = 0xFFFF00FF;
+    pub const Paleyellow = 0xFFFFAAFF;
+    pub const Darkyellow = 0xEEEE9EFF;
+    pub const Darkgreen = 0x448844FF;
+    pub const Palegreen = 0xAAFFAAFF;
+    pub const Medgreen = 0x88CC88FF;
+    pub const Darkblue = 0x000055FF;
+    pub const Palebluegreen = 0xAAFFFFFF;
+    pub const Paleblue = 0x0000BBFF;
+    pub const Bluegreen = 0x008888FF;
+    pub const Greygreen = 0x55AAAAFF;
+    pub const Palegreygreen = 0x9EEEEEFF;
+    pub const Yellowgreen = 0x99994CFF;
+    pub const Medblue = 0x000099FF;
+    pub const Greyblue = 0x005DBBFF;
+    pub const Palegreyblue = 0x4993DDFF;
+    pub const Purpleblue = 0x8888CCFF;
+
+    pub const Notacolor = 0xFFFFFF00;
+    pub const Nofill = Notacolor;
+};
+pub const Borderwidth = 4;
+/// Refresh methods
+pub const Refresh = enum(u8) {
+    backup = 0,
+    none = 1,
+    mesg = 2,
+};
+pub fn initDraw(ally: std.mem.Allocator, fontname: ?[]const u8, label: ?[]const u8) !*Display {
+    return genInitDraw(
+        ally,
+        "/dev",
+        fontname,
+        label,
+        "/dev",
+        .none,
+    );
+}
+pub fn genInitDraw(ally: std.mem.Allocator, devdir: []const u8, fontname: ?[]const u8, label: ?[]const u8, windir: []const u8, ref: Refresh) !*Display {
+    var buf: [128]u8 = undefined;
+    var display = try Display.init(ally, .{ .devdir = devdir, .windir = windir });
+    // TODO deal with fonts
+    _ = fontname;
+    if (label) |l| blk: {
+        const labelfds = std.fmt.bufPrint(&buf, "{s}/label", .{display.windir}) catch break :blk;
+        const labelfd = std.fs.openFileAbsolute(labelfds, .{ .mode = .read_write }) catch break :blk;
+        defer labelfd.close();
+        _ = try labelfd.write(l);
+    }
+    const winnamefds = std.fmt.bufPrint(&buf, "{s}/winname", .{display.windir}) catch unreachable;
+    try display.genGetWindow(winnamefds, &display.screen, &display._screen, ref);
+    return display;
+}
binary files a/t /dev/null differ
--- a/test.zig
+++ /dev/null
@@ -1,792 +1,0 @@
-const std = @import("std");
-pub fn main() !void {
-    const ally = std.heap.page_allocator;
-    const d = initDraw(ally, null, "bruh") catch |e| {
-        std.debug.print("errstr: {s}\n", .{std.os.plan9.errstr()});
-        return e;
-    };
-    const screen = d.getScreen();
-    var buf: [128]u8 = undefined;
-    _ = buf;
-    try screen.draw(screen.r, d.white, null, Point.Zero);
-    try d.flushImage(true);
-}
-
-pub fn parseIntSkipPreceedingSpaces(comptime T: type, buf: []const u8) !T {
-    var i: u32 = 0;
-    while (buf[i] == ' ') i += 1;
-    const int = try std.fmt.parseInt(T, buf[i..], 10);
-    return int;
-}
-
-pub const Image = struct {
-    display: *Display, // display holding data
-    id: u32, // id of system-held Image
-    r: Rectangle, // rectangle in data area, local coords
-    clipr: Rectangle, // clipping region
-    depth: u32, // number of bits per pixel
-    chan: Chan,
-    repl: bool, // flag: data replicates to tile clipr
-    screen: ?*Screen, // 0 if not a window
-    next: ?*Image, // next in list of windows
-    fn allocScreen(image: *Image, fill: *Image, public: bool) !*Screen {
-        const d = image.display;
-        if (d != fill.display)
-            return error.ImageAndFillOnDifferentDisplays;
-        var s = try d.ally.create(Screen);
-        errdefer d.ally.destroy(s);
-        if (screenid == 0) {
-            screenid = std.os.plan9.getpid();
-        }
-        var id: u32 = 0;
-        var trys: usize = 0;
-        while (trys < 25) : (trys += 1) {
-            var bs = try d.bufImage(1 + 4 + 4 + 4 + 1);
-            var a = bs.writer();
-            screenid += 1;
-            id = screenid & 0xffff; // old devdraw bug
-            a.writeByte('A') catch unreachable;
-            a.writeIntLittle(u32, id) catch unreachable;
-            a.writeIntLittle(u32, image.id) catch unreachable;
-            a.writeIntLittle(u32, fill.id) catch unreachable;
-            a.writeByte(@intFromBool(public)) catch unreachable;
-            try d.flushImage(false);
-        }
-        s.display = d;
-        s.id = id;
-        s.image = image;
-        s.fill = fill;
-        return s;
-    }
-    pub fn free(self: *Image) !void {
-        try Display.freeImage1(self);
-        self.display.ally.destroy(self);
-    }
-    pub fn line(dest: *Image, p0: Point, p1: Point, end0: u32, end1: u32, radius: u32, src: *Image, sp: Point) !void {
-        return dest.lineop(p0, p1, end0, end1, radius, src, sp, DrawOp.SoverD);
-    }
-    pub fn lineop(dst: *Image, p0: Point, p1: Point, end0: u32, end1: u32, radius: u32, src: *Image, sp: Point, op: DrawOp) !void {
-        const d = dst.display;
-        try d.setDrawOp(op);
-        var bs = try d.bufImage(1 + 4 + 2 * 4 + 2 * 4 + 4 + 4 + 4 + 4 + 2 * 4);
-        var a = bs.writer();
-        a.writeByte('L') catch unreachable;
-        a.writeIntLittle(u32, dst.id) catch unreachable;
-        a.writeIntLittle(u32, p0.x) catch unreachable;
-        a.writeIntLittle(u32, p0.y) catch unreachable;
-        a.writeIntLittle(u32, p1.x) catch unreachable;
-        a.writeIntLittle(u32, p1.y) catch unreachable;
-        a.writeIntLittle(u32, end0) catch unreachable;
-        a.writeIntLittle(u32, end1) catch unreachable;
-        a.writeIntLittle(u32, radius) catch unreachable;
-        a.writeIntLittle(u32, src.id) catch unreachable;
-        a.writeIntLittle(u32, sp.x) catch unreachable;
-        a.writeIntLittle(u32, sp.y) catch unreachable;
-    }
-    pub fn draw1(dst: *Image, r: Rectangle, src: ?*Image, p0: Point, mask: ?*Image, p1: Point, op: DrawOp) !void {
-        const d = dst.display;
-        try d.setDrawOp(op);
-
-        var bs = try d.bufImage(1 + 4 + 4 + 4 + 4 * 4 + 2 * 4 + 2 * 4);
-        var a = bs.writer();
-        const s = src orelse d.black;
-        const m = mask orelse d.@"opaque";
-        a.writeByte('d') catch unreachable;
-        a.writeIntLittle(u32, dst.id) catch unreachable;
-        a.writeIntLittle(u32, s.id) catch unreachable;
-        a.writeIntLittle(u32, m.id) catch unreachable;
-        a.writeIntLittle(u32, @as(u32, @bitCast(r.min.x))) catch unreachable;
-        a.writeIntLittle(u32, @as(u32, @bitCast(r.min.y))) catch unreachable;
-        a.writeIntLittle(u32, @as(u32, @bitCast(r.max.x))) catch unreachable;
-        a.writeIntLittle(u32, @as(u32, @bitCast(r.max.y))) catch unreachable;
-        a.writeIntLittle(u32, @as(u32, @bitCast(p0.x))) catch unreachable;
-        a.writeIntLittle(u32, @as(u32, @bitCast(p0.y))) catch unreachable;
-        a.writeIntLittle(u32, @as(u32, @bitCast(p1.x))) catch unreachable;
-        a.writeIntLittle(u32, @as(u32, @bitCast(p1.y))) catch unreachable;
-    }
-
-    pub fn draw(dst: *Image, r: Rectangle, src: *Image, mask: ?*Image, p1: Point) !void {
-        return draw1(dst, r, src, p1, mask, p1, .soverD);
-    }
-
-    pub fn drawop(dst: *Image, r: Rectangle, src: ?*Image, mask: ?*Image, p1: Point, op: DrawOp) !void {
-        return draw1(dst, r, src, p1, mask, p1, op);
-    }
-
-    pub fn gendraw(dst: *Image, r: Rectangle, src: ?*Image, p0: Point, mask: ?*Image, p1: Point) !void {
-        return draw1(dst, r, src, p0, mask, p1, .soverD);
-    }
-
-    pub fn gendrawop(dst: *Image, r: Rectangle, src: ?*Image, p0: Point, mask: ?*Image, p1: Point, op: DrawOp) !void {
-        return draw1(dst, r, src, p0, mask, p1, op);
-    }
-};
-/// Porter-Duff compositing operators
-const DrawOp = enum(u8) {
-    pub const Clear = 0;
-
-    pub const SinD = 8;
-    pub const DinS = 4;
-    pub const SoutD = 2;
-    pub const DoutS = 1;
-
-    pub const S = SinD | SoutD;
-    pub const SoverD = SinD | SoutD | DoutS;
-    pub const SatopD = SinD | DoutS;
-    pub const SxorD = SoutD | DoutS;
-
-    pub const D = DinS | DoutS;
-    pub const DoverS = DinS | DoutS | SoutD;
-    pub const DatopS = DinS | SoutD;
-    pub const DxorS = DoutS | SoutD; // == SxorD
-
-    pub const Ncomp = 12;
-
-    sinD = SinD,
-    dinS = DinS,
-    soutD = SoutD,
-    doutS = DoutS,
-
-    s = S,
-    soverD = SoverD,
-    satopD = SatopD,
-    sxorD = SxorD,
-
-    d = D,
-    doverS = DoverS,
-    datopS = DatopS,
-    // dxorS = DxorS, // == SxorD TODO have multiple enum vals with the same name
-
-};
-var screenid: u32 = 0;
-pub const Point = struct {
-    x: i32,
-    y: i32,
-    pub const Zero: Point = .{ .x = 0, .y = 0 };
-};
-pub const Rectangle = struct {
-    min: Point,
-    max: Point,
-    pub const Zero: Rectangle = .{ .min = Point.Zero, .max = Point.Zero };
-    pub fn init(min_x: i32, min_y: i32, max_x: i32, max_y: i32) Rectangle {
-        return .{
-            .min = .{
-                .x = min_x,
-                .y = min_y,
-            },
-            .max = .{
-                .x = max_x,
-                .y = max_y,
-            },
-        };
-    }
-    pub fn isBad(self: Rectangle) bool {
-        const x = self.dX();
-        const y = self.dY();
-        if (x > 0 and y > 0) {
-            const z = x * y;
-            if (@divFloor(z, x) == y and z < 0x10000000) return false;
-        }
-        return true;
-    }
-    pub fn dX(self: Rectangle) i64 {
-        return self.max.x - self.min.x;
-    }
-    pub fn dY(self: Rectangle) i64 {
-        return self.max.y - self.min.y;
-    }
-    pub fn inset(self: Rectangle, n: i32) Rectangle {
-        var r = self;
-        r.min.x += n;
-        r.min.y += n;
-        r.max.x -= n;
-        r.max.y -= n;
-        return r;
-    }
-};
-pub const Screen = struct {
-    display: *Display, // display holding data
-    id: u32, // id of system-held Screen
-    image: *Image, // unused; for reference only
-    fill: *Image, // color to paint behind windows
-    fn free(self: *Screen) !void {
-        const d = self.display;
-        try d.freeRemote(self.id, .screen);
-        d.ally.destroy(self);
-    }
-};
-pub const Display = struct {
-    ally: std.mem.Allocator,
-    qlock: void, // some sort of mutex???
-    locking: bool, // program is using lockdisplay
-    dirno: u32, // the window id
-    fd: std.fs.File,
-    reffd: std.fs.File,
-    ctlfd: std.fs.File,
-    imageid: u32 = 0,
-    local: u32,
-    @"error": void, //  void		(*error)(Display*, char*);
-    devdir: []const u8 = "/dev",
-    windir: []const u8 = "/dev",
-    oldlabel: [64]u8,
-    dataqid: u64,
-    white: *Image,
-    black: *Image,
-    @"opaque": *Image,
-    transparent: *Image,
-    image: ?*Image,
-    buf: []u8,
-    bufsize: u32,
-    bufp: [*]u8,
-    defaultfont: void, // TODO deal with this
-    subfont: void, // TODO deal with this
-    windows: ?*Image,
-    screenimage: ?*Image,
-    _isnewdisplay: bool,
-    screen: ?*Image = null,
-    _screen: ?*Screen = null,
-    pub fn init(ally: std.mem.Allocator, options: struct { devdir: []const u8 = "/dev", windir: []const u8 = "/dev" }) !*Display {
-        const NINFO = 12 * 12;
-        var info: [NINFO + 1]u8 = undefined;
-        var buf: [512]u8 = undefined;
-        var image: ?*Image = null;
-        const ctlfd = try std.fs.openFileAbsolute(try std.fmt.bufPrint(&buf, "{s}/draw/new", .{options.devdir}), .{ .mode = .read_write });
-        errdefer ctlfd.close();
-        var n = try ctlfd.read(&info);
-        if (n < 12) {
-            return error.InvalidReadFromDrawCtl;
-        }
-        if (n == NINFO + 1) n = NINFO;
-        info[n] = 0;
-        const infoslice = info[0..n];
-        var isnew: bool = false;
-        if (n < NINFO) isnew = true;
-        const winnum = try parseIntSkipPreceedingSpaces(u32, infoslice[0 .. 1 * 12 - 1]);
-        const datafd = try std.fs.openFileAbsolute(try std.fmt.bufPrint(&buf, "{s}/draw/{d}/data", .{ options.devdir, winnum }), .{ .mode = .read_write });
-        errdefer datafd.close();
-        const reffd = try std.fs.openFileAbsolute(try std.fmt.bufPrint(&buf, "{s}/draw/{d}/refresh", .{ options.devdir, winnum }), .{});
-        errdefer reffd.close();
-        const disp = try ally.create(Display);
-        disp.ally = ally;
-        if (n >= NINFO) {
-            image = try ally.create(Image);
-            errdefer ally.destroy(image.?);
-
-            const chan = Chan.fromString(infoslice[2 * 12 .. 3 * 12 - 1]);
-            image.?.* = .{
-                .display = disp,
-                .id = 0,
-                .chan = chan,
-                .depth = chan.depth(),
-                .repl = try parseIntSkipPreceedingSpaces(u32, infoslice[3 * 12 .. 4 * 12 - 1]) != 0,
-                .r = .{
-                    .min = .{
-                        .x = try parseIntSkipPreceedingSpaces(i32, infoslice[4 * 12 .. 5 * 12 - 1]),
-                        .y = try parseIntSkipPreceedingSpaces(i32, infoslice[5 * 12 .. 6 * 12 - 1]),
-                    },
-                    .max = .{
-                        .x = try parseIntSkipPreceedingSpaces(i32, infoslice[6 * 12 .. 7 * 12 - 1]),
-                        .y = try parseIntSkipPreceedingSpaces(i32, infoslice[7 * 12 .. 8 * 12 - 1]),
-                    },
-                },
-                .clipr = .{
-                    .min = .{
-                        .x = try parseIntSkipPreceedingSpaces(i32, infoslice[8 * 12 .. 9 * 12 - 1]),
-                        .y = try parseIntSkipPreceedingSpaces(i32, infoslice[9 * 12 .. 10 * 12 - 1]),
-                    },
-                    .max = .{
-                        .x = try parseIntSkipPreceedingSpaces(i32, infoslice[10 * 12 .. 11 * 12 - 1]),
-                        .y = try parseIntSkipPreceedingSpaces(i32, infoslice[11 * 12 .. 12 * 12 - 1]),
-                    },
-                },
-                .screen = null,
-                .next = null,
-            };
-        }
-        // TODO refactor this into a disp.* = .{ ... } expression
-        const bufsize_iounit = iounit(datafd);
-        const bufsz = if (bufsize_iounit == 0) 8000 else if (disp.bufsize < 512) return error.IounitTooSmall else bufsize_iounit;
-        disp.* = .{
-            .ally = ally,
-            .dirno = winnum,
-            .fd = datafd,
-            .reffd = reffd,
-            .ctlfd = ctlfd,
-            .imageid = 0,
-            .local = 0,
-            .devdir = options.devdir,
-            .windir = options.windir,
-            .oldlabel = .{0} ** 64,
-            .dataqid = 0,
-            .white = undefined, // filled in later
-            .black = undefined, // filled in later
-            .@"opaque" = undefined, // filled in later
-            .transparent = undefined, // filled in later
-            .buf = undefined, // filled in later
-            .bufsize = bufsz,
-            .bufp = undefined, // filled in later
-            .windows = null,
-            .screenimage = null,
-            ._isnewdisplay = isnew,
-            .qlock = {}, // TODO make this an actual lock
-            .locking = false,
-            .@"error" = {}, // TODO audit if we need this
-            .image = image,
-            .defaultfont = {},
-            .subfont = {},
-        };
-        disp.buf = try ally.alloc(u8, bufsz + 5); // +5 for flush message;
-        errdefer ally.free(disp.buf);
-        disp.bufp = disp.buf.ptr;
-        disp.white = try disp.allocImage(Rectangle.init(0, 0, 1, 1), .grey1, true, DColor.White);
-        disp.black = try disp.allocImage(Rectangle.init(0, 0, 1, 1), .grey1, true, DColor.Black);
-        // disp.error = error;
-        disp.windir = try ally.dupe(u8, options.windir);
-        errdefer ally.free(disp.windir);
-        disp.devdir = try ally.dupe(u8, options.devdir);
-        errdefer ally.free(disp.devdir);
-        // qlock(&disp.qlock)
-        disp.@"opaque" = disp.white;
-        disp.transparent = disp.black;
-        return disp;
-    }
-    pub fn allocImage(self: *Display, r: Rectangle, chan: Chan, repl: bool, col: u32) !*Image {
-        return self._allocImage(null, r, chan, repl, col, 0, .backup);
-    }
-    fn _allocImage(self: *Display, ai: ?*Image, r: Rectangle, chan: Chan, repl: bool, col: u32, _screenid: u32, refresh: Refresh) !*Image {
-        if (r.isBad()) {
-            return error.BadRect;
-        }
-        if (@intFromEnum(chan) == 0) {
-            return error.BadChanDesc;
-        }
-        const depth = chan.depth();
-        if (depth == 0) {
-            return error.BadChanDesc;
-        }
-        var bs = try self.bufImage(1 + 4 + 4 + 1 + 4 + 1 + 4 * 4 + 4 * 4 + 4);
-        var a = bs.writer();
-        self.imageid += 1;
-        const id = self.imageid;
-        // start writing the protocol
-        // everything is little endian
-        a.writeByte('b') catch unreachable;
-        a.writeIntLittle(u32, id) catch unreachable;
-        a.writeIntLittle(u32, _screenid) catch unreachable;
-        a.writeByte(@intFromEnum(refresh)) catch unreachable;
-        a.writeIntLittle(u32, @intFromEnum(chan)) catch unreachable;
-        a.writeByte(@intFromBool(repl)) catch unreachable;
-        a.writeIntLittle(i32, r.min.x) catch unreachable;
-        a.writeIntLittle(i32, r.min.y) catch unreachable;
-        a.writeIntLittle(i32, r.max.x) catch unreachable;
-        a.writeIntLittle(i32, r.max.y) catch unreachable;
-        const clipr = if (!repl)
-            Rectangle.init(-0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF)
-        else
-            r;
-        a.writeIntLittle(i32, clipr.min.x) catch unreachable;
-        a.writeIntLittle(i32, clipr.min.y) catch unreachable;
-        a.writeIntLittle(i32, clipr.max.x) catch unreachable;
-        a.writeIntLittle(i32, clipr.max.y) catch unreachable;
-        a.writeIntLittle(u32, col) catch unreachable;
-        var i: *Image = undefined;
-        if (ai) |image| {
-            i = image;
-        } else {
-            i = self.ally.create(Image) catch {
-                try self.freeRemote(id, .image);
-                return error.OutOfMemory;
-            };
-            errdefer self.ally.destroy(i);
-        }
-        i.* = .{ .display = self, .id = id, .depth = depth, .chan = chan, .r = r, .clipr = clipr, .repl = repl, .screen = null, .next = null };
-        return i;
-    }
-    pub fn namedImage(self: *Display, name: []const u8) !*Image {
-        if (name.len > 256) {
-            return error.ImageNameTooLong;
-        }
-        self.flushImage(false) catch {};
-        var bs = try self.bufImage(1 + 4 + 1 + name.len);
-        var a = bs.writer();
-        self.imageid += 1;
-        const id = self.imageid;
-        a.writeByte('n') catch unreachable;
-        a.writeIntLittle(u32, id) catch unreachable;
-        a.writeByte(@intCast(name.len)) catch unreachable;
-        a.writeAll(name) catch unreachable;
-        try self.flushImage(false);
-        var buf: [12 * 12 + 1]u8 = undefined;
-        if (try self.ctlfd.pread(&buf, 0) < 12 * 12) {
-            return error.CtlReadTooShort;
-        }
-        buf[12 * 12] = 0;
-        var i = self.ally.create(Image) catch {
-            try self.freeRemote(id, .image);
-            try self.flushImage(false);
-            return error.OutOfMemory;
-        };
-        errdefer self.ally.destroy(i);
-        const chan = Chan.fromString(buf[2 * 12 .. 3 * 12 - 1]);
-        i.* = .{
-            .display = self,
-            .id = id,
-            .chan = chan,
-            .depth = chan.depth(),
-            .repl = try parseIntSkipPreceedingSpaces(u32, buf[3 * 12 .. 4 * 12 - 1]) != 0,
-            .r = .{
-                .min = .{
-                    .x = try parseIntSkipPreceedingSpaces(i32, buf[4 * 12 .. 5 * 12 - 1]),
-                    .y = try parseIntSkipPreceedingSpaces(i32, buf[5 * 12 .. 6 * 12 - 1]),
-                },
-                .max = .{
-                    .x = try parseIntSkipPreceedingSpaces(i32, buf[6 * 12 .. 7 * 12 - 1]),
-                    .y = try parseIntSkipPreceedingSpaces(i32, buf[7 * 12 .. 8 * 12 - 1]),
-                },
-            },
-            .clipr = .{
-                .min = .{
-                    .x = try parseIntSkipPreceedingSpaces(i32, buf[8 * 12 .. 9 * 12 - 1]),
-                    .y = try parseIntSkipPreceedingSpaces(i32, buf[9 * 12 .. 10 * 12 - 1]),
-                },
-                .max = .{
-                    .x = try parseIntSkipPreceedingSpaces(i32, buf[10 * 12 .. 11 * 12 - 1]),
-                    .y = try parseIntSkipPreceedingSpaces(i32, buf[11 * 12 .. 12 * 12 - 1]),
-                },
-            },
-            .screen = null,
-            .next = null,
-        };
-        return i;
-    }
-    fn _allocWindow(self: *Display, i: ?*Image, s: *Screen, r: Rectangle, ref: Refresh, col: u32) !*Image {
-        var im = try self._allocImage(i, r, self.screenimage.?.chan, false, col, s.id, ref);
-        im.screen = s;
-        im.next = self.windows;
-        self.windows = im;
-        return im;
-    }
-    fn freeRemote(self: *Display, id: u32, t: enum { image, screen }) !void {
-        var bs = try self.bufImage(1 + 4);
-        const a = bs.writer();
-        const c: u8 = if (t == .image) 'f' else 'F';
-        a.writeByte(c) catch unreachable;
-        a.writeIntLittle(u32, id) catch unreachable;
-    }
-    fn freeImage1(image: *Image) !void {
-        const d = image.display;
-        if (image.screen != null) {
-            var w: ?*Image = d.windows;
-            if (w.? == image) {
-                d.windows = image.next;
-            } else {
-                while (w != null) {
-                    if (w.?.next == image) {
-                        w.?.next = image.next;
-                        break;
-                    }
-                    w = w.?.next;
-                }
-            }
-        }
-        try d.freeRemote(image.id, .image);
-    }
-    pub fn bufImage(self: *Display, n: usize) !std.io.FixedBufferStream([]u8) {
-        if (n > self.bufsize) {
-            return error.BadCountBufSize;
-        }
-        if (@intFromPtr(self.bufp + n) > @intFromPtr(self.buf.ptr + self.bufsize)) {
-            try self.flush();
-        }
-        const p = self.bufp;
-        self.bufp += n;
-        return std.io.fixedBufferStream(p[0..n]);
-    }
-    pub fn flush(self: *Display) !void {
-        const n: i64 = @intCast(@intFromPtr(self.bufp) - @intFromPtr(self.buf.ptr));
-        if (n <= 0) return error.UnableToFlushInvalidN;
-        // std.debug.print("about to flush: {}\n{s}\n", .{ std.fmt.fmtSliceHexLower(self.buf[0..@intCast(n)]), self.buf[0..@intCast(n)] });
-        if ((self.fd.write(self.buf[0..@intCast(n)]) catch return error.UnableToFlushWrite) != n) {
-            self.bufp = self.buf.ptr; // might as well; chance of continuing
-            return error.UnableToFlushN;
-        }
-        self.bufp = self.buf.ptr;
-    }
-    pub fn flushImage(self: *Display, visible: bool) !void {
-        if (visible) {
-            self.bufp[0] = 'v';
-            self.bufp += 1;
-            if (self._isnewdisplay) {
-                std.mem.writeIntLittle(u32, self.bufp[0..4], self.screenimage.?.id);
-            }
-        }
-        return self.flush();
-    }
-    pub fn genGetWindow(self: *Display, winname: []const u8, winp: *?*Image, scrp: *?*Screen, ref: Refresh) !void {
-        var buf: [64 + 1]u8 = undefined;
-        var obuf: [64 + 1]u8 = undefined;
-        var image: ?*Image = null;
-        obuf[0] = 0;
-        while (true) {
-            const fd = std.fs.openFileAbsolute(winname, .{}) catch {
-                std.mem.copyForwards(u8, &buf, "noborder");
-                image = self.image;
-                break;
-            };
-            var n: ?usize = fd.read(buf[0..64]) catch null;
-            if (n == 0) n = null; // TODO do I need this?
-            if (n == null) {
-                fd.close();
-                std.mem.copyForwards(u8, &buf, "noborder");
-                image = self.image;
-                break;
-            }
-            // we correctly read in to buf
-            fd.close();
-            image = self.namedImage(buf[0..n.?]) catch |err| {
-                std.debug.print("namedImage: {}\n", .{err});
-                if (!std.mem.eql(u8, buf[0..n.?], obuf[0..n.?])) {
-                    std.debug.print("trying to fix the race\n", .{});
-                    std.mem.copyForwards(u8, obuf[0..n.?], buf[0..n.?]);
-                    continue;
-                }
-                break;
-            };
-            break;
-        }
-        if (winp.*) |i| {
-            try freeImage1(i);
-            if (scrp.*.?.image != self.image)
-                try scrp.*.?.image.free();
-            try scrp.*.?.free();
-            scrp.* = null;
-        }
-        if (image == null) {
-            winp.* = null;
-            self.screenimage = null;
-            return error.CouldNotGetImage; // TODO audit this error
-        }
-        self.screenimage = image.?;
-        scrp.* = image.?.allocScreen(self.white, false) catch |err| {
-            winp.* = null;
-            self.screenimage = null;
-            if (image != self.image) {
-                if (image) |i| try i.free();
-            }
-            return err;
-        };
-        const i = image.?;
-        var r = i.r;
-        if (!std.mem.eql(u8, buf[0..8], "noborder")) {
-            r = r.inset(Borderwidth);
-        }
-        winp.* = self._allocWindow(winp.*, scrp.*.?, r, ref, DColor.White) catch |err| {
-            std.debug.print("could not alloc window {}\n", .{err});
-            try scrp.*.?.free();
-            scrp.* = null;
-            self.screenimage = null;
-            if (image != self.image)
-                if (image) |im|
-                    try im.free();
-            return err;
-        };
-        self.screenimage = winp.*;
-    }
-    pub fn setDrawOp(self: *Display, op: DrawOp) !void {
-        if (op != .soverD) {
-            var bs = try self.bufImage(1 + 1);
-            var a = bs.writer();
-            a.writeByte('O') catch unreachable;
-            a.writeByte(@intFromEnum(op)) catch unreachable;
-        }
-    }
-    // asserts self.screen != null
-    pub fn getScreen(self: Display) *Image {
-        return self.screen.?;
-    }
-};
-fn iounit(file: std.fs.File) u32 {
-    var buf: [128]u8 = undefined;
-    const f = std.fmt.bufPrint(&buf, "/fd/{d}ctl", .{file.handle}) catch unreachable;
-    const cfd = std.fs.openFileAbsolute(f, .{}) catch return 0;
-    defer cfd.close();
-    const i = cfd.read(&buf) catch 0;
-    if (i == 0)
-        return 0;
-    const str = buf[0..i];
-    var toks = std.mem.tokenizeSequence(u8, str, " ");
-    var j: usize = 0;
-    // skip the first 7
-    while (j < 7) : (j += 1) _ = toks.next() orelse return 0;
-    const iounit_str = toks.next() orelse return 0;
-    return std.fmt.parseInt(u32, iounit_str, 10) catch return 0;
-}
-pub const Chan = enum(u32) {
-    pub const Color = struct {
-        const Red = 0;
-        const Green = 1;
-        const Blue = 2;
-        const Grey = 3;
-        const Alpha = 4;
-        const Map = 5;
-        const Ignore = 6;
-    };
-    pub const NChan = 7;
-    grey1 = chan1(Color.Grey, 1),
-    grey2 = chan1(Color.Grey, 2),
-    grey4 = chan1(Color.Grey, 4),
-    grey8 = chan1(Color.Grey, 8),
-    cmap8 = chan1(Color.Map, 8),
-    rgb15 = chan4(Color.Ignore, 1, Color.Red, 5, Color.Green, 5, Color.Blue, 5),
-    rgb16 = chan3(Color.Red, 5, Color.Green, 6, Color.Blue, 5),
-    rgb24 = chan3(Color.Red, 8, Color.Green, 8, Color.Blue, 8),
-    rgba32 = chan4(Color.Red, 8, Color.Green, 8, Color.Blue, 8, Color.Alpha, 8),
-    argb32 = chan4(Color.Alpha, 8, Color.Red, 8, Color.Green, 8, Color.Blue, 8),
-    xrgb32 = chan4(Color.Ignore, 8, Color.Red, 8, Color.Green, 8, Color.Blue, 8),
-    bgr24 = chan3(Color.Blue, 8, Color.Green, 8, Color.Red, 8),
-    abgr32 = chan4(Color.Alpha, 8, Color.Blue, 8, Color.Green, 8, Color.Red, 8),
-    xbgr32 = chan4(Color.Ignore, 8, Color.Blue, 8, Color.Green, 8, Color.Red, 8),
-    _,
-    const channames: []const u8 = "rgbkamx";
-    fn TYPE(self: u32) u32 {
-        return (self >> 4) & 15;
-    }
-    fn NBITS(self: u32) u32 {
-        return self & 15;
-    }
-    pub fn fromString(str: []const u8) Chan {
-        // strip str
-        const spaces: []const u8 = &.{ ' ', '\t', '\r', '\n' };
-        const pos = std.mem.indexOfNone(u8, str, spaces).?;
-        const s = str[pos..];
-
-        var d: u32 = 0;
-        var chan: u32 = 0;
-        var i: usize = 0;
-        const chan_ = blk: {
-            while (i < s.len) : (i += 2) {
-                if (std.ascii.isWhitespace(s[i])) break;
-                if (std.mem.indexOfScalar(u8, channames, s[i])) |ty| {
-                    const n = std.fmt.parseInt(u8, s[i + 1 .. i + 2], 10) catch break :blk 0;
-                    d += n;
-                    chan <<= 8;
-                    chan |= dc(@intCast(ty), @intCast(n));
-                } else break :blk 0;
-            }
-            if (d == 0 or (d > 8 and d % 8 != 0) or (d < 8 and 8 % d != 0)) break :blk 0;
-            break :blk chan;
-        };
-
-        return @enumFromInt(chan_);
-    }
-    pub fn toString(self: Chan, buf: []u8) ![]const u8 {
-        if (self.depth() == 0) {
-            return error.ChanDepthIsZero;
-        }
-        var rc: u32 = 0;
-        var c = @intFromEnum(self);
-        while (c != 0) : (c >>= 8) {
-            rc <<= 8;
-            rc |= c & 0xff;
-        }
-        var i: usize = 0;
-        c = rc;
-        while (c != 0) : (c >>= 8) {
-            buf[i] = channames[TYPE(c)];
-            i += 1;
-            buf[i] = @intCast('0' + NBITS(c));
-            i += 1;
-        }
-        return buf[0..i];
-    }
-    pub fn depth(self: Chan) u32 {
-        var d: u32 = 0;
-        var c: u32 = @intFromEnum(self);
-        while (c != 0) : (c >>= 8) {
-            d += cdepth(c);
-        }
-        if (d == 0 or (d > 8 and d % 8 != 0) or (d < 8 and 8 % d != 0)) return 0;
-        return d;
-    }
-    fn dc(ty: u32, nbit: u32) u32 {
-        return ((ty & 15) << 4) | (nbit & 15);
-    }
-    fn cdepth(c: u32) u32 {
-        return c & 0xf;
-    }
-    pub fn chan1(a: u32, b: u32) u32 {
-        return dc(a, b);
-    }
-    pub fn chan2(a: u32, b: u32, c: u32, d: u32) u32 {
-        return chan1(a, b) << 8 | dc(c, d);
-    }
-    pub fn chan3(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) u32 {
-        return chan2(a, b, c, d) << 8 | dc(e, f);
-    }
-    pub fn chan4(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32, g: u32, h: u32) u32 {
-        return chan3(a, b, c, d, e, f) << 8 | dc(g, h);
-    }
-};
-pub const DColor = struct {
-    pub const Opaque = 0xFFFFFFFF;
-    pub const Transparent = 0x00000000; // only useful for allocimage, memfillcolor
-    pub const Black = 0x000000FF;
-    pub const White = 0xFFFFFFFF;
-    pub const Red = 0xFF0000FF;
-    pub const Green = 0x00FF00FF;
-    pub const Blue = 0x0000FFFF;
-    pub const Cyan = 0x00FFFFFF;
-    pub const Magenta = 0xFF00FFFF;
-    pub const Yellow = 0xFFFF00FF;
-    pub const Paleyellow = 0xFFFFAAFF;
-    pub const Darkyellow = 0xEEEE9EFF;
-    pub const Darkgreen = 0x448844FF;
-    pub const Palegreen = 0xAAFFAAFF;
-    pub const Medgreen = 0x88CC88FF;
-    pub const Darkblue = 0x000055FF;
-    pub const Palebluegreen = 0xAAFFFFFF;
-    pub const Paleblue = 0x0000BBFF;
-    pub const Bluegreen = 0x008888FF;
-    pub const Greygreen = 0x55AAAAFF;
-    pub const Palegreygreen = 0x9EEEEEFF;
-    pub const Yellowgreen = 0x99994CFF;
-    pub const Medblue = 0x000099FF;
-    pub const Greyblue = 0x005DBBFF;
-    pub const Palegreyblue = 0x4993DDFF;
-    pub const Purpleblue = 0x8888CCFF;
-
-    pub const Notacolor = 0xFFFFFF00;
-    pub const Nofill = Notacolor;
-};
-pub const Borderwidth = 4;
-/// Refresh methods
-pub const Refresh = enum(u8) {
-    backup = 0,
-    none = 1,
-    mesg = 2,
-};
-pub fn initDraw(ally: std.mem.Allocator, fontname: ?[]const u8, label: ?[]const u8) !*Display {
-    return genInitDraw(
-        ally,
-        "/dev",
-        fontname,
-        label,
-        "/dev",
-        .none,
-    );
-}
-pub fn genInitDraw(ally: std.mem.Allocator, devdir: []const u8, fontname: ?[]const u8, label: ?[]const u8, windir: []const u8, ref: Refresh) !*Display {
-    var buf: [128]u8 = undefined;
-    var display = try Display.init(ally, .{ .devdir = devdir, .windir = windir });
-    // TODO deal with fonts
-    _ = fontname;
-    if (label) |l| blk: {
-        const labelfds = std.fmt.bufPrint(&buf, "{s}/label", .{display.windir}) catch break :blk;
-        const labelfd = std.fs.openFileAbsolute(labelfds, .{ .mode = .read_write }) catch break :blk;
-        defer labelfd.close();
-        _ = try labelfd.write(l);
-    }
-    const winnamefds = std.fmt.bufPrint(&buf, "{s}/winname", .{display.windir}) catch unreachable;
-    try display.genGetWindow(winnamefds, &display.screen, &display._screen, ref);
-    return display;
-}