summaryrefslogtreecommitdiff
path: root/gui/draw/mouse.go
blob: a4ddf63ea936c601318c177ce732df14ad4b9688 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
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() }