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() }