2026-08-31 14:58:51 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2026-08-31 15:51:52 -07:00
|
|
|
"g.fc.run/theater/cabletest/internal/evdev"
|
2026-08-31 14:58:51 -07:00
|
|
|
)
|
|
|
|
|
|
2026-08-31 15:51:52 -07:00
|
|
|
var touchscreen *evdev.Touchscreen
|
2026-08-31 14:58:51 -07:00
|
|
|
|
|
|
|
|
type inputEvent struct {
|
|
|
|
|
typ, code uint16
|
|
|
|
|
val int32
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-31 15:51:52 -07:00
|
|
|
// Events written to the node are fed into the input core as if the hardware
|
|
|
|
|
// produced them, so cabletest sees a finger without carrying any injection
|
|
|
|
|
// code. Timestamps are left zero: evdev stamps injected events at delivery.
|
|
|
|
|
func send(t *evdev.Touchscreen, evs []inputEvent) error {
|
|
|
|
|
buf := make([]byte, len(evs)*evdev.EventSize)
|
2026-08-31 14:58:51 -07:00
|
|
|
for i, e := range evs {
|
2026-08-31 15:51:52 -07:00
|
|
|
o := i * evdev.EventSize
|
2026-08-31 14:58:51 -07:00
|
|
|
binary.NativeEndian.PutUint16(buf[o+16:], e.typ)
|
|
|
|
|
binary.NativeEndian.PutUint16(buf[o+18:], e.code)
|
|
|
|
|
binary.NativeEndian.PutUint32(buf[o+20:], uint32(e.val))
|
|
|
|
|
}
|
2026-08-31 15:51:52 -07:00
|
|
|
_, err := t.F.Write(buf)
|
2026-08-31 14:58:51 -07:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The page speaks portrait canvas pixels; the device speaks the panel's
|
|
|
|
|
// landscape axes, so the quarter turn cabletest applies on the way in is
|
|
|
|
|
// inverted on the way out.
|
|
|
|
|
func touch(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
body, err := io.ReadAll(r.Body)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
f := strings.Fields(string(body))
|
|
|
|
|
if len(f) == 1 && f[0] == "up" {
|
2026-08-31 15:51:52 -07:00
|
|
|
if err := send(touchscreen, []inputEvent{{evdev.Key, evdev.BtnTouch, 0}, {evdev.Syn, 0, 0}}); err != nil {
|
2026-08-31 14:58:51 -07:00
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if len(f) != 3 || (f[0] != "down" && f[0] != "move") {
|
|
|
|
|
http.Error(w, "want down x y, move x y, or up", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
cx, errX := strconv.Atoi(f[1])
|
|
|
|
|
cy, errY := strconv.Atoi(f[2])
|
|
|
|
|
if errX != nil || errY != nil {
|
|
|
|
|
http.Error(w, "bad coordinates", http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
latestMu.RLock()
|
|
|
|
|
k := latest
|
|
|
|
|
latestMu.RUnlock()
|
|
|
|
|
if k.buf == nil {
|
|
|
|
|
http.Error(w, "no frame to locate the touch on", http.StatusServiceUnavailable)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
cx = min(max(cx, 0), k.ph-1)
|
|
|
|
|
cy = min(max(cy, 0), k.pw-1)
|
|
|
|
|
xl, yl := cy, k.ph-1-cx
|
|
|
|
|
t := touchscreen
|
2026-08-31 15:51:52 -07:00
|
|
|
rawX := t.MinX + int32(int64(xl)*int64(t.MaxX-t.MinX)/int64(k.pw-1))
|
|
|
|
|
rawY := t.MinY + int32(int64(yl)*int64(t.MaxY-t.MinY)/int64(k.ph-1))
|
|
|
|
|
evs := []inputEvent{{evdev.Abs, evdev.AbsX, rawX}, {evdev.Abs, evdev.AbsY, rawY}}
|
2026-08-31 14:58:51 -07:00
|
|
|
if f[0] == "down" {
|
2026-08-31 15:51:52 -07:00
|
|
|
evs = append(evs, inputEvent{evdev.Key, evdev.BtnTouch, 1})
|
2026-08-31 14:58:51 -07:00
|
|
|
}
|
2026-08-31 15:51:52 -07:00
|
|
|
evs = append(evs, inputEvent{evdev.Syn, 0, 0})
|
|
|
|
|
if err := send(touchscreen, evs); err != nil {
|
2026-08-31 14:58:51 -07:00
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|