Fix sff panic deadlock, share the evdev probe, assorted review cleanups

This commit is contained in:
flamingcow
2026-08-31 15:51:52 -07:00
parent 10d97ee82a
commit e4731c94cd
13 changed files with 205 additions and 272 deletions
+16 -90
View File
@@ -4,31 +4,13 @@ import (
"encoding/binary"
"fmt"
"os"
"path/filepath"
"sync"
"unsafe"
"golang.org/x/sys/unix"
"g.fc.run/theater/cabletest/internal/evdev"
)
const (
evSyn = 0x00
evKey = 0x01
evAbs = 0x03
synReport = 0x00
btnTouch = 0x14a
absX = 0x00
absY = 0x01
inputPropDirect = 0x01
sizeofInputEvent = 24
)
func evIoc(nr, size uintptr) uintptr {
const iocRead = 2
return iocRead<<30 | size<<16 | 'E'<<8 | nr
}
// Current finger position and whether it is down. Held as state rather than
// delivered as events, so a dropped release can never leave a hold armed.
type touchState struct {
@@ -49,109 +31,53 @@ func (t *touchState) get() (x, y int, down bool) {
return t.x, t.y, t.down
}
// Picks the direct-input device that reports absolute X and Y, which is the
// touchscreen; a mouse is relative and a keyboard has no absolute axes.
func findTouchscreen() (*os.File, error) {
paths, err := filepath.Glob("/dev/input/event*")
if err != nil {
return nil, err
}
for _, p := range paths {
f, err := os.OpenFile(p, os.O_RDONLY, 0)
if err != nil {
continue
}
var props, absBits uint64
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
evIoc(0x09, 8), uintptr(unsafe.Pointer(&props))); errno != 0 {
f.Close()
continue
}
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
evIoc(0x20+evAbs, 8), uintptr(unsafe.Pointer(&absBits))); errno != 0 {
f.Close()
continue
}
if props&(1<<inputPropDirect) != 0 &&
absBits&(1<<absX) != 0 && absBits&(1<<absY) != 0 {
return f, nil
}
f.Close()
}
return nil, fmt.Errorf("no touchscreen found among /dev/input/event*")
}
func axisRange(f *os.File, axis uintptr) (min, max int32, err error) {
var info [6]int32
if _, _, errno := unix.Syscall(unix.SYS_IOCTL, f.Fd(),
evIoc(0x40+axis, 24), uintptr(unsafe.Pointer(&info[0]))); errno != 0 {
return 0, 0, errno
}
return info[1], info[2], nil
}
// Tracks the finger position, scaled to the given screen size.
func watchTouch(w, h int) (*touchState, error) {
f, err := findTouchscreen()
ts, err := evdev.FindTouchscreen(os.O_RDONLY)
if err != nil {
return nil, err
}
minX, maxX, err := axisRange(f, absX)
if err != nil {
f.Close()
return nil, fmt.Errorf("x range: %w", err)
}
minY, maxY, err := axisRange(f, absY)
if err != nil {
f.Close()
return nil, fmt.Errorf("y range: %w", err)
}
if maxX <= minX || maxY <= minY {
f.Close()
return nil, fmt.Errorf("touchscreen axes are degenerate: x %d..%d y %d..%d",
minX, maxX, minY, maxY)
}
state := &touchState{}
go func() {
defer holdPanic()
defer f.Close()
buf := make([]byte, sizeofInputEvent*32)
defer ts.F.Close()
buf := make([]byte, evdev.EventSize*32)
var rawX, rawY int32
var down bool
for {
n, err := f.Read(buf)
n, err := ts.F.Read(buf)
// Nothing else feeds this state, so returning here would freeze the
// last touch in place and leave the reset button dead while the panel
// goes on looking alive.
if err != nil {
panic(fmt.Sprintf("reading touchscreen events: %v", err))
}
for o := 0; o+sizeofInputEvent <= n; o += sizeofInputEvent {
for o := 0; o+evdev.EventSize <= n; o += evdev.EventSize {
typ := binary.NativeEndian.Uint16(buf[o+16:])
code := binary.NativeEndian.Uint16(buf[o+18:])
val := int32(binary.NativeEndian.Uint32(buf[o+20:]))
switch typ {
case evAbs:
case evdev.Abs:
switch code {
case absX:
case evdev.AbsX:
rawX = val
case absY:
case evdev.AbsY:
rawY = val
}
case evKey:
if code == btnTouch {
case evdev.Key:
if code == evdev.BtnTouch {
down = val != 0
}
case evSyn:
if code != synReport {
case evdev.Syn:
if code != evdev.SynReport {
break
}
// Scaled onto [0, w-1], so full deflection is the last pixel
// rather than one past it.
state.set(
int(int64(rawX-minX)*int64(w-1)/int64(maxX-minX)),
int(int64(rawY-minY)*int64(h-1)/int64(maxY-minY)),
int(int64(rawX-ts.MinX)*int64(w-1)/int64(ts.MaxX-ts.MinX)),
int(int64(rawY-ts.MinY)*int64(h-1)/int64(ts.MaxY-ts.MinY)),
down)
}
}