ref: e3c2fff3fead8501068149464c2716ed5f06a594
dir: /src/libdraw.zig/
const std = @import("std"); 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 a = try d.allocBuf(1 + 4 + 4 + 4 + 1); 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 a = try d.allocBuf(1 + 4 + 2 * 4 + 2 * 4 + 4 + 4 + 4 + 4 + 2 * 4); 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 a = try d.allocBuf(1 + 4 + 4 + 4 + 4 * 4 + 2 * 4 + 2 * 4); 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); } pub fn doellipse(dst: *Image, cmd: u8, c: Point, xr: u32, yr: u32, thick: u32, src: *Image, sp: Point, alpha: u32, phi: u32, op: DrawOp) !void { try dst.display.setDrawOp(op); const a = try dst.display.allocBuf(1 + 4 + 4 + 2 * 4 + 4 + 4 + 4 + 2 * 4 + 2 * 4); a.writeByte(cmd) catch unreachable; a.writeIntLittle(u32, dst.id) catch unreachable; a.writeIntLittle(u32, src.id) catch unreachable; a.writeIntLittle(i32, c.x) catch unreachable; a.writeIntLittle(i32, c.y) catch unreachable; a.writeIntLittle(u32, xr) catch unreachable; a.writeIntLittle(u32, yr) catch unreachable; a.writeIntLittle(u32, thick) catch unreachable; a.writeIntLittle(i32, sp.x) catch unreachable; a.writeIntLittle(i32, sp.y) catch unreachable; a.writeIntLittle(u32, alpha) catch unreachable; a.writeIntLittle(u32, phi) catch unreachable; } pub fn ellipse(dst: *Image, c: Point, a: u32, b: u32, thick: u32, src: *Image, sp: Point) !void { try dst.doellipse('e', c, a, b, thick, src, sp, 0, 0, .soverD); } pub fn ellipseop(dst: *Image, c: Point, a: u32, b: u32, thick: u32, src: *Image, sp: Point, op: DrawOp) !void { try dst.doellipse('e', c, a, b, thick, src, sp, 0, 0, op); } pub fn fillellipse(dst: *Image, c: Point, a: u32, b: u32, src: *Image, sp: Point) !void { try dst.doellipse('E', c, a, b, 0, src, sp, 0, 0, .soverD); } pub fn fillellipseop(dst: *Image, c: Point, a: u32, b: u32, src: *Image, sp: Point, op: DrawOp) !void { try dst.doellipse('E', c, a, b, 0, src, sp, 0, 0, op); } pub fn arc(dst: *Image, c: Point, a: u32, b: u32, thick: u32, src: *Image, sp: Point, alpha: u32, phi: u32) !void { const al = alpha | 1 << 31; try dst.doellipse('e', c, a, b, thick, src, sp, al, phi, .soverD); } pub fn arcop(dst: *Image, c: Point, a: u32, b: u32, thick: u32, src: *Image, sp: Point, alpha: u32, phi: u32, op: DrawOp) !void { const al = alpha | 1 << 31; try dst.doellipse('e', c, a, b, thick, src, sp, al, phi, op); } pub fn fillarc(dst: *Image, c: Point, a: u32, b: u32, src: *Image, sp: Point, alpha: u32, phi: u32) !void { const al = alpha | 1 << 31; try dst.doellipse('E', c, a, b, 0, src, sp, al, phi, .soverD); } pub fn fillarcop(dst: *Image, c: Point, a: u32, b: u32, src: *Image, sp: *Image, alpha: u32, phi: u32, op: DrawOp) !void { const al = alpha | 1 << 31; try dst.doellipse('E', c, a, b, 0, src, sp, al, phi, 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) i32 { return self.max.x - self.min.x; } pub fn dY(self: Rectangle) i32 { return self.max.y - self.min.y; } pub const width = dX; pub const height = dY; 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, abpos: usize = 0, // for use in the writer 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 fn open(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, }; } 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, Color.White); errdefer disp.white.free() catch {}; disp.black = try disp.allocImage(Rectangle.init(0, 0, 1, 1), .grey1, true, Color.Black); errdefer disp.black.free() catch {}; // 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 close(self: *Display) !void { // TODO reset the window's label to the old label self.ally.free(self.devdir); self.ally.free(self.windir); try self.white.free(); try self.black.free(); self.fd.close(); self.ctlfd.close(); self.reffd.close(); self.ally.destroy(self); } 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 a = try self.allocBuf(1 + 4 + 4 + 1 + 4 + 1 + 4 * 4 + 4 * 4 + 4); 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 a = try self.allocBuf(1 + 4 + 1 + name.len); 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 a = try self.allocBuf(1 + 4); 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); } const AllocedBuf = struct { buffer: []u8, pos: *usize, fn writer(self: AllocedBuf) std.io.Writer(AllocedBuf, error{}, write) { return .{ .context = self }; } fn write(self: AllocedBuf, bytes: []const u8) error{}!usize { if (bytes.len == 0) return 0; if (self.pos.* >= self.buffer.len) unreachable; // we don't allocate more than we use const n = if (self.pos.* + bytes.len <= self.buffer.len) bytes.len else self.buffer.len - self.pos.*; @memcpy(self.buffer[self.pos.*..][0..n], bytes[0..n]); self.pos.* += n; if (n == 0) unreachable; return n; } }; pub fn allocBuf(self: *Display, n: usize) !std.io.Writer(AllocedBuf, error{}, AllocedBuf.write) { 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; self.abpos = 0; var ab = AllocedBuf{ .buffer = p[0..n], .pos = &self.abpos }; return ab.writer(); } 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, Color.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 a = try self.allocBuf(1 + 1); 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) { const CColor = 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(CColor.Grey, 1), grey2 = chan1(CColor.Grey, 2), grey4 = chan1(CColor.Grey, 4), grey8 = chan1(CColor.Grey, 8), cmap8 = chan1(CColor.Map, 8), rgb15 = chan4(CColor.Ignore, 1, CColor.Red, 5, CColor.Green, 5, CColor.Blue, 5), rgb16 = chan3(CColor.Red, 5, CColor.Green, 6, CColor.Blue, 5), rgb24 = chan3(CColor.Red, 8, CColor.Green, 8, CColor.Blue, 8), rgba32 = chan4(CColor.Red, 8, CColor.Green, 8, CColor.Blue, 8, CColor.Alpha, 8), argb32 = chan4(CColor.Alpha, 8, CColor.Red, 8, CColor.Green, 8, CColor.Blue, 8), xrgb32 = chan4(CColor.Ignore, 8, CColor.Red, 8, CColor.Green, 8, CColor.Blue, 8), bgr24 = chan3(CColor.Blue, 8, CColor.Green, 8, CColor.Red, 8), abgr32 = chan4(CColor.Alpha, 8, CColor.Blue, 8, CColor.Green, 8, CColor.Red, 8), xbgr32 = chan4(CColor.Ignore, 8, CColor.Blue, 8, CColor.Green, 8, CColor.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 Color = 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.open(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; }