summaryrefslogtreecommitdiff
path: root/gui/draw/mouse.go
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@pobox.com>2026-08-22 13:16:35 -0400
committerCalvin Morrison <calvin@pobox.com>2026-08-22 13:16:35 -0400
commit9aacce8b3b54060d0037eca897856d7273e6e5e8 (patch)
treea98ea0cb151a7ded56fe681d6782986b2807dfcf /gui/draw/mouse.go
parent5ef4699d05bc919255449a9af780f216a0589a72 (diff)
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 <noreply@anthropic.com>
Diffstat (limited to 'gui/draw/mouse.go')
-rw-r--r--gui/draw/mouse.go74
1 files changed, 74 insertions, 0 deletions
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() }