m1k1o/neko · warning
unknown touch id %v
Error message
unknown touch id %v
What it means
TouchUpdate is called to send an XI_TouchUpdate event for an in-progress touch gesture. The driver tracks active touch IDs in debounceTouchIds (populated by TouchBegin); if the given touchId is not present there, it means no touch with that ID was begun, so the driver refuses to synthesize an update and returns this error. It is a state-machine guard against orphan touch updates.
Source
Thrown at server/pkg/xinput/xinput.go:87
d.debounceTouchIds[touchId] = time.Now()
msg := Message{
_type: XI_TouchBegin,
touchId: touchId,
x: int32(x),
y: int32(y),
pressure: pressure,
}
_, err := d.conn.Write(msg.Pack())
return err
}
func (d *driver) TouchUpdate(touchId uint32, x, y int, pressure uint8) error {
d.mu.Lock()
defer d.mu.Unlock()
if _, ok := d.debounceTouchIds[touchId]; !ok {
return fmt.Errorf("unknown touch id %v", touchId)
}
d.debounceTouchIds[touchId] = time.Now()
msg := Message{
_type: XI_TouchUpdate,
touchId: touchId,
x: int32(x),
y: int32(y),
pressure: pressure,
}
_, err := d.conn.Write(msg.Pack())
return err
}
func (d *driver) TouchEnd(touchId uint32, x, y int, pressure uint8) error {
d.mu.Lock()
defer d.mu.Unlock()View on GitHub (pinned to b0f01cedea)
Solutions
- Ensure TouchBegin is called and succeeds for each touchId before any TouchUpdate for that ID
- Track touch lifecycle client-side; on 'unknown touch id' fall back to sending TouchBegin again to re-register the touch
- Stop sending updates for a touch after TouchEnd; generate a new touchId for the next gesture
- Check for duplicate/parallel input clients reusing the same touch IDs
Example fix
// before
if err := xinput.TouchUpdate(id, x, y, p); err != nil { log.Fatal(err) }
// after
if err := xinput.TouchUpdate(id, x, y, p); err != nil {
// re-register the touch then retry
if beginErr := xinput.TouchBegin(id, x, y, p); beginErr == nil {
_ = xinput.TouchUpdate(id, x, y, p)
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// caller-side touch state
var activeTouches = map[uint32]bool{}
func canUpdate(id uint32) bool { return activeTouches[id] } Type guard
func isUnknownTouchID(err error) bool {
return err != nil && strings.Contains(err.Error(), "unknown touch id")
} Try / catch
if err := d.TouchUpdate(id, x, y, p); err != nil {
if isUnknownTouchID(err) {
// re-begin then retry, or drop the stale touch
_ = d.TouchBegin(id, x, y, p)
err = d.TouchUpdate(id, x, y, p)
}
if err != nil { return err }
} Prevention
- Always call TouchBegin before TouchUpdate for a given ID
- Generate unique touchIds per gesture; never reuse after TouchEnd
- Treat reconnects as gesture abort: reset local touch state
- Keep a local mirror of active touch IDs and check it before each call
When it happens
Trigger: Calling TouchUpdate(touchId, x, y, pressure) with a touchId that was never registered via TouchBegin, or after the touch was already finished with TouchEnd (which deletes the ID), or after an internal debounce cleanup removed the ID.
Common situations: Client reconnects mid-gesture and resumes sending TouchUpdate for a touch the server never saw; a lost/undelivered TouchBegin due to network issues; double-sending TouchEnd then continuing updates; race where two clients drive the same touch sequence.
Related errors
AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01).
Data as JSON: /api/errors/184bf58c324cc159.
Report an issue: GitHub.