From b913ad677a5705021e1ced8a78ab717ac654d7d5 Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Sat, 6 Jan 2018 22:58:47 +0100 Subject: [PATCH 1/6] set Display.errch and use it for errors from mouse/keyboard i've only seen error EOF from devdraw mouse/keyboard, when i close a window (at least on macos), or by calling Display.Close(). before this change, the draw lib would call log.Fatal, ending the entire program. now, it will send the error on errch. it is the caller's responsibility to pass an errch to Init() that has buffering, if it wants to be sure it sees the errors. this doesn't remove the "TODO use errch" comment at Init(), because not all errors are handled yet. there are still some log.Fatal's, around allocimage/allocwindow. --- draw/init.go | 3 +++ draw/keyboard.go | 8 +++++--- draw/mouse.go | 7 +++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/draw/init.go b/draw/init.go index 5839342..8643adb 100644 --- a/draw/init.go +++ b/draw/init.go @@ -88,6 +88,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, diff --git a/draw/keyboard.go b/draw/keyboard.go index 12fce67..37cab40 100644 --- a/draw/keyboard.go +++ b/draw/keyboard.go @@ -1,7 +1,5 @@ package draw -import "log" - const ( KeyFn = '\uF000' @@ -42,7 +40,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/mouse.go b/draw/mouse.go index 81e4c3c..5c6d92f 100644 --- a/draw/mouse.go +++ b/draw/mouse.go @@ -3,7 +3,6 @@ package draw import ( "fmt" "image" - "log" "os" ) @@ -45,7 +44,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 From c03900057bf06b6dac1d256e80679a666b0e2784 Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Fri, 27 Apr 2018 18:00:23 +0200 Subject: [PATCH 2/6] make libdraw (mostly) work on plan 9 under rio this now mounts $wsys to create a new window of the requested size. it mounts the rio window at /n/duit., and opens its mouse/keyboard files, writes its label. bugs: on interrupt (del) of the command, the window isn't removed. this is loosely based on mischiefs draw9 library. --- draw/alloc.go | 15 +- draw/draw.go | 5 +- draw/getsubfont.go | 28 -- draw/getsubfont_devdraw.go | 34 ++ draw/getsubfont_plan9.go | 27 ++ draw/{init.go => init_devdraw.go} | 4 +- draw/init_plan9.go | 503 ++++++++++++++++++++++ draw/iounit.go | 25 ++ draw/{keyboard.go => keyboard_devdraw.go} | 2 + draw/keyboard_plan9.go | 94 ++++ draw/{mouse.go => mouse_devdraw.go} | 2 + draw/mouse_plan9.go | 129 ++++++ draw/{snarf.go => snarf_devdraw.go} | 2 + draw/snarf_plan9.go | 29 ++ draw/window.go | 18 - 15 files changed, 859 insertions(+), 58 deletions(-) create mode 100644 draw/getsubfont_devdraw.go create mode 100644 draw/getsubfont_plan9.go rename draw/{init.go => init_devdraw.go} (99%) create mode 100644 draw/init_plan9.go create mode 100644 draw/iounit.go rename draw/{keyboard.go => keyboard_devdraw.go} (98%) create mode 100644 draw/keyboard_plan9.go rename draw/{mouse.go => mouse_devdraw.go} (99%) create mode 100644 draw/mouse_plan9.go rename draw/{snarf.go => snarf_devdraw.go} (97%) create mode 100644 draw/snarf_plan9.go 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..35b7ab0 --- /dev/null +++ b/draw/getsubfont_plan9.go @@ -0,0 +1,27 @@ +// +build plan9 + +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 99% rename from draw/init.go rename to draw/init_devdraw.go index 8643adb..f22b977 100644 --- a/draw/init.go +++ b/draw/init_devdraw.go @@ -1,3 +1,5 @@ +// +build !plan9 + package draw import ( @@ -364,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..726ad04 --- /dev/null +++ b/draw/init_plan9.go @@ -0,0 +1,503 @@ +// +build plan9 + +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 + + 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.mousectl = d.initMouse() + d.keyboardctl = d.initKeyboard() + + 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]) + drawDir := fmt.Sprintf("/dev/draw/%d", id) + fd, err := os.OpenFile(drawDir+"/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 + } + + 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 + + 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 98% rename from draw/keyboard.go rename to draw/keyboard_devdraw.go index 37cab40..7c794b5 100644 --- a/draw/keyboard.go +++ b/draw/keyboard_devdraw.go @@ -1,3 +1,5 @@ +// +build !plan9 + package draw const ( diff --git a/draw/keyboard_plan9.go b/draw/keyboard_plan9.go new file mode 100644 index 0000000..6f76579 --- /dev/null +++ b/draw/keyboard_plan9.go @@ -0,0 +1,94 @@ +// +build plan9 + +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 99% rename from draw/mouse.go rename to draw/mouse_devdraw.go index 049fdc8..f544607 100644 --- a/draw/mouse.go +++ b/draw/mouse_devdraw.go @@ -1,3 +1,5 @@ +// +build !plan9 + package draw import ( diff --git a/draw/mouse_plan9.go b/draw/mouse_plan9.go new file mode 100644 index 0000000..260e380 --- /dev/null +++ b/draw/mouse_plan9.go @@ -0,0 +1,129 @@ +// +build plan9 + +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..65640a5 --- /dev/null +++ b/draw/snarf_plan9.go @@ -0,0 +1,29 @@ +// +build plan9 + +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(d.mtpt + "/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(d.mtpt+"/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 -} */ From d3148b5970d9267a4362391640d361641cd62dd4 Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Sun, 29 Apr 2018 14:34:48 +0200 Subject: [PATCH 3/6] init mouse & keyboard in right order --- draw/init_plan9.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/draw/init_plan9.go b/draw/init_plan9.go index 726ad04..5377970 100644 --- a/draw/init_plan9.go +++ b/draw/init_plan9.go @@ -150,9 +150,6 @@ func Init(errch chan<- error, fontname, label, winsize string) (*Display, error) return nil, err } - d.mousectl = d.initMouse() - d.keyboardctl = d.initKeyboard() - d.ctl, err = os.OpenFile("/dev/draw/new", os.O_RDWR|syscall.O_CLOEXEC, 0666) if err != nil { return nil, err @@ -246,6 +243,9 @@ func Init(errch chan<- error, fontname, label, winsize string) (*Display, error) return nil, err } + d.mousectl = d.initMouse() + d.keyboardctl = d.initKeyboard() + return d, nil } From fafada5f286e8fb84ff9c5973f0ad2204e377a9c Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Sun, 29 Apr 2018 14:35:28 +0200 Subject: [PATCH 4/6] remove "// +build plan9" annotation it isn't needed because of the "_plan9" suffix in the filename. pointed out by nick owens. --- draw/getsubfont_plan9.go | 2 -- draw/init_plan9.go | 2 -- draw/keyboard_plan9.go | 2 -- draw/mouse_plan9.go | 2 -- draw/snarf_plan9.go | 2 -- 5 files changed, 10 deletions(-) diff --git a/draw/getsubfont_plan9.go b/draw/getsubfont_plan9.go index 35b7ab0..8eff716 100644 --- a/draw/getsubfont_plan9.go +++ b/draw/getsubfont_plan9.go @@ -1,5 +1,3 @@ -// +build plan9 - package draw import ( diff --git a/draw/init_plan9.go b/draw/init_plan9.go index 5377970..f7f0292 100644 --- a/draw/init_plan9.go +++ b/draw/init_plan9.go @@ -1,5 +1,3 @@ -// +build plan9 - package draw import ( diff --git a/draw/keyboard_plan9.go b/draw/keyboard_plan9.go index 6f76579..71bdc29 100644 --- a/draw/keyboard_plan9.go +++ b/draw/keyboard_plan9.go @@ -1,5 +1,3 @@ -// +build plan9 - package draw import ( diff --git a/draw/mouse_plan9.go b/draw/mouse_plan9.go index 260e380..bc3af09 100644 --- a/draw/mouse_plan9.go +++ b/draw/mouse_plan9.go @@ -1,5 +1,3 @@ -// +build plan9 - package draw import ( diff --git a/draw/snarf_plan9.go b/draw/snarf_plan9.go index 65640a5..0f96a3f 100644 --- a/draw/snarf_plan9.go +++ b/draw/snarf_plan9.go @@ -1,5 +1,3 @@ -// +build plan9 - package draw import ( From 8e0eca235d4a54e94e748338c0fe84729342aa76 Mon Sep 17 00:00:00 2001 From: Kyle Nusbaum Date: Mon, 6 Apr 2020 21:29:32 +0000 Subject: [PATCH 5/6] draw: reopen ctl file when reattaching For one reason or another, the ctl file seems to be closed often when reattaching. This patch preemptively reopens the ctl file when the window is attached or reattached to make sure the handle is good. --- draw/init_plan9.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/draw/init_plan9.go b/draw/init_plan9.go index f7f0292..ad7a25e 100644 --- a/draw/init_plan9.go +++ b/draw/init_plan9.go @@ -53,6 +53,7 @@ type Display struct { DefaultFont *Font DefaultSubfont *Subfont + dir string conn *drawFile ctl *os.File mousectl *Mousectl @@ -159,8 +160,8 @@ func Init(errch chan<- error, fontname, label, winsize string) (*Display, error) } id := atoi(info[:1*12]) - drawDir := fmt.Sprintf("/dev/draw/%d", id) - fd, err := os.OpenFile(drawDir+"/data", os.O_RDWR|syscall.O_CLOEXEC, 0666) + 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 } @@ -263,6 +264,13 @@ func (d *Display) getwindow(ref int) error { // 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 { From e15035a4ba2f9857765e8b15f9885ec663733ffa Mon Sep 17 00:00:00 2001 From: Kyle Nusbaum Date: Mon, 13 Apr 2020 16:23:55 -0500 Subject: [PATCH 6/6] draw: fix snarf for plan9 - correct snarf is always at /dev --- draw/snarf_plan9.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/draw/snarf_plan9.go b/draw/snarf_plan9.go index 0f96a3f..6861319 100644 --- a/draw/snarf_plan9.go +++ b/draw/snarf_plan9.go @@ -9,7 +9,7 @@ import ( // 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(d.mtpt + "/snarf") + sbuf, err := ioutil.ReadFile("/dev/snarf") if err != nil { return -1, -1, err } @@ -23,5 +23,5 @@ func (d *Display) ReadSnarf(buf []byte) (int, int, error) { // WriteSnarf writes the data to the snarf buffer. func (d *Display) WriteSnarf(data []byte) error { - return ioutil.WriteFile(d.mtpt+"/snarf", data, 0666) + return ioutil.WriteFile("/dev/snarf", data, 0666) }