summaryrefslogtreecommitdiff
path: root/gui/draw/draw.go
diff options
context:
space:
mode:
Diffstat (limited to 'gui/draw/draw.go')
-rw-r--r--gui/draw/draw.go250
1 files changed, 250 insertions, 0 deletions
diff --git a/gui/draw/draw.go b/gui/draw/draw.go
new file mode 100644
index 0000000..0aa7911
--- /dev/null
+++ b/gui/draw/draw.go
@@ -0,0 +1,250 @@
+// Package draw speaks the Plan 9 /dev/draw protocol directly.
+//
+// There is no C, no cgo and no libdraw here: /dev/draw is a file, its
+// protocol is a handful of little-endian messages, and this file is the
+// whole of what libdraw's init.c, alloc.c and draw.c actually do.
+// See draw(3) and /sys/src/libdraw for the authority.
+package draw
+
+import (
+ "encoding/binary"
+ "fmt"
+ "os"
+ "strconv"
+ "sync"
+)
+
+// Channel descriptors, from /sys/include/draw.h.
+const (
+ chGrey1 = 0x31
+ chRGB24 = 0x081828
+ chRGBA32 = 0x08182848
+)
+
+type Point struct{ X, Y int32 }
+
+type Rectangle struct{ Min, Max Point }
+
+func Rect(x0, y0, x1, y1 int32) Rectangle {
+ return Rectangle{Point{x0, y0}, Point{x1, y1}}
+}
+
+func (r Rectangle) Dx() int32 { return r.Max.X - r.Min.X }
+func (r Rectangle) Dy() int32 { return r.Max.Y - r.Min.Y }
+
+// Add offsets a rectangle by p.
+func (r Rectangle) Add(p Point) Rectangle {
+ return Rectangle{
+ Point{r.Min.X + p.X, r.Min.Y + p.Y},
+ Point{r.Max.X + p.X, r.Max.Y + p.Y},
+ }
+}
+
+var ZP Point
+
+// Colors are RGBA, as draw(2) writes them.
+const (
+ White = 0xFFFFFFFF
+ Black = 0x000000FF
+ Red = 0xFF0000FF
+ Green = 0x00FF00FF
+ Blue = 0x0000FFFF
+)
+
+type Image struct {
+ d *Display
+ id uint32
+ R Rectangle
+ Clipr Rectangle
+ chn uint32
+ repl bool
+}
+
+func (i *Image) Rect() Rectangle { return i.R }
+
+type Display struct {
+ ctl, data *os.File
+ dirno int
+
+ // Screen is the display's own image, id 0. Without a window system
+ // that is the physical screen; under rio it is the whole screen and
+ // the window comes from /dev/winname instead.
+ Screen *Image
+
+ // opaque is libdraw's display->opaque: a replicated all-ones GREY1
+ // pixel. Draw with a nil mask means "mask with this", not "mask with
+ // image 0" -- image 0 is the screen.
+ opaque *Image
+
+ mu sync.Mutex
+ nextid uint32
+}
+
+// Init attaches to the draw device under dev (normally "/dev").
+func Init(dev string) (*Display, error) {
+ if dev == "" {
+ dev = "/dev"
+ }
+ ctl, err := os.OpenFile(dev+"/draw/new", os.O_RDWR, 0)
+ if err != nil {
+ return nil, fmt.Errorf("open draw/new: %w", err)
+ }
+ // 12 fields of 12 bytes: id, imageid, chan, repl, r[4], clipr[4].
+ var info [12 * 12]byte
+ n, err := ctl.Read(info[:])
+ if err != nil || n < 12 {
+ ctl.Close()
+ return nil, fmt.Errorf("read draw/new: short read %d: %w", n, err)
+ }
+ fld := func(i int) int32 {
+ s := string(info[i*12 : i*12+12])
+ v, _ := strconv.Atoi(trim(s))
+ return int32(v)
+ }
+ d := &Display{ctl: ctl, dirno: int(fld(0))}
+ data, err := os.OpenFile(fmt.Sprintf("%s/draw/%d/data", dev, d.dirno), os.O_RDWR, 0)
+ if err != nil {
+ ctl.Close()
+ return nil, fmt.Errorf("open draw/%d/data: %w", d.dirno, err)
+ }
+ d.data = data
+ if n >= len(info) {
+ d.Screen = &Image{
+ d: d,
+ id: 0,
+ chn: strToChan(trim(string(info[2*12 : 3*12]))),
+ repl: fld(3) != 0,
+ R: Rect(fld(4), fld(5), fld(6), fld(7)),
+ Clipr: Rect(fld(8), fld(9), fld(10), fld(11)),
+ }
+ }
+ if d.opaque, err = d.Alloc(Rect(0, 0, 1, 1), chGrey1, true, White); err != nil {
+ d.Close()
+ return nil, err
+ }
+ return d, nil
+}
+
+func (d *Display) Close() error {
+ d.data.Close()
+ return d.ctl.Close()
+}
+
+func trim(s string) string {
+ i, j := 0, len(s)
+ for i < j && s[i] == ' ' {
+ i++
+ }
+ for j > i && (s[j-1] == ' ' || s[j-1] == '\n') {
+ j--
+ }
+ return s[i:j]
+}
+
+// strToChan parses "r8g8b8" and friends. Only what the screen actually
+// reports is needed, so unknown names fall back to RGB24.
+func strToChan(s string) uint32 {
+ typ := map[byte]uint32{'r': 0, 'g': 1, 'b': 2, 'k': 3, 'a': 4, 'm': 5, 'x': 6}
+ var c uint32
+ for i := 0; i+1 < len(s); i += 2 {
+ t, ok := typ[s[i]]
+ if !ok {
+ return chRGB24
+ }
+ nb := uint32(s[i+1] - '0')
+ c = c<<8 | (t&15)<<4 | nb&15
+ }
+ if c == 0 {
+ return chRGB24
+ }
+ return c
+}
+
+func put32(b []byte, off int, v uint32) {
+ binary.LittleEndian.PutUint32(b[off:], v)
+}
+
+func (d *Display) write(msg []byte) error {
+ d.mu.Lock()
+ defer d.mu.Unlock()
+ _, err := d.data.Write(msg)
+ return err
+}
+
+// Alloc creates a new image on the server. A 1x1 replicated image is how
+// you make a solid colour: it tiles to fill whatever you draw it into.
+func (d *Display) Alloc(r Rectangle, chn uint32, repl bool, col uint32) (*Image, error) {
+ d.mu.Lock()
+ d.nextid++
+ id := d.nextid
+ d.mu.Unlock()
+
+ clipr := r
+ if repl {
+ // Huge but not infinite, so offsets stay huge instead of overflowing.
+ clipr = Rect(-0x3FFFFFFF, -0x3FFFFFFF, 0x3FFFFFFF, 0x3FFFFFFF)
+ }
+ b := make([]byte, 51)
+ b[0] = 'b'
+ put32(b, 1, id)
+ put32(b, 5, 0) // screenid: not a window
+ b[9] = 0 // refresh: Refbackup
+ put32(b, 10, chn)
+ if repl {
+ b[14] = 1
+ }
+ put32(b, 15, uint32(r.Min.X))
+ put32(b, 19, uint32(r.Min.Y))
+ put32(b, 23, uint32(r.Max.X))
+ put32(b, 27, uint32(r.Max.Y))
+ put32(b, 31, uint32(clipr.Min.X))
+ put32(b, 35, uint32(clipr.Min.Y))
+ put32(b, 39, uint32(clipr.Max.X))
+ put32(b, 43, uint32(clipr.Max.Y))
+ put32(b, 47, col)
+ if err := d.write(b); err != nil {
+ return nil, fmt.Errorf("alloc image: %w", err)
+ }
+ return &Image{d: d, id: id, R: r, Clipr: clipr, chn: chn, repl: repl}, nil
+}
+
+// Color is the common case of Alloc: one replicated pixel of a solid colour.
+func (d *Display) Color(col uint32) (*Image, error) {
+ return d.Alloc(Rect(0, 0, 1, 1), chRGBA32, true, col)
+}
+
+// Free releases the image. All of a client's images go away by themselves
+// when its data fd closes, so this is only for long-running programs.
+func (i *Image) Free() error {
+ b := make([]byte, 5)
+ b[0] = 'f'
+ put32(b, 1, i.id)
+ return i.d.write(b)
+}
+
+// Draw copies src (through mask, or opaquely if mask is nil) into r on dst.
+func Draw(dst *Image, r Rectangle, src *Image, mask *Image, p Point) error {
+ b := make([]byte, 45)
+ b[0] = 'd'
+ put32(b, 1, dst.id)
+ put32(b, 5, src.id)
+ if mask == nil {
+ mask = dst.d.opaque
+ }
+ put32(b, 9, mask.id)
+ put32(b, 13, uint32(r.Min.X))
+ put32(b, 17, uint32(r.Min.Y))
+ put32(b, 21, uint32(r.Max.X))
+ put32(b, 25, uint32(r.Max.Y))
+ put32(b, 29, uint32(p.X))
+ put32(b, 33, uint32(p.Y))
+ put32(b, 37, uint32(p.X))
+ put32(b, 41, uint32(p.Y))
+ return dst.d.write(b)
+}
+
+// Flush makes queued drawing visible. devdraw executes each write as it
+// arrives, so this only matters for the screen refresh.
+func (d *Display) Flush() error {
+ return d.write([]byte{'v'})
+}