shithub: libdraw.myr

Download patch

ref: d180b70c6c69ffe8168cc8571772ad6e15ac6aba
author: Ori Bernstein <[email protected]>
date: Sun Nov 29 13:39:41 EST 2020

import from hg

--- /dev/null
+++ b/bld.proj
@@ -1,0 +1,17 @@
+lib draw =
+	chan.myr
+	draw.myr
+	event.myr
+	font.myr
+	image.myr
+	init.myr
+	load.myr
+	pack.myr
+	string.myr
+	types.myr
+;;
+
+bin testdraw =
+	testdraw.myr
+	lib draw
+;;
--- /dev/null
+++ b/chan.myr
@@ -1,0 +1,83 @@
+use std
+use "types"
+
+pkg draw =
+	pkglocal const chanparse	: (str : byte[:] -> std.option(chan))
+	pkglocal const chanfmt	: (buf : byte[:], chan : chan -> byte[:])
+	pkglocal const chandepth	: (chan : chan -> uint32)
+;;
+
+var channames = "rgbkamx"
+
+const chanparse = {str
+	var c, n, chan, depth
+
+	str = std.strstrip(str)
+	depth = 0
+	chan = 0
+	for var i = 0; i < str.len - 1; i += 2
+		if std.isspace((str[i] : char))
+			break
+		;;
+		match std.strfind(channames, str[i:i+1])
+		| `std.None:	-> `std.None
+		| `std.Some ty:
+			c = (str[i + 1] : char)
+			n = std.charval(c, 10)
+			if n < 0
+				-> `std.None
+			;;
+			depth += n
+			chan <<= 8
+			chan |= dc((ty : uint32), n)
+		;;
+	;;
+
+	if depth==0 || (depth>8 && depth%8 != 0) || (depth<8 && 8%depth != 0)
+		-> `std.None
+	;;
+	-> `std.Some chan
+}
+
+const chanfmt = {buf, chan
+	var c, name, rc, i
+
+	rc = 0
+	for c = chan; c != 0; c >>= 8
+		rc <<= 8
+		rc |= c & 0xff
+	;;
+
+	i = 0
+	for c = rc; c != 0; c >>= 8
+		name = std.decode(channames[ctype(c & 0xff):])
+		std.bfmt(buf[i:i+2], "{}{}", name, cdepth(c & 0xff))
+		i += 2
+	;;
+	-> buf[:i]
+}
+
+const chandepth = {chan
+	var depth
+
+	depth = 0
+	for var c = chan; c != 0; c >>= 8
+		depth += cdepth(c)
+	;;
+	if depth==0 || (depth>8 && depth%8 != 0)  || (depth<8 && 8%depth != 0)
+		-> 0
+	;;
+	-> depth
+}
+
+const dc = {ty, nbit
+	-> (((ty & 15) << 4) | (nbit & 15) : chan)
+}
+
+const ctype = {c
+	-> ((c & 0xf0) >> 4 : uint32)
+}
+
+const cdepth = {c
+	-> (c & 0xf : uint32)
+}
\ No newline at end of file
--- /dev/null
+++ b/draw.myr
@@ -1,0 +1,51 @@
+use std
+
+use "types"
+use "chan"
+use "pack"
+
+pkg draw =
+	const fill	: (dst : image#, src : image#, r : rect -> void)
+	const draw	: (dst : image#, r : rect, src : image#, p0 : point -> void)
+	const drawop	: (dst : image#, r : rect, src : image#, p0 : point, op : drawop -> void)
+	const mdrawop	: (dst : image#, r : rect, s : image#, p0 : point, mask : image#, p1 : point, op : drawop -> void)
+	const inset	: (r : rect, off : int -> rect)
+;;
+
+const fill = {dst, src, r
+	// d dstid[4] srcid[4] maskid[4] dstr[4*4] srcp[2*4] maskp[2*4]
+	mdrawop(dst, r, src, Zp, dst.dpy.opaque, Zp, SoverD)
+}
+
+const draw = {dst, r, src, p0
+	mdrawop(dst, r, src, p0, dst.dpy.opaque, Zp, SoverD)
+}
+
+const drawop = {dst, r, src, p0, op
+	mdrawop(dst, r, src, p0, dst.dpy.opaque, Zp, SoverD)
+}
+
+const mdrawop = {dst, r, src, p0, mask, p1, op
+	if op != SoverD
+		pack(dst.dpy, "bb", 0, ('O' : byte), (op : byte))
+	;;
+	pack(dst.dpy, "biiiriiii", 0, \
+		('d' : byte), /* draw */ \
+		dst.id, \
+		src.id, \
+		mask.id, \
+		r, \
+		p0.x, \
+		p0.y, \
+		p1.x, \
+		p1.y)
+
+}
+
+const inset = {r, off
+	r.x0 += off
+	r.y0 += off
+	r.x1 -= off
+	r.y1 -= off
+	-> r
+}
--- /dev/null
+++ b/event.myr
@@ -1,0 +1,167 @@
+use std
+use sys
+use thread
+
+use "types"
+
+pkg draw =
+	const event	: (dpy : display# -> event#)
+
+	pkglocal const mouseproc	: (dpy : display# -> void)
+	pkglocal const kbdproc		: (dpy : display# -> void)
+	pkglocal const refrproc		: (dpy : display# -> void)
+;;
+
+const mouseproc = {dpy
+	var buf : byte[49]
+	var sp	: byte[:][5]
+	var ev : event#
+	var x, y, b, o
+
+	o = -1
+	while true
+		match std.read(dpy.mousefd, buf[:])
+		| `std.Ok 49:	/* got the message */
+		| `std.Ok _:	break
+		| `std.Err _:	break
+		;;
+
+		std.bstrtok(sp[:], buf[:])
+		x = std.getv(std.intparse(sp[1]), 0)
+		y = std.getv(std.intparse(sp[2]), 0)
+		b = std.getv(std.intparse(sp[3]), 0)
+		if o < 0
+			o = b
+		;;
+		match sp[0]
+		| "r":	ev = std.mk(`Resize)
+		| "m":	ev = std.mk(`Mmove (x, y))
+		| chr:	std.fatal("garbled mouse message")
+		;;
+		send(dpy, &dpy.mousep, ev)
+		if b > o
+			ev = std.mk(`Mdown (x, y, b))
+			send(dpy, &dpy.mousep, ev)
+		elif b < o
+			ev = std.mk(`Mup (x, y, b))
+			send(dpy, &dpy.mousep, ev)
+		;;
+		o = b
+	;;
+}
+
+const kbdproc = {dpy
+	var buf : byte[128]
+	var ev : event#
+	var k, c, s
+
+	while true
+		match std.read(dpy.kbdfd, buf[:])
+		| `std.Err e:	break
+		| `std.Ok 0:	break
+		| `std.Ok n:
+			c = -1	
+			s = std.cstrconv(buf[:n])
+			(k, s) = std.charstep(s)
+			match k
+			| 'k':	/* drop */
+				while s.len > 0 && c != 0
+					(c, s) = std.charstep(s)
+					if dpy.rawevt
+						send(dpy, &dpy.kbdp, std.mk(`Kp c))
+					;;
+				;;
+			| 'K':
+				while s.len > 0 && c != 0
+					(c, s) = std.charstep(s)
+					if dpy.rawevt
+						send(dpy, &dpy.kbdp, std.mk(`Kr c))
+					;;
+				;;
+			| 'c':
+				while s.len > 0 && c != 0
+					(c, s) = std.charstep(s)
+					send(dpy, &dpy.kbdp, std.mk(`Kp c))
+				;;
+			| _:
+				std.die("fail")
+			;;
+
+		;;
+	;;
+:fail
+}
+
+const refrproc = {dpy
+	var buf : byte[128]
+	var ev : event#
+
+	while true
+		std.read(dpy.refrfd, buf[:])
+		ev = std.mk(`Refresh)
+		send(dpy, &dpy.refrp, ev)
+
+	;;
+}
+
+const event = {dpy
+	var p, r
+
+	thread.mtxlock(&dpy.qmtx)
+	/* find tag for rendezvous */
+	for evp : [dpy.refrp, dpy.mousep, dpy.kbdp][:]
+		if evp != (0 : void#)
+			p = evp
+			goto found
+		;;
+	;;
+
+	/* if we couldn't find a tag, enqueue ourselves */
+	p = (dpy : void#)
+	for qp : [&dpy.refrp, &dpy.mousep, &dpy.kbdp][:]
+		qp# = p
+	;;
+:found
+	thread.mtxunlock(&dpy.qmtx)
+
+	r = (-1 : void#)
+	while r == (-1 : void#)
+		r = sys.rendezvous(p, (0 : void#))
+	;;
+
+	/* now dequeue */
+	thread.mtxlock(&dpy.qmtx)
+	for qp : [&dpy.refrp, &dpy.mousep, &dpy.kbdp][:]
+		if qp# == (dpy : void#)
+			qp# = (0 : void#)
+		;;
+	;;
+	thread.mtxunlock(&dpy.qmtx)
+	-> (r : event#)
+}
+
+const send = {dpy, qp, evp
+	var p, r
+
+	thread.mtxlock(&dpy.qmtx)
+	if qp# == (0 : void#)
+		/* nobody is queued; we enquue ourselves */
+		p = (qp : void#)
+		qp# = p
+	else
+		/* somebody is waiting, use their tag */
+		p = qp#
+		qp# = (0 : void#)
+	;;
+	thread.mtxunlock(&dpy.qmtx)
+	r = (-1 : void#)
+	while r == (-1 : void#)
+		r = sys.rendezvous(p, (evp : void#))
+	;;
+	/* now dequeue */
+	thread.mtxlock(&dpy.qmtx)
+	if qp# == (qp : void#)
+		qp# = (0 : void#)
+	;;
+	thread.mtxunlock(&dpy.qmtx)
+}
--- /dev/null
+++ b/font.myr
@@ -1,0 +1,171 @@
+use std
+use bio
+use iter
+
+use "types"
+use "image"
+use "load"
+use "pack"
+
+pkg draw =
+	const openfont	: (dpy : display#, name : byte[:] -> std.result(font#, err))
+
+	pkglocal const loadsubfont	: (dpy : display#, sub : subfont# -> void)
+	pkglocal const cachechar	: (font : font#, c : char -> int16)
+;;
+
+const openfont = {dpy, name
+	var dir
+
+	dir = std.dirname(name)
+	match bio.open(name, bio.Rd)
+	| `std.Ok f:	-> loadfont(dpy, dir, f)
+	| `std.Err e:	-> `std.Err `Efont
+	;;
+}
+
+const cachechar = {ft, c
+	match std.htget(ft.cache, c)
+	| `std.Some inf:
+		inf.hits++
+		-> inf.idx
+	| `std.None:
+		-> loadglyph(ft, c)
+	;;
+}
+
+const loadglyph = {ft, c
+	var r : rect
+	var p : point
+	var g, info
+
+	for sub : iter.byref(ft.subf)
+		if c < sub.lo || c >= sub.hi
+			continue
+		;;
+		if sub.glyph.len == 0
+			loadsubfont(ft.cimage.dpy, sub)
+		;;
+		/*
+		  l cacheid[4] srcid[4] index[2] r[4*4] sp[2*4] left[1]
+		  width[1]
+		 */
+		g = &sub.glyph[c - sub.lo]
+		p = [.x=g.x, .y=0]
+		r = [.x0=ft.x, .y0=0, .x1=ft.x + g.width, .y1=ft.ascent]
+		pack(ft.cimage.dpy, "biisrpbb", 0, \
+			('l' : byte), \
+			ft.cimage.id, \
+			sub.image.id, \
+			ft.nextidx, \
+			r, \
+			p, \
+			g.left, \
+			g.width)
+		flush(ft.cimage.dpy)
+		info.hits = 0
+		info.idx = ft.nextidx
+		std.htput(ft.cache, c, info)
+		ft.x += g.width
+		ft.nextidx++
+		-> info.idx
+	;;
+	-> -1
+}
+
+const loadfont = {dpy, dir, f
+	var sp : byte[:][3]
+	var lo, hi, start, n, r
+	var font
+
+	font = std.zalloc()
+	match bio.readln(f)
+	| `std.Ok ln:
+		std.bstrtok(sp[:2], ln)
+		font.height = std.getv(std.intparse(sp[0]), -1)
+		font.ascent = std.getv(std.intparse(sp[0]), -1)
+	| `std.Err `bio.Eof:
+	| `std.Err e:
+	;;
+
+	for ln : bio.byline(f)
+		n = 0
+		start = 0
+		std.bstrtok(sp[:3], ln)
+		lo = std.getv(std.intparse(sp[n++]), -1)
+		hi = std.getv(std.intparse(sp[n++]), -1)
+		if sp.len == 4
+			start = std.getv(std.intparse(sp[n++]), -1)
+		;;
+		std.slpush(&font.subf, [
+			.lo=lo,
+			.hi=hi,
+			.start=start,
+			.path=std.pathcat(dir, sp[n]),
+		])
+	;;
+
+	/* heuristic: probably big enough... */
+	font.cache = std.mkht()
+	r = [.x0 = 0, .y0 = 0, .x1 = 1000, .y1 = font.ascent]
+	font.cimage = genallocimage(dpy, 0, Refnone, Cgrey8, DBlack, false, r, r)
+	pack(dpy, "biib", 0, \
+		('i' : byte), \
+		font.cimage.id, \
+		128, \
+		font.ascent)
+	-> `std.Ok font
+}
+
+const loadsubfont = {dpy, subf
+	var buf
+
+	match std.slurp(subf.path)
+	| `std.Err e:	std.fatal("could not open subf {}: {}\n", subf.path, e)
+	| `std.Ok b:	buf = b
+	;;
+
+	match xload(dpy, buf)
+	| `std.Err e:	std.fatal("could not load image {}: {}\n")
+	| `std.Ok (img, off):
+		match readglyphs(buf[off:])
+		| `std.Ok gd:
+			subf.image = img
+			subf.glyph = gd
+		| `std.Err e:
+			std.fatal("invalid glyph data in subf {}\n", subf.path)
+		;;
+	;;
+}
+
+const readglyphs = {glyphdata
+	var sp : byte[:][3]
+	var n, height, ascent, err
+	var gd, g
+
+	if glyphdata.len < 3*12 || std.bstrtok(sp[:], glyphdata[:3*12]).len != 3
+		-> `std.Err `Efmt
+	;;
+
+	err = false
+	n = getint(sp[0], &err)
+	height = getint(sp[1], &err)
+	ascent = getint(sp[2], &err)
+	glyphdata = glyphdata[3*12:]
+	if err || glyphdata.len < n
+		-> `std.Err `Efmt
+	;;
+
+	gd = [][:]
+	for var i = 0; i <= n; i++
+		g = [
+			.x=std.getle16(glyphdata[6*i+0 : 6*i+2]),
+			.top=(glyphdata[6*i + 2] : int),
+			.bottom=(glyphdata[6*i + 3] : int),
+			.left=(glyphdata[6*i + 4] : int),
+			.width=(glyphdata[6*i + 5] : int),
+		]
+		std.slpush(&gd, g)
+	;;
+	-> `std.Ok gd
+}
--- /dev/null
+++ b/image.myr
@@ -1,0 +1,182 @@
+use std
+use sys
+
+use "types"
+use "chan"
+use "pack"
+
+pkg draw =
+	const Ninfo
+	const Infosz
+
+	const namedimage	: (dpy : display#, name : byte[:] -> std.result(image#, err))
+	const parseimgfmt	: (img : image#, buf : byte[:][:] -> std.result(image#, err))
+	const allocimage	: (dpy : display#, r : rect, bg : color, repl : bool -> image#)
+	const allocscreen	: (dpy : display#, img : image#, fill : image#, public : bool -> screen#)
+	const allocwindow	: (dpy : display#, r : rect -> std.result(image#, err))
+	const freeimage		: (img : image# -> void)
+
+	/* general versions */
+	const genallocimage	: (dpy : display#, \
+					screenid : int32, \
+					refr : refresh, \
+					chan : chan, \
+					color : color, \
+					repl : bool, \
+					r : rect, \
+					clipr : rect \
+					-> image#)
+
+	pkglocal generic getint	: (str : byte[:], errp : bool# -> @a) :: integral,numeric @a
+;;
+
+const Ninfo	= 12
+const Infosz	= 12
+
+const Ctldir	= 0
+const Imgid	= 1
+const Chan	= 2
+const Repl	= 3
+const Scrx0	= 4
+const Scry0	= 5
+const Scrx1	= 6
+const Scry1	= 7
+const Clipx0	= 8
+const Clipy0	= 9
+const Clipx1	= 10
+const Clipy1	= 11
+
+const namedimage = {dpy, name
+	var info : byte[:][Ninfo]
+	var buf	: byte[12*Ninfo]
+	var img, id //, n, chan
+
+	if name.len > 256
+		-> `std.Err `Ename
+	;;
+	flush(dpy)
+	id = dpy.imageid++
+	pack(dpy, "biS", name.len + 1, \
+		('n' : byte), \
+		id, \
+		name)
+	flush(dpy)
+
+	if sys.pread((dpy.ctlfd : sys.fd), buf[:], 0) == -1
+		-> `std.Err `Eimg
+	;;
+	if std.bstrtok(info[:], buf[:]).len != Ninfo
+		-> `std.Err `Edpy
+	;;
+	img = std.zalloc()
+	img.dpy = dpy
+	img.id = id
+	parseimgfmt(img, info[:])
+	-> `std.Ok img
+}
+
+const genallocimage = {dpy, screenid, refr, chan, color, repl, r, clipr
+	var img
+
+	img = std.zalloc()
+	img.dpy = dpy
+	img.chan = chan
+	img.repl = repl
+	img.refresh = refr
+	img.clipr = clipr
+	img.color = color
+	img.r = r
+	img.id = dpy.imageid++
+
+	pack(dpy, "biibibrri", 0, \
+		('b' : byte), \
+		img.id, \
+		screenid, \
+		(refr : byte), \
+		chan, \
+		(repl : byte), \
+		r, \
+		clipr, \
+		color)
+
+	-> img
+}
+
+const allocimage = {dpy, r, color, repl
+	var clipr
+
+	if repl
+		clipr = [.x0=-0x3FFFFFFF, .y0=-0x3FFFFFFF, .x1=0x3FFFFFFF, .y1=0x3FFFFFFF]
+	else
+		clipr = r
+	;;
+	-> genallocimage(dpy, dpy.screen.id, Refnone, dpy.root.chan, color, repl, r, clipr)
+}
+
+const freeimage = {img
+	pack(img.dpy, "bi", 0, ('f' : byte), img.id)
+	std.free(img)
+}
+
+const allocscreen = {dpy, img, fill, public
+	var scr
+
+	scr = std.zalloc()
+	scr.store = img
+	scr.fill = fill
+
+	for var i = 0; i < 30; i++
+		scr.id = dpy.scrid++
+		pack(dpy, "biiib", 0, \
+			('A' : byte), \
+			scr.id, \
+			scr.store.id, \
+			scr.fill.id, \
+			(public : byte))
+		if flushbuf(dpy)
+			break
+		;;
+	;;
+
+	-> scr
+}
+
+const allocwindow = {scr, r
+	-> `std.Err `Edpy
+}
+
+
+const parseimgfmt = {img, info
+	var err
+
+	err = false
+	match chanparse(info[Chan])
+	| `std.Some c:
+		img.chan = c
+		img.depth = chandepth(img.chan)
+	| `std.None:	-> `std.Err `Edpy
+	;;
+	
+	img.repl = std.sleq("1", std.strstrip(info[Repl]))
+	img.r.x0 = getint(info[Scrx0], &err)
+	img.r.y0 = getint(info[Scry0], &err)
+	img.r.x1 = getint(info[Scrx1], &err)
+	img.r.y1 = getint(info[Scry1], &err)
+	img.clipr.x0 = getint(info[Clipx0], &err)
+	img.clipr.y0 = getint(info[Clipy0], &err)
+	img.clipr.x1 = getint(info[Clipx1], &err)
+	img.clipr.y1 = getint(info[Clipy1], &err)
+	if err
+		-> `std.Err `Edpy
+	;;
+	-> `std.Ok img
+}
+
+generic getint = {str, errp -> @a :: integral,numeric @a
+	match std.intparse(std.strstrip(str))
+	| `std.Some n:	-> n
+	| `std.None:	errp# = true
+	;;
+	-> 0
+}
+
--- /dev/null
+++ b/init.myr
@@ -1,0 +1,186 @@
+use std
+use thread
+use sys
+
+use "types"
+use "chan"
+use "font"
+use "image"
+use "pack"
+use "draw"
+use "event"
+
+pkg draw =
+	const open	: (-> std.result(display#, err))
+	const close	: (dpy : display# -> void)
+	const getwindow	: (dpy : display#, ref : refresh -> std.result(image#, err))
+;;
+
+const open = {
+	var infobuf : byte[12*12] /* 12 infos, 12 bytes each */
+	var r, clipr, font
+	var dpy
+
+	/* connect */
+	dpy = std.zalloc()
+	dpy.imageid = 1
+	dpy.scrid = (std.getpid() : int32)
+	dpy.ctlfd = std.try(std.open("/dev/draw/new", std.Ordwr | std.Ocexec))
+	match std.read(dpy.ctlfd, infobuf[:])
+	| `std.Err e:	-> `std.Err `Edpy
+	| `std.Ok 0:	-> `std.Err `Edpy
+	| `std.Ok n:	
+		match setup(dpy, infobuf[:n])
+		| `std.Ok void:
+		| `std.Err e: -> `std.Err e
+		;;
+	;;
+
+	/* set up default images */
+	r = [.x0=0, .y0=0,.x1=1,.y1=1]
+	clipr = [.x0=-0x3FFFFFFF, .y0=-0x3FFFFFFF, .x1=0x3FFFFFFF, .y1=0x3FFFFFFF]
+	dpy.black = genallocimage(dpy, 0, Refnone, dpy.root.chan, DBlack, true, r, clipr)
+	dpy.white = genallocimage(dpy, 0, Refnone, dpy.root.chan, DWhite, true, r, clipr)
+	dpy.opaque = genallocimage(dpy, 0, Refnone, dpy.root.chan, DOpaque, true, r, clipr)
+
+	/* load up default font */
+	font = std.getenvv("font", Dfont)
+	match openfont(dpy, font)
+	| `std.Ok ft:	dpy.dfont = ft
+	| `std.Err e:	-> `std.Err e
+	;;
+	-> `std.Ok dpy
+}
+
+const setup = {dpy, infobuf
+	var info : byte[:][Ninfo]
+	var buf	: byte[12*Ninfo]
+	var mp, kp, rp
+	var p
+
+	if std.bstrtok(info[:], infobuf).len != Ninfo
+		-> `std.Err `Edpy
+	;;
+	p = std.bfmt(buf[:], "/dev/draw/{}/data", info[0])
+	dpy.datafd = std.try(std.open(p, std.Ordwr | std.Ocexec))
+
+	/* open device fds */
+	p = std.bfmt(buf[:], "/dev/draw/{}/refresh", info[0])
+	dpy.qmtx = thread.mkmtx()
+	dpy.refrfd = std.try(std.open(p, std.Ordwr | std.Ocexec))
+	dpy.mousefd = std.try(std.open("/dev/mouse", std.Ordwr | std.Ocexec))
+	dpy.kbdfd = std.try(std.open("/dev/kbd", std.Ordwr | std.Ocexec))
+	mp = std.try(thread.spawn({; mouseproc(dpy)}))
+	kp = std.try(thread.spawn({; kbdproc(dpy)}))
+	rp = std.try(thread.spawn({; refrproc(dpy)}))
+
+	sys.atexit(std.fndup({; kill(mp)}))
+	sys.atexit(std.fndup({; kill(kp)}))
+	sys.atexit(std.fndup({; kill(rp)}))
+
+
+	/* parse out root image properties */
+	dpy.ctldir = std.get(std.intparse(info[0]))
+	dpy.buf = std.slalloc(iounit(dpy.datafd) + 5)	/* slop for flush */
+	dpy.root = std.zalloc()
+	parseimgfmt(dpy.root, info[:])
+
+	-> `std.Ok void
+}
+
+const initfont = {dpy
+	var font
+
+	font = std.getenvv("font", "/lib/font/bit/vga/unicode.font")
+	match openfont(dpy, font)
+	| `std.Err e:	-> `std.Err `Efont
+	| `std.Ok f:
+		dpy.dfont = f
+		-> `std.Ok void
+	;;
+}
+	
+const getwindow = {dpy, ref
+	var obuf : byte[128], buf : byte[128]
+	var on, fd, img, win
+	var r
+
+	on = 0
+:retry
+	match std.open("/dev/winname", std.Oread)
+	| `std.Ok f:	fd = f
+	| `std.Err e:	-> `std.Err `Ewin
+	;;
+
+	match std.read(fd, buf[:])
+	| `std.Err e:
+		-> `std.Err `Edpy
+	| `std.Ok n:
+		match namedimage(dpy, buf[:n])
+		| `std.Ok i:
+			img = i
+		| `std.Err e:
+			/*
+			 * theres a race where the winname can change after
+			 * we read it, so keep trying as long as the name
+			 * keeps changing.
+			 */
+			if !std.sleq(buf[:n], obuf[:on])
+				std.close(fd)
+				std.slcp(obuf[:n], buf[:n])
+				on = n
+				goto retry
+			;;
+			-> `std.Err `Edpy
+		;;
+	;;
+	r = inset(img.r, Borderwidth)
+	dpy.screen = allocscreen(dpy, img, dpy.white, false)
+	win = allocimage(dpy, r, DWhite, false)
+	if flush(dpy)
+		-> `std.Ok win
+	else
+		-> `std.Err `Edpy
+	;;
+}
+
+const close = {dpy
+//	freescreen(dpy.screen)
+//	freeimage(dpy.root)
+	std.slfree(dpy.buf)
+	std.close(dpy.ctlfd)
+	std.close(dpy.datafd)
+	std.close(dpy.refrfd)
+	std.close(dpy.mousefd)
+	std.close(dpy.kbdfd)
+	std.free(dpy)
+}
+
+/*
+Format:  3 r  M    4 (0000000000457def 11 00)   8192      512 /rc/lib/rcmain
+ */
+const iounit = {fd
+	var args : byte[:][10]
+	var buf : byte[128]
+	var cfd, ret
+	var path
+
+	path = std.bfmt(buf[:], "#d/{}ctl", fd)
+	cfd = std.try(std.open(path, std.Oread))
+	match std.read(cfd,buf[:])
+	| `std.Ok n:
+		std.bstrtok(args[:], buf[:n])
+		ret = std.get(std.intparse(args[7]))
+	| `std.Err e:
+		ret = -1
+	;;
+	std.close(cfd);
+	-> ret
+}
+
+const kill = {pid
+	match std.open(std.fmt("/proc/{}/note", pid), std.Owrite)
+	| `std.Ok fd:	std.write(fd, "kill")
+	| `std.Err e:	/* already gone */
+	;;
+}
--- /dev/null
+++ b/load.myr
@@ -1,0 +1,151 @@
+use std
+use sys
+
+use "types"
+use "chan"
+use "image"
+use "pack"
+
+pkg draw =
+	const load	: (dpy : display#, buf : byte[:] -> std.result(image#, err))
+	const xload	: (dpy : display#, buf : byte[:] -> std.result((image#, std.size), err))
+;;
+
+const load = {dpy, buf
+	match xload(dpy, buf)
+	| `std.Ok (img, n):
+		if n != buf.len
+			// freeimg(img)
+			-> `std.Err `Efmt
+		;;
+		-> `std.Ok img
+	| `std.Err e:
+		-> `std.Err e
+	;;
+}
+
+const xload = {dpy, buf
+	var chan, rect, compressed
+	var n, img
+
+	if !parsehdr(buf, &chan, &rect, &compressed, &buf)
+		-> `std.Err `Efmt
+	;;
+
+	img =  genallocimage(dpy, 0, Refnone, chan, DWhite, false, rect, rect)
+	if compressed
+		n = 5*12 + 11
+		n += loadcomp(dpy, img, buf)
+	else
+		n = 5*12
+		n += loadraw(dpy, img, buf)
+	;;
+	if n > 0
+		-> `std.Ok (img, n)
+	else
+		// freeimg(img)
+		-> `std.Err `Efmt
+	;;
+}
+
+const loadcomp = {dpy, img, buf
+	var l, n, ymin, ymax, err
+
+	l = 0
+	ymin = img.r.y0
+	while ymin != img.r.y1
+		if buf.len < 24
+			-> -1
+		;;
+		err = false
+		ymax = getint(buf[:12], &err)
+		n = getint(buf[12:24], &err)
+		if err
+			-> -1
+		;;
+		l += 24
+		buf = buf[24:]
+		pack(dpy, "biiiiid", (n : std.size), \
+			('Y' : byte), \
+			img.id, \
+			img.r.x0, \
+			ymin, \
+			img.r.x1, \
+			ymax, \
+			buf[:n])
+		buf = buf[n:]
+		ymin = ymax
+		l += n
+	;;
+	-> (l : std.size)
+}
+
+const loadraw = {dpy, img, buf
+	var chunk, btp, btl
+	var l, n, r, dx, dy
+
+	l = 0
+	r = img.r
+	dx = r.x1 - r.x0
+	btp = (chandepth(img.chan) / 8 : int)
+	btl = btp * dx
+	chunk = dpy.buf.len - 64
+	while r.y0 != r.y1
+		dy = r.y1 - r.y0
+		/* FIXME: deal with images where btl > chunk */
+		if dy * btl > chunk
+			dy = chunk / btl
+		;;
+		n = dy * btl
+		pack(dpy, "biiiiid", (n : std.size), \
+			('y' : byte), \
+			img.id, \
+			r.x0, \
+			r.y0, \
+			r.x1, \
+			r.y0 + dy, \
+			buf[:n])
+		l += n
+		buf = buf[n:]
+		r.y0 += dy
+	;;
+	-> (l : std.size)
+}
+
+const parsehdr = {buf, chanp, rectp, compressedp, bufp
+	if buf.len < 5*12
+		-> false
+	;;
+	if std.sleq(buf[:11], "compressed\n") && buf.len >= 5*12 + 11
+		compressedp# = true
+		bufp# = buf[5*12 + 11:]
+		-> parsefmt(buf[11:11+5*12], chanp, rectp)
+	else
+		compressedp# = false
+		bufp# = buf[5*12:]
+		-> parsefmt(buf[:5*12], chanp, rectp)
+	;;
+}
+
+const parsefmt = {buf, chanp, rectp
+	var spbuf : byte[:][5]
+	var sp, err
+
+	sp = std.bstrtok(spbuf[:], buf)
+	if sp.len == 0
+		-> false
+	;;
+
+	match chanparse(sp[0])
+	| `std.Some c:	chanp# = c
+	| `std.None:	std.fatal("bad chan\n"); -> false
+	;;
+
+	err = false
+	rectp.x0 = getint(sp[1], &err)
+	rectp.y0 = getint(sp[2], &err)
+	rectp.x1 = getint(sp[3], &err)
+	rectp.y1 = getint(sp[4], &err)
+	-> !err
+}
+
--- /dev/null
+++ b/pack.myr
@@ -1,0 +1,121 @@
+use std
+use sys
+
+use "types"
+
+pkg draw =
+	const pack	: (dpy : display#, fmt : byte[:], vsz : std.size,  args : ... -> void)
+	const flush	: (dpy : display# -> bool)
+	const flushbuf	: (dpy : display# -> bool)
+;;
+
+const pack = {dpy, fmt, vsz, args
+	var ap
+	var sz, b, o
+
+	ap = std.vastart(&args)
+	sz = fixsize(fmt) + vsz
+	b = ensure(dpy, sz)
+	o = 0
+	for c : fmt
+		match (c : char)
+		| 'b':
+			b[o] = std.vanext(&ap)
+			o += 1
+		| 's':
+			var v : int16 = std.vanext(&ap)
+			std.putle16(b[o:o+2], v)
+			o += 2
+		| 'i':
+			var v : int32 = std.vanext(&ap)
+			std.putle32(b[o:o+4], v)
+			o += 4
+		| 'p':
+			var p : point = std.vanext(&ap)
+			std.putle32(b[o + 0:o + 4], p.x)
+			std.putle32(b[o + 4:o + 8], p.y)
+			o += 2*4
+		| 'r':
+			var r : rect = std.vanext(&ap)
+			std.putle32(b[o+ 0:o+ 4], r.x0)
+			std.putle32(b[o+ 4:o+ 8], r.y0)
+			std.putle32(b[o+ 8:o+12], r.x1)
+			std.putle32(b[o+12:o+16], r.y1)
+			o += 4*4
+		| 'S':
+			var s : byte[:] = std.vanext(&ap)
+			b[o++] = (s.len : byte)
+			std.slcp(b[o:o+s.len], s)
+			o += s.len
+		| 'I':
+			var s : int16[:] = std.vanext(&ap)
+			for var i = 0; i < s.len; i++
+				std.putle16(b[o:o+2], s[i])
+				o += 2
+			;;
+		| 'd':
+			var d : byte[:] = std.vanext(&ap)
+			std.slcp(b[o:o+d.len], d)
+			o += d.len
+		| ' ':
+			/* for readability of format strings */
+		| chr:
+			std.fatal("unknown specifier {} in pack\n", chr)
+		;;
+	;;
+}
+
+const fixsize = {fmt
+	var sz
+
+	sz = 0
+	for c : fmt
+		match (c : char)
+		| 'b':	sz += 1
+		| 's':	sz += 2
+		| 'i':	sz += 4
+		| 'r':	sz += 4*4
+		| 'p':	sz += 2*4
+		| _:	/* variable sizes */
+		;;
+	;;
+	-> sz
+}
+
+const ensure = {dpy, sz
+	var ret
+
+	if dpy.bufoff + sz > dpy.buf.len
+		flush(dpy)
+	;;
+	ret = dpy.buf[dpy.bufoff:dpy.bufoff + sz]
+	dpy.bufoff += sz
+	-> ret
+}
+
+const flush = {dpy
+
+	/* five bytes always reserved here */
+	dpy.buf[dpy.bufoff++] = ('v' : byte)
+	-> flushbuf(dpy)
+}
+		
+const flushbuf = {dpy
+	var off, ret
+	var errbuf : byte[128]
+
+	off = 0
+	ret = true
+	while off != dpy.bufoff
+		match std.write(dpy.datafd, dpy.buf[off:dpy.bufoff])
+		| `std.Ok n:	off += n
+		| `std.Err e:
+			sys.errstr(errbuf[:])
+			std.fatal("**error: {}\n", std.cstrconv(errbuf[:]))
+			ret = false
+			break
+		;;
+	;;
+	dpy.bufoff = 0
+	-> true
+}
--- /dev/null
+++ b/shape.myr
@@ -1,0 +1,51 @@
+use std
+
+use "types"
+use "chan"
+use "pack"
+
+pkg draw =
+	const fill	: (dst : image#, src : image#, r : rect -> void)
+	const draw	: (dst : image#, r : rect, src : image#, p0 : point -> void)
+	const drawop	: (dst : image#, r : rect, src : image#, p0 : point, op : drawop -> void)
+	const mdrawop	: (dst : image#, r : rect, s : image#, p0 : point, mask : image#, p1 : point, op : drawop -> void)
+	const inset	: (r : rect, off : int -> rect)
+;;
+
+const fill = {dst, src, r
+	// d dstid[4] srcid[4] maskid[4] dstr[4*4] srcp[2*4] maskp[2*4]
+	mdrawop(dst, r, src, Zp, dst.dpy.opaque, Zp, SoverD)
+}
+
+const draw = {dst, r, src, p0
+	mdrawop(dst, r, src, p0, dst.dpy.opaque, Zp, SoverD)
+}
+
+const drawop = {dst, r, src, p0, op
+	mdrawop(dst, r, src, p0, dst.dpy.opaque, Zp, SoverD)
+}
+
+const mdrawop = {dst, r, src, p0, mask, p1, op
+	if op != SoverD
+		pack(dst.dpy, "bb", 0, ('O' : byte), (op : byte))
+	;;
+	pack(dst.dpy, "biiiriiii", 0, \
+		('d' : byte), /* draw */ \
+		dst.id, \
+		src.id, \
+		mask.id, \
+		r, \
+		p0.x, \
+		p0.y, \
+		p1.x, \
+		p1.y)
+
+}
+
+const inset = {r, off
+	r.x0 += off
+	r.y0 += off
+	r.x1 -= off
+	r.y1 -= off
+	-> r
+}
--- /dev/null
+++ b/string.myr
@@ -1,0 +1,39 @@
+use std
+use "types"
+use "font"
+use "pack"
+
+pkg draw =
+	const string	: (dst : image#, dp : point, str : byte[:] -> void)
+	const gstring	: (dst : image#, dp : point, clipr : rect, src : image#, sp : point, ft : font#, str : byte[:] -> void)
+;;
+
+const string = {dst, dp, str
+	gstring(dst, dp, dst.r, dst.dpy.black, [.x=0, .y=0], dst.dpy.dfont, str)	
+}
+
+const gstring = {dst, dp, clipr, src, sp, ft, str
+	var cids, id
+
+	cids = [][:]
+	for c : std.bychar(str)
+		id = cachechar(ft, c)
+		if id < 0
+			break
+		;;
+		std.slpush(&cids, id)
+	;;
+
+	/* s dstid[4] srcid[4] fontid[4] p[2*4] clipr[4*4] sp[2*4] n[2] n*(index[2]) */
+	pack(dst.dpy, "biiiprpsI", 2*cids.len, \
+		('s' : byte), \
+		dst.id, \
+		src.id, \
+		ft.cimage.id, \
+		dp, \
+		clipr, \
+		sp, \
+		cids.len, \
+		cids)
+	std.slfree(cids)
+}
--- /dev/null
+++ b/testdraw.myr
@@ -1,0 +1,41 @@
+use std
+use draw
+
+const main = {
+	var ev
+
+	match draw.open()
+	| `std.Err e:	std.fatal("error: {}\n", e)
+	| `std.Ok dpy:
+		testpattern(dpy)
+		draw.flush(dpy)
+		while true
+			ev =  draw.event(dpy)
+			match ev#
+			| `draw.Mmove _:
+			| `draw.Mdown (x, y, mask):	std.put("click {} at {}\n", mask, (x, y))
+			| `draw.Mup (x, y, mask):	std.put("release {} at {}\n", mask, (x, y))
+			| `draw.Kp 'q':
+				break
+			| _:
+				std.put("ev: {}\n", ev)
+			;;
+			std.free(ev)
+		;;
+		draw.close(dpy)
+	;;
+}
+
+const testpattern = {dpy
+	var w, r, p : draw.point
+
+
+	match draw.getwindow(dpy, draw.Refnone)
+	| `std.Ok win:	w = win
+	| `std.Err e:	-> std.fatal("could not get window: {}\n", e);
+	;;
+	r = [.x0=w.r.x0+100, .y0=w.r.y0+100, .x1=w.r.x0+200, .y1=w.r.y0+200]
+	draw.fill(w, dpy.black, r)
+	p = [.x=w.r.x0+100, .y=w.r.y0+210]
+	draw.string(w, p, "hello world")
+}
--- /dev/null
+++ b/testload.myr
@@ -1,0 +1,27 @@
+use std
+use draw
+
+const main = {args : byte[:][:]
+	var img, dpy, w
+	var b : byte[1]
+
+	dpy = std.try(draw.open())
+	for a : args[1:]
+		match std.slurp(a)
+		| `std.Err e:	std.fatal("could not open {}: {}\n", a, e)
+		| `std.Ok buf:
+			match draw.xload(dpy, buf)
+			| `std.Ok (i, _):	img = i
+			| `std.Err e:	std.fatal("could not load image {}: {}\n", a, e)
+			;;
+			match draw.getwindow(dpy, draw.Refnone)
+			| `std.Ok win:	w = win
+			| `std.Err e:	-> std.fatal("could not get window: {}\n", e);
+			;;
+			draw.fill(w, img, w.r)
+			draw.flush(dpy)
+			std.read(std.In, b[:])
+		;;
+	;;
+	draw.close(dpy)
+}
--- /dev/null
+++ b/types.myr
@@ -1,0 +1,252 @@
+use std
+use thread
+
+pkg draw =
+	type chan = uint32
+	type color = uint32
+	type refresh = uint32
+	type drawop = uint32
+
+	/* KF|1, KF|2, ..., KF|0xC is F1, F2, ..., F12 */
+	const KF	= 0xF000	/* Rune: beginning of private Unicode space */
+	const Spec	= 0xF800
+	const PF	= Spec|0x20	/* num pad function key */
+	const Kview	= Spec|0x00	/* view (shift window up) */
+	const Khome	= KF|0x0D
+	const Kup	= KF|0x0E
+	const Kdown	= Kview
+	const Kpgup	= KF|0x0F
+	const Kprint	= KF|0x10
+	const Kleft	= KF|0x11
+	const Kright	= KF|0x12
+	const Kpgdown	= KF|0x13
+	const Kins	= KF|0x14
+
+	const Kalt	= KF|0x15
+	const Kshift	= KF|0x16
+	const Kctl	= KF|0x17
+
+	const Kend	= KF|0x18
+	const Kscroll	= KF|0x19
+	const Kscrolloneup	= KF|0x20
+	const Kscrollonedown	= KF|0x21
+
+	const Ksoh	= 0x01
+	const Kstx	= 0x02
+	const Ketx	= 0x03
+	const Keof	= 0x04
+	const Kenq	= 0x05
+	const Kack	= 0x06
+	const Kbs	= 0x08
+	const Knack	= 0x15
+	const Ketb	= 0x17
+	const Kdel	= 0x7f
+	const Kesc	= 0x1b
+
+	const Kbreak	= Spec|0x61
+	const Kcaps	= Spec|0x64
+	const Knum	= Spec|0x65
+	const Kmiddle	= Spec|0x66
+	const Kaltgr	= Spec|0x67
+	const Kmouse	= Spec|0x100
+
+	type display = struct
+		datafd	: std.fd
+		ctlfd	: std.fd
+
+		/* inputs */
+		qmtx	: thread.mutex
+		rawevt	: bool
+		refrfd	: std.fd
+		mousefd	: std.fd
+		kbdfd	: std.fd
+		refrp	: void#
+		mousep	: void#
+		kbdp	: void#
+
+		ctldir	: int
+		windir	: byte[:]
+
+		imageid	: int32
+		scrid	: int32
+
+		root	: image#
+		screen	: screen#
+		dfont	: font#
+		buf	: byte[:]
+		bufoff	: std.size
+
+		white	: image#
+		black	: image#
+		opaque	: image#
+		trans	: image#
+	;;
+
+	type event = union
+		`Kp char
+		`Kr char
+		`Mmove	(int, int)
+		`Mdown	(int, int, int)
+		`Mup	(int, int, int)
+		`Resize	
+		`Refresh
+	;;
+
+	type err = union
+		`Edpy
+		`Efont
+		`Ewin
+		`Ename
+		`Enamelen
+		`Eimg
+		`Eio
+		`Efmt
+	;;
+
+	type font = struct
+		name	: byte[:]
+		height	: int
+		ascent	: int
+		subf	: subfont[:]
+		cache	: std.htab(char, fcinfo)#
+		cimage	: image#
+		x	: int
+		nextidx	: int16
+	;;
+
+	type fcinfo = struct
+		hits	: int
+		idx	: int16
+	;;
+
+	type subfont = struct
+		lo	: char
+		hi	: char
+		start	: std.size
+		path	: byte[:]
+		image	: image#
+		glyph	: glyph[:]
+	;;
+
+	type glyph = struct
+		x	: int
+		top	: int
+		bottom	: int
+		left	: int
+		width	: int
+	;;
+
+	type screen = struct
+		dpy	: display#
+		id	: int32
+		store	: image#
+		fill	: image#
+	;;
+
+	type image = struct
+		dpy	: display#
+		chan	: chan
+		color	: color
+		refresh	: refresh
+		depth	: uint32
+		id	: int32
+		r	: rect
+		clipr	: rect
+		repl	: bool
+	;;
+
+	type rect = struct
+		x0	: int
+		x1	: int
+		y0	: int
+		y1	: int
+	;;
+
+	type point = struct
+		x	: int
+		y	: int
+	;;
+
+	const Zp	: point = [.x=0, .y=0]
+	const Dfont	: byte[:] = "/lib/font/bit/vga/unicode.font"
+
+	const CidxRed	: int = 0
+	const CidxGreen	: int = 1
+	const CidxBlue	: int = 2
+	const CidxGrey	: int = 3
+	const CidxAlpha	: int = 4
+	const CidxMap	: int = 5
+	const CidxIgnore	: int = 6
+
+	const Cgrey1	: chan = 0x31
+	const Cgrey2	: chan = 0x32
+	const Cgrey4	: chan = 0x34
+	const Cgrey8	: chan = 0x38
+	const Ccmap8	: chan = 0x58
+	const Crgb15	: chan = 0x61051525
+	const Crgb16	: chan = 0x51625
+	const Crgb24	: chan = 0x81828
+	const Crgba32	: chan = 0x8182848
+	const Cargb32	: chan = 0x48081828
+	const Cxrgb32	: chan = 0x68081828
+	const Cbgr24	: chan = 0x281808
+	const Cabgr32	: chan = 0x48281808
+	const Cxbgr32	: chan = 0x68281808
+
+	/* Porter-Duff compositing operators */
+	const Clear	: drawop = 0
+	const SinD	: drawop = 8
+	const DinS	: drawop = 4
+	const SoutD	: drawop = 2
+	const DoutS	: drawop = 1
+
+	const S		: drawop = SinD|SoutD
+	const SoverD	: drawop = SinD|SoutD|DoutS
+	const SatopD	: drawop = SinD|DoutS
+	const SxorD	: drawop = SoutD|DoutS
+
+	const D		: drawop = DinS|DoutS
+	const DoverS	: drawop = DinS|DoutS|SoutD
+	const DatopS	: drawop = DinS|SoutD
+	const DxorS	: drawop = DoutS|SoutD	/* == SxorD */
+
+	/* refresh methods */
+	const Refbackup	: refresh = 0
+	const Refnone	: refresh = 1
+	const Refmesg	: refresh = 2
+
+	/* colors */
+	const DOpaque		: color =  0xFFFFFFFF
+	const DTransparent	: color =  0x00000000	/* only useful for allocimage, memfillcolor */
+	const DBlack		: color =  0x000000FF
+	const DWhite		: color =  0xFFFFFFFF
+	const DRed		: color =  0xFF0000FF
+	const DGreen		: color =  0x00FF00FF
+	const DBlue		: color =  0x0000FFFF
+	const DCyan		: color =  0x00FFFFFF
+	const DMagenta		: color =  0xFF00FFFF
+	const DYellow		: color =  0xFFFF00FF
+	const DPaleyellow	: color =  0xFFFFAAFF
+	const DDarkyellow	: color =  0xEEEE9EFF
+	const DDarkgreen	: color =  0x448844FF
+	const DPalegreen	: color =  0xAAFFAAFF
+	const DMedgreen		: color =  0x88CC88FF
+	const DDarkblue		: color =  0x000055FF
+	const DPalebluegreen	: color =  0xAAFFFFFF
+	const DPaleblue		: color =  0x0000BBFF
+	const DBluegreen	: color =  0x008888FF
+	const DGreygreen	: color =  0x55AAAAFF
+	const DPalegreygreen	: color =  0x9EEEEEFF
+	const DYellowgreen	: color =  0x99994CFF
+	const DMedblue		: color =  0x000099FF
+	const DGreyblue		: color =  0x005DBBFF
+	const DPalegreyblue	: color =  0x4993DDFF
+	const DPurpleblue	: color =  0x8888CCFF
+
+	const DNotacolor	: color =  0xFFFFFF00
+	const DNofill		: color =  DNotacolor
+
+	/* rio expects us to reserve space for the border */
+	const Borderwidth	: int	= 4
+
+;;