From 9aacce8b3b54060d0037eca897856d7273e6e5e8 Mon Sep 17 00:00:00 2001 From: Calvin Morrison Date: Sat, 22 Aug 2026 13:16:35 -0400 Subject: gui: a /dev/draw layer in go, with no cgo and no devdraw 9fans.net/go/draw builds for plan9 but shells out to plan9port's devdraw, which 9front does not have. This talks to /dev/draw itself: the protocol is file i/o, so a pure-go client is a few hundred lines under an already-complete idea. draw/keyboard.go opens /dev/cons and turns the console raw, as libdraw's initkeyboard does. Without it rio keeps its line editor on the window and paints what you type over the drawing. draw/snarf.go writes /dev/snarf, which is what every other program on the system means by copy. Co-Authored-By: Claude Opus 5 --- gui/draw/mouse.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 gui/draw/mouse.go (limited to 'gui/draw/mouse.go') diff --git a/gui/draw/mouse.go b/gui/draw/mouse.go new file mode 100644 index 0000000..a4ddf63 --- /dev/null +++ b/gui/draw/mouse.go @@ -0,0 +1,74 @@ +package draw + +import ( + "fmt" + "os" + "strconv" +) + +// Mouse is one record from /dev/mouse. +type Mouse struct { + Point + Buttons int + Msec uint32 +} + +// Mousectl delivers mouse and resize events on channels. This is the shape +// libdraw's Mousectl already has -- it is hand-rolled CSP in C because there +// were no goroutines. Here it is what the language does natively. +type Mousectl struct { + C <-chan Mouse + Resize <-chan struct{} + + f *os.File +} + +func OpenMouse(dev string) (*Mousectl, error) { + if dev == "" { + dev = "/dev" + } + f, err := os.OpenFile(dev+"/mouse", os.O_RDWR, 0) + if err != nil { + return nil, fmt.Errorf("open mouse: %w", err) + } + c := make(chan Mouse) + rc := make(chan struct{}, 1) + mc := &Mousectl{C: c, Resize: rc, f: f} + // One goroutine parked in read(2). Plan 9 has no netpoll in the Go + // runtime, so this pins an OS thread for as long as it blocks -- fine + // for the two or three event sources a UI has, not for hundreds. + go func() { + defer close(c) + buf := make([]byte, 1+5*12) + for { + n, err := f.Read(buf) + if err != nil { + return + } + if n != 1+4*12 { + continue + } + fld := func(i int) int { + v, _ := strconv.Atoi(trim(string(buf[1+i*12 : 1+i*12+12]))) + return v + } + switch buf[0] { + case 'r': + select { + case rc <- struct{}{}: + default: + } + fallthrough + case 'm': + c <- Mouse{ + Point: Point{int32(fld(0)), int32(fld(1))}, + Buttons: fld(2), + Msec: uint32(fld(3)), + } + } + } + }() + return mc, nil +} + +func (mc *Mousectl) Close() error { return mc.f.Close() } -- cgit v1.2.3