// 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'}) }