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

  1. Ensure TouchBegin is called and succeeds for each touchId before any TouchUpdate for that ID
  2. Track touch lifecycle client-side; on 'unknown touch id' fall back to sending TouchBegin again to re-register the touch
  3. Stop sending updates for a touch after TouchEnd; generate a new touchId for the next gesture
  4. 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

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.