diff --git a/draw/alloc.go b/draw/alloc.go index cf5fbda..447d3be 100644 --- a/draw/alloc.go +++ b/draw/alloc.go @@ -86,23 +86,18 @@ func allocImage(d *Display, ai *Image, r image.Rectangle, pix Pix, repl bool, va return i, nil } -/* -func namedimage(d *Display, name string) (*Image, nil) { - panic("namedimage") -} - +/* implements message 'N' */ func nameimage(i *Image, name string, in bool) error { - a := i.Display.bufimage(1+4+1+1+len(name)) + a := i.Display.bufimage(1 + 4 + 1 + 1 + len(name)) a[0] = 'N' - bplong(a[1:], i.ID) + bplong(a[1:], i.id) if in { a[5] = 1 } - a[6] = len(name) + a[6] = byte(len(name)) copy(a[7:], name) - return d.flushimage(false) + return i.Display.flush(false) } -*/ func (i *Image) free() error { if i == nil || i.Display == nil { diff --git a/draw/draw.go b/draw/draw.go index fe3e9c1..3c7c771 100644 --- a/draw/draw.go +++ b/draw/draw.go @@ -1,5 +1,8 @@ // Package draw is a port of Plan 9's libdraw to Go. -// It connects to the 'devdraw' binary built as part of Plan 9 from User Space (http://swtch.com/plan9port/). +// +// On Plan 9 it talks to /dev/draw and rio(4) at $wsys directly. +// On unix, it connects to the 'devdraw' binary built as part of Plan 9 from User Space (http://swtch.com/plan9port/). +// // All graphics operations are done in the remote server. The functions // in this package typically send a message to the server. // diff --git a/draw/getsubfont.go b/draw/getsubfont.go index 6cc38c2..97800b6 100644 --- a/draw/getsubfont.go +++ b/draw/getsubfont.go @@ -1,38 +1,10 @@ package draw import ( - "bytes" - "fmt" "image" - "io/ioutil" "log" - "os" - "strings" ) -func getsubfont(d *Display, name string) (*Subfont, error) { - scale, fname := parsefontscale(name) - data, err := ioutil.ReadFile(fname) - if err != nil && strings.HasPrefix(fname, "/mnt/font/") { - data1, err1 := fontPipe(fname[len("/mnt/font/"):]) - if err1 == nil { - data, err = data1, err1 - } - } - if err != nil { - fmt.Fprintf(os.Stderr, "getsubfont: %v\n", err) - return nil, err - } - f, err := d.readSubfont(name, bytes.NewReader(data), nil) - if err != nil { - fmt.Fprintf(os.Stderr, "getsubfont: can't read %s: %v\n", fname, err) - } - if scale > 1 { - scalesubfont(f, scale) - } - return f, err -} - func scalesubfont(f *Subfont, scale int) { r := f.Bits.R r2 := r diff --git a/draw/getsubfont_devdraw.go b/draw/getsubfont_devdraw.go new file mode 100644 index 0000000..a304849 --- /dev/null +++ b/draw/getsubfont_devdraw.go @@ -0,0 +1,34 @@ +// +build !plan9 + +package draw + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strings" +) + +func getsubfont(d *Display, name string) (*Subfont, error) { + scale, fname := parsefontscale(name) + data, err := ioutil.ReadFile(fname) + if err != nil && strings.HasPrefix(fname, "/mnt/font/") { + data1, err1 := fontPipe(fname[len("/mnt/font/"):]) + if err1 == nil { + data, err = data1, err1 + } + } + if err != nil { + fmt.Fprintf(os.Stderr, "getsubfont: %v\n", err) + return nil, err + } + f, err := d.readSubfont(name, bytes.NewReader(data), nil) + if err != nil { + fmt.Fprintf(os.Stderr, "getsubfont: can't read %s: %v\n", fname, err) + } + if scale > 1 { + scalesubfont(f, scale) + } + return f, err +} diff --git a/draw/getsubfont_plan9.go b/draw/getsubfont_plan9.go new file mode 100644 index 0000000..8eff716 --- /dev/null +++ b/draw/getsubfont_plan9.go @@ -0,0 +1,25 @@ +package draw + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" +) + +func getsubfont(d *Display, name string) (*Subfont, error) { + scale, fname := parsefontscale(name) + data, err := ioutil.ReadFile(fname) + if err != nil { + fmt.Fprintf(os.Stderr, "getsubfont: %v\n", err) + return nil, err + } + f, err := d.readSubfont(name, bytes.NewReader(data), nil) + if err != nil { + fmt.Fprintf(os.Stderr, "getsubfont: can't read %s: %v\n", fname, err) + } + if scale > 1 { + scalesubfont(f, scale) + } + return f, err +} diff --git a/draw/init.go b/draw/init_devdraw.go similarity index 98% rename from draw/init.go rename to draw/init_devdraw.go index 5839342..f22b977 100644 --- a/draw/init.go +++ b/draw/init_devdraw.go @@ -1,3 +1,5 @@ +// +build !plan9 + package draw import ( @@ -88,6 +90,9 @@ func Init(errch chan<- error, fontname, label, winsize string) (*Display, error) if err != nil { return nil, err } + if errch == nil { + errch = make(chan error) + } d := &Display{ conn: c, errch: errch, @@ -361,7 +366,7 @@ func bpshort(b []byte, n uint16) { } func (d *Display) HiDPI() bool { - return d.DPI >= DefaultDPI*3/2 + return d.DPI >= DefaultDPI*3/2 } func (d *Display) ScaleSize(n int) int { diff --git a/draw/init_plan9.go b/draw/init_plan9.go new file mode 100644 index 0000000..ad7a25e --- /dev/null +++ b/draw/init_plan9.go @@ -0,0 +1,509 @@ +package draw + +import ( + "encoding/binary" + "fmt" + "image" + "io/ioutil" + "os" + "path" + "strconv" + "strings" + "sync" + "syscall" +) + +// Display locking: +// The Exported methods of Display, being entry points for clients, lock the Display structure. +// The unexported ones do not. +// The methods for Font, Image and Screen also lock the associated display by the same rules. + +// drawFile exists solely to provide a ReadDraw, which makes Display.Conn compatible with a call in unloadimage, used by the devdraw version. +type drawFile struct { + *os.File +} + +func (f *drawFile) ReadDraw(buf []byte) (int, error) { + return f.Read(buf) +} + +type Display struct { + mu sync.Mutex + debug bool + errch chan<- error + bufsize int + buf []byte + imageid uint32 + qmask *Image + + Image *Image + Screen *Screen + ScreenImage *Image + Windows *Image + DPI int + + firstfont *Font + lastfont *Font + + White *Image // Pre-allocated color. + Black *Image // Pre-allocated color. + Opaque *Image // Pre-allocated color. + Transparent *Image // Pre-allocated color. + + DefaultFont *Font + DefaultSubfont *Subfont + + dir string + conn *drawFile + ctl *os.File + mousectl *Mousectl + keyboardctl *Keyboardctl + + mtpt string +} + +// An Image represents an image on the server, possibly visible on the display. +type Image struct { + Display *Display + id uint32 + Pix Pix // The pixel format for the image. + Depth int // The depth of the pixels in bits. + Repl bool // Whether the image is replicated (tiles the rectangle). + R image.Rectangle // The extent of the image. + Clipr image.Rectangle // The clip region. + Origin image.Point // Of image in screen, for mouse warping. + next *Image + Screen *Screen // If non-nil, the associated screen; this is a window. +} + +// A Screen is a collection of windows that are visible on an image. +type Screen struct { + Display *Display // Display connected to the server. + id uint32 + Fill *Image // Background image behind the windows. +} + +// Refresh algorithms to execute when a window is resized or uncovered. +// Refmesg is almost always the correct one to use. +const ( + Refbackup = 0 + Refnone = 1 + Refmesg = 2 +) + +const deffontname = "*default*" + +// Init starts and connects to a server and returns a Display structure through +// which all graphics will be mediated. The arguments are an error channel on +// which to deliver errors (currently unused), the name of the font to use (the +// empty string may be used to represent the default font), the window label, +// and the window size as a string in the form XxY, as in "1000x500"; the units +// are pixels. +func Init(errch chan<- error, fontname, label, winsize string) (*Display, error) { + if errch == nil { + errch = make(chan error, 1) + } + + var err error + + d := &Display{ + errch: errch, + } + + // Lock Display so we maintain the contract within this library. + d.mu.Lock() + defer d.mu.Unlock() + + if dbg := os.Getenv("DRAWDEBUG"); dbg != "" { + d.debug = true + } + + width, height := 800, 600 + if winsize != "" { + t := strings.Split(winsize, "x") + if len(t) != 2 { + return nil, fmt.Errorf("bad winsize, must be $widthx$height") + } + width, err = strconv.Atoi(t[0]) + if err != nil { + return nil, fmt.Errorf("bad width in winsize: %s", err) + } + height, err = strconv.Atoi(t[1]) + if err != nil { + return nil, fmt.Errorf("bad height in winsize: %s", err) + } + } + + wsys := os.Getenv("wsys") + if wsys == "" { + return nil, fmt.Errorf("$wsys not set") + } + wsysfd, err := os.OpenFile(wsys, os.O_RDWR, 0666) + if err != nil { + return nil, err + } + // note: must not close wsysfd, or fd's get mixed up... + d.mtpt = "/n/duit." + path.Base(wsys) + err = syscall.Mount(int(wsysfd.Fd()), -1, d.mtpt, 0, fmt.Sprintf("new -r 0 0 %d %d", width, height)) + if err != nil { + return nil, err + } + + d.ctl, err = os.OpenFile("/dev/draw/new", os.O_RDWR|syscall.O_CLOEXEC, 0666) + if err != nil { + return nil, err + } + + info, err := d.readctl() + if err != nil { + return nil, err + } + + id := atoi(info[:1*12]) + d.dir = fmt.Sprintf("/dev/draw/%d", id) + fd, err := os.OpenFile(d.dir+"/data", os.O_RDWR|syscall.O_CLOEXEC, 0666) + if err != nil { + return nil, err + } + d.conn = &drawFile{fd} + + pix, _ := ParsePix(strings.TrimSpace(string(info[2*12 : 3*12]))) + + d.Image = &Image{ + Display: d, + id: 0, + Pix: pix, + Depth: pix.Depth(), + Repl: atoi(info[3*12:]) > 0, + R: ator(info[4*12:]), + Clipr: ator(info[8*12:]), + } + + d.bufsize = Iounit(int(d.conn.Fd())) + if d.bufsize <= 0 { + d.bufsize = 8000 + } + if d.bufsize < 512 { + return nil, fmt.Errorf("iounit too small") + } + d.buf = make([]byte, 0, d.bufsize+5) + + d.White, err = d.allocImage(image.Rect(0, 0, 1, 1), GREY1, true, White) + if err != nil { + return nil, fmt.Errorf("can't allocate white: %s", err) + } + d.Black, err = d.allocImage(image.Rect(0, 0, 1, 1), GREY1, true, Black) + if err != nil { + return nil, fmt.Errorf("can't allocate black: %s", err) + } + d.Opaque = d.White + d.Transparent = d.Black + + /* + * Set up default font + */ + df, err := getdefont(d) + if err != nil { + return nil, err + } + d.DefaultSubfont = df + + if fontname == "" { + fontname = os.Getenv("font") + } + + /* + * Build fonts with caches==depth of screen, for speed. + * If conversion were faster, we'd use 0 and save memory. + */ + var font *Font + if fontname == "" { + buf := []byte(fmt.Sprintf("%d %d\n0 %d\t%s\n", df.Height, df.Ascent, + df.N-1, deffontname)) + //fmt.Printf("%q\n", buf) + //BUG: Need something better for this installsubfont("*default*", df); + font, err = d.buildFont(buf, deffontname) + } else { + font, err = d.openFont(fontname) // BUG: grey fonts + } + if err != nil { + return nil, err + } + d.DefaultFont = font + + err = ioutil.WriteFile(d.mtpt+"/label", []byte(label), 0600) + if err != nil { + return nil, err + } + + err = gengetwindow(d, d.mtpt+"/winname", Refnone) + if err != nil { + d.close() + return nil, err + } + + d.mousectl = d.initMouse() + d.keyboardctl = d.initKeyboard() + + return d, nil +} + +// Attach (re-)attaches to a display, typically after a resize, updating the +// display's associated image, screen, and screen image data structures. +func (d *Display) Attach(ref int) error { + d.mu.Lock() + defer d.mu.Unlock() + return d.getwindow(ref) +} + +func (d *Display) getwindow(ref int) error { + return gengetwindow(d, d.mtpt+"/winname", ref) +} + +// Attach, or possibly reattach, to window. +// If reattaching, maintain value of screen pointer. +func gengetwindow(d *Display, winname string, ref int) error { + var i *Image + var err error + + d.ctl.Close() + d.ctl, err = os.OpenFile(d.dir + "/ctl", os.O_RDWR|syscall.O_CLOEXEC, 0666) + if err != nil { + return err + } + + buf, err := ioutil.ReadFile(winname) + if err != nil { + return fmt.Errorf("gengetwindow: %s", err) + } + i, err = d.namedimage(buf) + if err != nil { + return fmt.Errorf("namedimage %s: %s", buf, err) + } + + if d.ScreenImage != nil { + d.ScreenImage.free() + d.Screen.free() + d.Screen = nil + } + + if i == nil { + d.ScreenImage = nil + return fmt.Errorf("namedimage returned nil image") + } + + d.Screen, err = i.allocScreen(d.White, false) + if err != nil { + return err + } + + r := i.R + const Borderwidth = 4 + r = i.R.Inset(Borderwidth) + + d.ScreenImage = d.Image + d.ScreenImage, err = allocwindow(nil, d.Screen, r, 0, White) + if err != nil { + return err + } + err = originwindow(d.ScreenImage, image.Pt(0, 0), r.Min) + if err != nil { + return err + } + + screen := d.ScreenImage + screen.draw(screen.R, d.White, nil, image.ZP) + if err := d.flush(true); err != nil { + return err + } + + return nil +} + +func (d *Display) readctl() ([]byte, error) { + buf := make([]byte, 12*12) + n, err := d.ctl.Read(buf) + if err == nil && n < 143 { + return nil, fmt.Errorf("bad ctl read, expected 143 bytes, saw %d", n) + } + return buf[:n], err +} + +/* implements message 'n' */ +func (d *Display) namedimage(name []byte) (*Image, error) { + err := d.flush(false) + if err != nil { + return nil, err + } + a := d.bufimage(1 + 4 + 1 + len(name)) + d.imageid++ + id := d.imageid + + a[0] = 'n' + bplong(a[1:], id) + a[5] = byte(len(name)) + copy(a[6:], name) + err = d.flush(false) + if err != nil { + return nil, fmt.Errorf("namedimage: %s", err) + } + + ctlbuf, err := d.readctl() + if err != nil { + return nil, fmt.Errorf("namedimage: %s", err) + } + + pix, _ := ParsePix(string(ctlbuf[2*12 : 3*12])) + image := &Image{ + Display: d, + id: id, + Pix: pix, + Depth: pix.Depth(), + Repl: atoi(ctlbuf[3*12:]) > 0, + R: ator(ctlbuf[4*12:]), + Clipr: ator(ctlbuf[8*12:]), + next: nil, + Screen: nil, + } + + return image, nil +} + +// Close closes the Display. +func (d *Display) Close() error { + d.mu.Lock() + defer d.mu.Unlock() + return d.close() +} + +func (d *Display) close() error { + d.keyboardctl.fd.Close() + d.keyboardctl.ctlfd.Close() + d.mousectl.mfd.Close() + d.mousectl.cfd.Close() + ioutil.WriteFile(d.mtpt+"/wctl", []byte("delete"), 0666) + d.conn.Close() + d.ctl.Close() + return nil +} + +// TODO: drawerror + +func (d *Display) flushBuffer() error { + if len(d.buf) == 0 { + return nil + } + _, err := d.conn.Write(d.buf) + d.buf = d.buf[:0] + if err != nil { + fmt.Fprintf(os.Stderr, "doflush: %s\n", err) + return err + } + return nil +} + +// Flush writes any pending data to the screen. +func (d *Display) Flush() error { + d.mu.Lock() + defer d.mu.Unlock() + return d.flush(true) +} + +// flush data, maybe make visible +func (d *Display) flush(vis bool) error { + if vis { + d.bufsize++ + a := d.bufimage(1) + d.bufsize-- + a[0] = 'v' + } + + return d.flushBuffer() +} + +func (d *Display) bufimage(n int) []byte { + if d == nil || n < 0 || n > d.bufsize { + panic("bad count in bufimage") + } + if len(d.buf)+n > d.bufsize { + if err := d.flushBuffer(); err != nil { + panic("bufimage flush: " + err.Error()) + } + } + i := len(d.buf) + d.buf = d.buf[:i+n] + return d.buf[i:] +} + +const DefaultDPI = 133 + +// TODO: Document. +func (d *Display) Scale(n int) int { + if d == nil || d.DPI <= DefaultDPI { + return n + } + return (n*d.DPI + DefaultDPI/2) / DefaultDPI +} + +func atoi(b []byte) int { + i := 0 + for i < len(b) && b[i] == ' ' { + i++ + } + n := 0 + for ; i < len(b) && '0' <= b[i] && b[i] <= '9'; i++ { + n = n*10 + int(b[i]) - '0' + } + return n +} + +func atop(b []byte) image.Point { + return image.Pt(atoi(b), atoi(b[12:])) +} + +func ator(b []byte) image.Rectangle { + return image.Rectangle{atop(b), atop(b[2*12:])} +} + +func bplong(b []byte, n uint32) { + binary.LittleEndian.PutUint32(b, n) +} + +func bpshort(b []byte, n uint16) { + binary.LittleEndian.PutUint16(b, n) +} + +func (d *Display) HiDPI() bool { + return d.DPI >= DefaultDPI*3/2 +} + +func (d *Display) ScaleSize(n int) int { + if d == nil || d.DPI <= DefaultDPI { + return n + } + return (n*d.DPI + DefaultDPI/2) / DefaultDPI +} + +func originwindow(i *Image, log, scr image.Point) error { + d := i.Display + err := d.flush(false) + if err != nil { + return err + } + b := d.bufimage(1 + 4 + 2*4 + 2*4) + b[0] = 'o' + bplong(b[1:], i.id) + bplong(b[5:], uint32(log.X)) + bplong(b[9:], uint32(log.Y)) + bplong(b[13:], uint32(scr.X)) + bplong(b[17:], uint32(scr.Y)) + err = d.flush(false) + if err != nil { + return err + } + delta := log.Sub(i.R.Min) + i.R = i.R.Add(delta) + i.Clipr = i.Clipr.Add(delta) + i.Origin = i.Origin.Sub(delta) + return nil +} diff --git a/draw/iounit.go b/draw/iounit.go new file mode 100644 index 0000000..8edaf24 --- /dev/null +++ b/draw/iounit.go @@ -0,0 +1,25 @@ +package draw + +import ( + "fmt" + "io/ioutil" + "strconv" + "strings" +) + +// return size of atomic I/O unit for file descriptor +func Iounit(fd int) int { + buf, err := ioutil.ReadFile(fmt.Sprintf("#d/%dctl", fd)) + if err != nil { + return 0 + } + + tok := strings.Fields(string(buf)) + if len(tok) != 10 { + return 0 + } + + iounit, _ := strconv.Atoi(tok[7]) + + return iounit +} diff --git a/draw/keyboard.go b/draw/keyboard_devdraw.go similarity index 92% rename from draw/keyboard.go rename to draw/keyboard_devdraw.go index 12fce67..7c794b5 100644 --- a/draw/keyboard.go +++ b/draw/keyboard_devdraw.go @@ -1,6 +1,6 @@ -package draw +// +build !plan9 -import "log" +package draw const ( KeyFn = '\uF000' @@ -42,7 +42,11 @@ func kbdproc(d *Display, ch chan rune) { for { r, err := d.conn.ReadKbd() if err != nil { - log.Fatal(err) + select { + case d.errch <- err: + default: + } + return } ch <- r } diff --git a/draw/keyboard_plan9.go b/draw/keyboard_plan9.go new file mode 100644 index 0000000..71bdc29 --- /dev/null +++ b/draw/keyboard_plan9.go @@ -0,0 +1,92 @@ +package draw + +import ( + "io" + "log" + "os" + "syscall" + "unicode/utf8" +) + +const ( + KeyFn = '\uF000' + + KeyHome = KeyFn | 0x0D + KeyUp = KeyFn | 0x0E + KeyPageUp = KeyFn | 0xF + KeyPrint = KeyFn | 0x10 + KeyLeft = KeyFn | 0x11 + KeyRight = KeyFn | 0x12 + KeyDown = 0x80 + KeyView = 0x80 + KeyPageDown = KeyFn | 0x13 + KeyInsert = KeyFn | 0x14 + KeyEnd = KeyFn | 0x18 + KeyAlt = KeyFn | 0x15 + KeyShift = KeyFn | 0x16 + KeyCtl = KeyFn | 0x17 + KeyBackspace = 0x08 + KeyDelete = 0x7F + KeyEscape = 0x1b + KeyEOF = 0x04 + KeyCmd = 0xF100 +) + +type Keyboardctl struct { + C <-chan rune // Channel on which keyboard characters are delivered. + + fd *os.File + ctlfd io.WriteCloser +} + +func (d *Display) InitKeyboard() *Keyboardctl { + return d.keyboardctl +} + +func (d *Display) initKeyboard() *Keyboardctl { + ch := make(chan rune, 20) + kc := &Keyboardctl{ + C: ch, + } + var err error + kc.fd, err = os.OpenFile(d.mtpt+"/cons", os.O_RDWR|syscall.O_CLOEXEC, 0666) + if err != nil { + log.Fatal(err) + } + + // Must keep reference to ctlfd open so rawon stays in effect. + kc.ctlfd, err = os.OpenFile(d.mtpt+"/consctl", os.O_WRONLY|syscall.O_CLOEXEC, 0666) + if err != nil { + log.Fatal(err) + } + _, err = kc.ctlfd.Write([]byte("rawon")) + if err != nil { + log.Fatal(err) + } + go kbdproc(d, kc.fd, kc.ctlfd, ch) + return kc +} + +func kbdproc(d *Display, fd io.ReadCloser, ctlfd io.Closer, ch chan rune) { + buf := make([]byte, 32) + have := 0 + for { + for have > 0 && utf8.FullRune(buf[:have]) { + k, n := utf8.DecodeRune(buf[:have]) + copy(buf, buf[n:have]) + have -= n + ch <- k + } + n, err := fd.Read(buf[have:]) + if err != nil { + select { + case d.errch <- err: + default: + } + fd.Close() + ctlfd.Close() + return + } + have += n + } +} diff --git a/draw/mouse.go b/draw/mouse_devdraw.go similarity index 96% rename from draw/mouse.go rename to draw/mouse_devdraw.go index 3588742..f544607 100644 --- a/draw/mouse.go +++ b/draw/mouse_devdraw.go @@ -1,9 +1,10 @@ +// +build !plan9 + package draw import ( "fmt" "image" - "log" "os" "9fans.net/go/draw/drawfcall" @@ -47,7 +48,11 @@ func mouseproc(mc *Mousectl, d *Display, ch chan Mouse, rch chan bool) { for { m, resized, err := d.conn.ReadMouse() if err != nil { - log.Fatal(err) + select { + case d.errch <- err: + default: + } + return } if resized { rch <- true diff --git a/draw/mouse_plan9.go b/draw/mouse_plan9.go new file mode 100644 index 0000000..bc3af09 --- /dev/null +++ b/draw/mouse_plan9.go @@ -0,0 +1,127 @@ +package draw + +import ( + "fmt" + "image" + "log" + "os" + "strconv" + "strings" + "syscall" +) + +// Mouse is the structure describing the current state of the mouse. +type Mouse struct { + image.Point // Location. + Buttons int // Buttons; bit 0 is button 1, bit 1 is button 2, etc. + Msec uint32 // Time stamp in milliseconds. +} + +// TODO: Mouse field is racy but okay. + +// Mousectl holds the interface to receive mouse events. +// The Mousectl's Mouse is updated after send so it doesn't +// have the wrong value if the sending goroutine blocks during send. +// This means that programs should receive into Mousectl.Mouse +// if they want full synchrony. +type Mousectl struct { + Mouse // Store Mouse events here. + C <-chan Mouse // Channel of Mouse events. + Resize <-chan bool // Each received value signals a window resize (see the display.Attach method). + Display *Display // The associated display. + + mfd *os.File // mouse + cfd *os.File // cursor +} + +func (d *Display) InitMouse() *Mousectl { + return d.mousectl +} + +func (d *Display) initMouse() *Mousectl { + ch := make(chan Mouse, 0) + rch := make(chan bool, 2) + mc := &Mousectl{ + C: ch, + Resize: rch, + Display: d, + } + var err error + mc.mfd, err = os.OpenFile(d.mtpt+"/mouse", os.O_RDWR|syscall.O_CLOEXEC, 0666) + if err != nil { + log.Fatal(err) + } + + mc.cfd, err = os.OpenFile(d.mtpt+"/cursor", os.O_RDWR|syscall.O_CLOEXEC, 0666) + if err != nil { + log.Fatal(err) + } + + d.mousectl = mc + go mouseproc(mc, d, ch, rch) + return mc +} + +func mouseproc(mc *Mousectl, d *Display, ch chan Mouse, rch chan bool) { + buf := make([]byte, 1+5*12) + + for { + n, err := mc.mfd.Read(buf) + if n != 1+4*12 { + log.Fatalf("mouse: bad count %d: %s", n, err) + } + + switch buf[0] { + case 'r': // resize + rch <- true + fallthrough + case 'm': // mouse move + // note: msec can be negative on plan9 + msec, _ := strconv.ParseInt(strings.TrimLeft(string(buf[1+3*12:][:11]), " "), 10, 64) + mm := Mouse{ + Point: image.Point{ + X: atoi(buf[1+0*12:][:11]), + Y: atoi(buf[1+1*12:][:11]), + }.Sub(d.ScreenImage.Origin), + Buttons: atoi(buf[1+2*12:][:11]), + Msec: uint32(msec), + } + ch <- mm + /* + * See comment above. + */ + mc.Mouse = mm + } + } +} + +// Read returns the next mouse event. +func (mc *Mousectl) Read() Mouse { + mc.Display.Flush() + m := <-mc.C + mc.Mouse = m + return m +} + +// MoveTo moves the mouse cursor to the specified location. +func (d *Display) MoveTo(pt image.Point) error { + pt = pt.Add(d.ScreenImage.Origin) + _, err := fmt.Fprintf(d.mousectl.mfd, "m%d %d", pt.X, pt.Y) + return err +} + +// SetCursor sets the mouse cursor to the specified cursor image. +// SetCursor(nil) changes the cursor to the standard system cursor. +func (d *Display) SetCursor(c *Cursor) { + mc := d.mousectl + if c == nil { + mc.cfd.Write([]byte{0}) + } else { + buf := make([]byte, 2*4+2*2*16) + bplong(buf, uint32(c.Point.X)) + bplong(buf[4:], uint32(c.Point.Y)) + copy(buf[8:], c.Clr[:]) + copy(buf[8+2*16:], c.Set[:]) + mc.cfd.Write(buf) + } +} diff --git a/draw/snarf.go b/draw/snarf_devdraw.go similarity index 97% rename from draw/snarf.go rename to draw/snarf_devdraw.go index 4dafb20..1109e2d 100644 --- a/draw/snarf.go +++ b/draw/snarf_devdraw.go @@ -1,3 +1,5 @@ +// +build !plan9 + package draw // ReadSnarf reads the snarf buffer into buf, returning the number of bytes read, diff --git a/draw/snarf_plan9.go b/draw/snarf_plan9.go new file mode 100644 index 0000000..6861319 --- /dev/null +++ b/draw/snarf_plan9.go @@ -0,0 +1,27 @@ +package draw + +import ( + "io/ioutil" +) + +// ReadSnarf reads the snarf buffer into buf, returning the number of bytes read, +// the total size of the snarf buffer (useful if buf is too short), and any +// error. No error is returned if there is no problem except for buf being too +// short. +func (d *Display) ReadSnarf(buf []byte) (int, int, error) { + sbuf, err := ioutil.ReadFile("/dev/snarf") + if err != nil { + return -1, -1, err + } + n := len(sbuf) + if len(buf) < n { + n = len(buf) + } + copy(buf, sbuf[:n]) + return n, len(sbuf), nil +} + +// WriteSnarf writes the data to the snarf buffer. +func (d *Display) WriteSnarf(data []byte) error { + return ioutil.WriteFile("/dev/snarf", data, 0666) +} diff --git a/draw/window.go b/draw/window.go index 9168bc8..20a16e9 100644 --- a/draw/window.go +++ b/draw/window.go @@ -149,22 +149,4 @@ func bottomnwindows(w []*Image) { func topnwindows(w []*Image) { topbottom(w, true) } - -func originwindow(w *Image, log, scr image.Point) error { - w.Display.flushimage(false) - b := w.Display.bufimage(1+4+2*4+2*4) - b[0] = 'o' - bplong(b[1:], w.id) - bplong(b[5:], uint32(log.X)) - bplong(b[9:], uint32(log.Y)) - bplong(b[13:], uint32(scr.X)) - bplong(b[17:], uint32(scr.Y)) - if err := w.Display.flushimage(true); err != nil { - return err - } - delta := log.Sub(w.R.Min) - w.R = w.R.Add(delta) - w.Clipr = w.Clipr.Add(delta) - return nil -} */