Reset everything from a held on-screen button or space
This commit is contained in:
@@ -1,11 +1,158 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
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 {
|
||||
mu sync.Mutex
|
||||
x, y int
|
||||
down bool
|
||||
}
|
||||
|
||||
func (t *touchState) set(x, y int, down bool) {
|
||||
t.mu.Lock()
|
||||
t.x, t.y, t.down = x, y, down
|
||||
t.mu.Unlock()
|
||||
}
|
||||
|
||||
func (t *touchState) get() (x, y int, down bool) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
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()
|
||||
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 f.Close()
|
||||
buf := make([]byte, sizeofInputEvent*32)
|
||||
var rawX, rawY int32
|
||||
var down bool
|
||||
for {
|
||||
n, err := f.Read(buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for o := 0; o+sizeofInputEvent <= n; o += sizeofInputEvent {
|
||||
typ := *(*uint16)(unsafe.Pointer(&buf[o+16]))
|
||||
code := *(*uint16)(unsafe.Pointer(&buf[o+18]))
|
||||
val := *(*int32)(unsafe.Pointer(&buf[o+20]))
|
||||
switch typ {
|
||||
case evAbs:
|
||||
switch code {
|
||||
case absX:
|
||||
rawX = val
|
||||
case absY:
|
||||
rawY = val
|
||||
}
|
||||
case evKey:
|
||||
if code == btnTouch {
|
||||
down = val != 0
|
||||
}
|
||||
case evSyn:
|
||||
if code != synReport {
|
||||
break
|
||||
}
|
||||
state.set(
|
||||
int(int64(rawX-minX)*int64(w)/int64(maxX-minX)),
|
||||
int(int64(rawY-minY)*int64(h)/int64(maxY-minY)),
|
||||
down)
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// Delivers a value every time space is pressed. Puts the terminal in
|
||||
// non-canonical mode so the keypress arrives without waiting for a newline,
|
||||
// and returns a function that restores the original settings.
|
||||
|
||||
Reference in New Issue
Block a user