Skip to content

Input System

pinjinx edited this page Aug 4, 2026 · 1 revision

The input system allows a phone or tablet to function as a wireless trackpad and keyboard for the host computer. It converts browser touch gestures into native operating system input events while maintaining low latency and platform-independent behavior.

From a user's perspective, every interaction feels like using a physical trackpad. Internally, Rein recognizes gestures, transports them over WebRTC DataChannels, validates incoming messages, and injects native input events using the appropriate operating system APIs.


Input Pipeline

Every input event follows the same processing pipeline.

Untitled-2026-08-02-1922 (5)

Gesture Recognition

Gesture recognition is handled by useTrackpadGesture (src/hooks/useTrackpadGesture.ts).

The hook listens for browser touch events (touchstart, touchmove, touchend, and touchcancel) and converts them into higher-level input actions that can be transmitted to the host.

Internally it maintains a map of active touch points and continuously updates gesture state as fingers are added, moved, or removed.

Supported Gestures

Fingers Gesture Generated Event
1 Move move
1 Tap Left click
2 Tap Right click
3 Tap Middle click
1 Long press + move Drag
2 Pan scroll
2 Pinch zoom
Scroll mode One finger Scroll

Scroll Axis Lock

When one-finger scroll mode is enabled, Rein prevents accidental diagonal scrolling by locking movement to the dominant axis.

if (absDx > absDy * axisThreshold)
    dy = 0

else if (absDy > absDx * axisThreshold)
    dx = 0

The default threshold is 2.5, meaning one direction must be significantly stronger before the other axis is ignored.


Movement Thresholds

Small unintended movements are ignored before a gesture begins.

Fingers Threshold
1 10 px
2 15 px
3+ 15 px

Drag Detection

Dragging is implemented by delaying the release of an initial tap. Untitled-2026-08-02-1922 (7)


Transport Layer

Once a gesture has been recognized, it is serialized into an InputMessage and sent over a WebRTC DataChannel.

Rein uses two separate DataChannels to balance responsiveness and reliability:

  • input-unordered channel — mouse movement, scrolling, zooming, and touch events (maxRetransmits=0).
  • input-ordered channel — clicks, keyboard events, text input, key combinations, copy/paste, and ping/pong heartbeats (ordered=true).

Continuous movement generates hundreds of updates per second. Retransmitting lost packets would only increase latency because the latest cursor position is always more valuable than an older one. Keyboard events, on the other hand, must never be lost or reordered.

ConnectionProvider automatically routes each message to the appropriate channel and falls back to the other channel if necessary.

const isUnordered =
    type === "move" ||
    type === "scroll" ||
    type === "touch" ||
    type === "zoom"

Latency Measurement (Ping/Pong)

To estimate connection latency, ConnectionProvider periodically exchanges ping and pong messages over the reliable DataChannel:

Client (ConnectionProvider)
   │ { type: "ping", timestamp: Date.now() }
   ▼
Server (WebRTCManager)
   │ { type: "pong", timestamp: timestamp }
   ▼
Client

The client measures RTT (Date.now() - timestamp) every 2 seconds. RTT telemetry is reported every 10 seconds to POST /api/debug/report-latency, allowing the host Debug Dashboard (/debug) to display real-time latency charts.


InputHandler

InputHandler (src/server/InputHandler.ts) is responsible for processing every incoming input message on the host.

Before an event reaches the operating system it passes through three stages:

  1. Validation and sanitization
  2. Movement acceleration and rate limiting (8 ms throttle)
  3. Platform driver dispatch

Message Validation

Incoming messages are sanitized before processing:

  • truncating excessively long text
  • clamping movement coordinates
  • validating button names
  • validating modifier combinations

These checks prevent malformed or malicious messages from reaching the platform injectors.


Movement Throttling

Mouse movement and scrolling can generate thousands of events per second.

To avoid overwhelming the operating system, Rein applies an 8 ms leading-edge throttle with a trailing flush.

This means:

  • the first event is processed immediately,
  • intermediate events are merged into aggregate delta offsets,
  • the final accumulated movement is always delivered.

The result is smooth 125 Hz cursor movement while avoiding CPU bottlenecking.


Pointer Acceleration

Rein applies an acceleration curve similar to a native desktop touchpad:

Small movement  ──► Precise cursor movement
Large movement  ──► Accelerated cursor movement

The acceleration curve is controlled by ACCEL_THRESHOLD, ACCEL_FACTOR, ACCEL_EXPONENT, and user-configurable sensitivity.


Event Dispatch

After processing, each message is forwarded to the platform injector.

Message Native Action
move Move mouse
click Mouse button
scroll Mouse wheel
zoom Ctrl + Mouse Wheel
key Single key
combo Keyboard shortcut
text Text input
touch Native touch contacts
copy Copy shortcut
paste Paste shortcut

Platform Input Injection

The final stage converts processed input into native operating system events:

Platform Implementation Mechanism
Linux LinuxInputInjector /dev/uinput virtual devices created via Koffi C FFI
macOS MacInputInjector CoreGraphics (CGEvent) system event injection
Windows WindowsInputInjector Win32 SendInput and Synthetic Pointer API

The correct injector is loaded automatically during startup based on os.platform().


Runtime Configuration

The input system can be reconfigured without restarting the application. When client devices change settings via POST /api/config, WebRTCManager.updateConfig() applies settings directly to all active InputHandler instances:

Setting Purpose
sensitivity Mouse speed multiplier
invertScroll Reverse scroll direction
inputThrottleMs Minimum interval between mouse updates (default 8ms)

Modifier Keys

Since touchscreens do not have physical modifier keys, Rein maintains a state machine (ReleaseActiveHoldRelease) enabling shortcuts such as Ctrl+C, Ctrl+Shift+V, and Alt+Tab.

The modifier state is managed inside the trackpad interface and automatically cleared after completing a shortcut.


Input Message Format

Every event transmitted over WebRTC DataChannels follows this structure:

interface InputMessage {
  type:
    | "move"
    | "click"
    | "scroll"
    | "key"
    | "text"
    | "zoom"
    | "combo"
    | "touch"
    | "copy"
    | "paste"
    | "ping"
    | "pong"

  dx?: number
  dy?: number

  button?: "left" | "right" | "middle"
  press?: boolean

  key?: string
  keys?: string[]

  text?: string

  delta?: number

  contacts?: TouchContact[]

  timestamp?: number
}

This format provides a platform-independent representation of every input event generated by the client and consumed by the host.

Clone this wiki locally