summaryrefslogtreecommitdiff
path: root/gui/draw/mouse.go
diff options
context:
space:
mode:
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() }