// Copyright (c) 2026 Storm Dragon // Authors: Storm Dragon, Angus Kola // SPDX-License-Identifier: GPL-3.0-or-later package input import ( "errors" "time" ) const ( // RemoteRepeatReleaseDelay is the initial bound for holding a release // while waiting for its classic X11 autorepeat press. It is a tunable // latency limit for a private capture owner, not a measured hardware value. RemoteRepeatReleaseDelay = 25 * time.Millisecond RemoteRepeatReleaseDelayMax = 250 * time.Millisecond remoteRepeatMinKeycode = 8 ) var ErrRemoteRepeatDelay = errors.New("remote repeat release delay is invalid") // RemoteRepeatTransition is one normalized source transition. Repeat is set // only on a press for a key that is already owned; no synthetic release is // emitted between repeats. type RemoteRepeatTransition struct { Keycode uint8 Pressed bool Repeat bool } // RemoteRepeatNormalizer converts native key observations into the stream // required by Remote's outgoing encoder: one fresh down, repeated downs and // one final up. Classic X11 autorepeat reports release/press pairs with the // same server timestamp; a nonzero-timestamp release is therefore held until // the next observation or its deadline. Detectable autorepeat already reports // repeated downs and passes through unchanged. // // One instance belongs to one capture lifetime and one serial native owner. // It owns only keys whose downs it observed: releases for keys held before // capture are suppressed and never reported as Remote-owned. Admission stops // on an invalid keycode or more than RemoteHeldKeyLimit held keys; Retire still // returns releases for every owned key. The normalizer performs no I/O, grants // no consent and does not establish physical-source provenance. type RemoteRepeatNormalizer struct { delay time.Duration held [256]bool order []uint8 pending bool pendingCode uint8 pendingStamp uint32 pendingDeadline time.Time stopped bool retired bool } func NewRemoteRepeatNormalizer(delay time.Duration) (*RemoteRepeatNormalizer, error) { if delay <= 0 || delay > RemoteRepeatReleaseDelayMax { return nil, ErrRemoteRepeatDelay } return &RemoteRepeatNormalizer{delay: delay, order: make([]uint8, 0, RemoteHeldKeyLimit)}, nil } // Observe admits one native transition. A successful call can return zero, // one or two transitions; a returned error stops admission permanently while // retaining ownership for Retire. The caller must not drop returned work. func (normalizer *RemoteRepeatNormalizer) Observe(event Event, now time.Time) ([]RemoteRepeatTransition, error) { if normalizer == nil || normalizer.stopped { return nil, ErrRemoteKeyState } if event.Keycode < remoteRepeatMinKeycode { normalizer.stopped = true return nil, ErrRemoteKeyUnavailable } var out []RemoteRepeatTransition if normalizer.pending { if event.Pressed && event.Keycode == normalizer.pendingCode && event.Timestamp == normalizer.pendingStamp { normalizer.pending = false return []RemoteRepeatTransition{{Keycode: event.Keycode, Pressed: true, Repeat: true}}, nil } out = append(out, normalizer.releasePending()) } code := event.Keycode if event.Pressed { if normalizer.held[code] { return append(out, RemoteRepeatTransition{Keycode: code, Pressed: true, Repeat: true}), nil } if len(normalizer.order) >= RemoteHeldKeyLimit { normalizer.stopped = true return out, ErrRemoteKeyLimit } normalizer.held[code] = true normalizer.order = append(normalizer.order, code) return append(out, RemoteRepeatTransition{Keycode: code, Pressed: true}), nil } if !normalizer.held[code] { return out, nil } if event.Timestamp == 0 { // Unknown timestamps cannot prove a classic pair; release now. normalizer.forget(code) return append(out, RemoteRepeatTransition{Keycode: code}), nil } normalizer.pending = true normalizer.pendingCode = code normalizer.pendingStamp = event.Timestamp normalizer.pendingDeadline = now.Add(normalizer.delay) return out, nil } // Deadline reports when a held release must be flushed. The owner should arm // a timer for it rather than poll. func (normalizer *RemoteRepeatNormalizer) Deadline() (time.Time, bool) { if normalizer == nil || normalizer.retired || !normalizer.pending { return time.Time{}, false } return normalizer.pendingDeadline, true } // Flush emits a held release once its deadline has been reached. Pairing is // decided by event content, not by timer accuracy: a late flush only adds // latency, and a press arriving after a flush is a fresh down. func (normalizer *RemoteRepeatNormalizer) Flush(now time.Time) []RemoteRepeatTransition { if normalizer == nil || normalizer.retired || !normalizer.pending || now.Before(normalizer.pendingDeadline) { return nil } return []RemoteRepeatTransition{normalizer.releasePending()} } // Retire stops the normalizer and transfers releases for every owned key in // reverse down order, including a held pending release. It returns the plan // once; repeated retirement returns nothing. func (normalizer *RemoteRepeatNormalizer) Retire() []RemoteRepeatTransition { if normalizer == nil || normalizer.retired { return nil } normalizer.stopped = true normalizer.retired = true normalizer.pending = false out := make([]RemoteRepeatTransition, 0, len(normalizer.order)) for index := len(normalizer.order) - 1; index >= 0; index-- { out = append(out, RemoteRepeatTransition{Keycode: normalizer.order[index]}) } normalizer.held = [256]bool{} normalizer.order = normalizer.order[:0] return out } func (normalizer *RemoteRepeatNormalizer) releasePending() RemoteRepeatTransition { code := normalizer.pendingCode normalizer.pending = false normalizer.forget(code) return RemoteRepeatTransition{Keycode: code} } func (normalizer *RemoteRepeatNormalizer) forget(code uint8) { normalizer.held[code] = false for index, held := range normalizer.order { if held == code { copy(normalizer.order[index:], normalizer.order[index+1:]) normalizer.order = normalizer.order[:len(normalizer.order)-1] return } } }