Repository navigation
Input System
The input system allows a phone, tablet, or browser to function as a wireless trackpad, keyboard, and virtual gamepad for the host computer. It converts browser touch gestures and on-screen controls into native operating system input events while maintaining sub-8ms latency and platform-independent behavior.
From a user's perspective, every interaction feels immediate and natural. Internally, Rein recognizes gestures, transports them over specialized WebRTC DataChannels, validates and sanitizes incoming messages, throttles high-frequency motion, and injects native input events using cross-platform @imxade/inject drivers (Linux, macOS, Windows).
Every input event follows the same processing pipeline.
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.
| Fingers | Gesture | Generated Event | Channel |
|---|---|---|---|
| 1 | Move | Relative cursor move (dx, dy) |
input-unordered |
| 1 | Tap (within 250ms) | Left click (button: "left", press: true/false) |
input-ordered |
| 2 | Tap (within 250ms) | Right click (button: "right") |
input-ordered |
| 3 | Tap (within 250ms) | Middle click (button: "middle") |
input-ordered |
| 1 | Tap + immediate move | Dragging (holds left button while moving) | input-unordered |
| 2 | Pan | Two-finger scroll (dx, dy) |
input-unordered |
| 2 | Pinch | Pinch-to-zoom (delta) |
input-unordered |
| Scroll mode | 1 finger | Directional scroll with axis lock |
input-unordered |
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 = 0The default threshold is 2.5, meaning one direction must be significantly stronger before the other axis is ignored.
Small unintended movements are ignored before a gesture begins:
| Fingers | Threshold |
|---|---|
| 1 | 10 px |
| 2 | 15 px |
| 3+ | 15 px |
Dragging is implemented by converting a quick double-tap-and-hold into an active drag state:
In addition to relative trackpad mode, Rein supports direct touchscreen tap-to-click via rein_mouse_mode:
-
Letterbox-Aware Coordinate Normalization (
toNormalisedRatio): The video screen mirror renders with CSSobject-fit: contain. When a user taps anywhere on the screen,toNormalisedRatio()calculates the exact content rectangle (accounting for horizontal pillarboxing or vertical letterboxing) and computes normalizedx, y ∈ [0, 1]ratios relative to the desktop display. -
absoluteClickEvent: Sent overinput-ordered:{ type: "absoluteClick", button: "left", press: true, x: 0.452, y: 0.781 }
-
Host Injection:
The host
InputHandlermultiplies normalized ratios by configured screen dimensions (absX = msg.x * screenWidth,absY = msg.y * screenHeight), callsinjectMouseAbsolute(absX, absY)to warp the cursor instantly without relative acceleration artifacts, and firesinjectMouseButton(button, press). -
Physical Mouse Pointer Lock (
useMouseInput): When using a desktop browser to control another desktop,useMouseInput.tscaptures pointer lock on mouse click, locks the cursor, routes rawmovementX/movementYdeltas, passes native mouse button states, wheel events, and physical keyboard shortcuts directly to the remote host.
Rein includes a built-in virtual gamepad controller accessible at /gamepad (src/routes/gamepad.tsx).
-
Dual Analog Thumbsticks: Left stick (
ls) and right stick (rs) with smooth touch tracking and deadzone handling. -
D-Pad: Directional pad (
dpad-up,dpad-down,dpad-left,dpad-right). -
Action Buttons: Standard Xbox-style diamond buttons (
a,b,x,y). -
Shoulder Buttons & Triggers: Left/Right bumpers (
lb,rb) and digital triggers (lt,rt). -
Menu Buttons:
startandselect. -
Customizable Layout: Opacity, scale, and thumbstick positions can be adjusted in the Gamepad Settings tab and saved to
localStorage(rein_gamepad_layout).
-
Digital Buttons:
{ type: "gamepad", button: GamepadButtonId, press: boolean }sent viainput-ordered(ordered: true). -
Analog Axes:
{ type: "gamepad-axis", axis: "ls" | "rs", ax: number, ay: number }sent viainput-unordered(maxRetransmits: 0).- Axis coordinates are normalized between
-1.0and+1.0. - Throttled independently per stick at 8ms.
- Releasing a stick back to neutral
(0, 0)immediately cancels any pending throttle timer and dispatches instantly to guarantee the character stops moving immediately.
- Axis coordinates are normalized between
Rein balances responsiveness and reliability using two dedicated WebRTC DataChannels:
-
input-unorderedchannel (ordered: false, maxRetransmits: 0): Used for high-frequency continuous motion:move,scroll,zoom,touch,gamepad-axis. Dropping an occasional intermediate motion packet is vastly preferable to stalling subsequent packets with Head-of-Line (HoL) blocking. -
input-orderedchannel (ordered: true): Used for discrete state transitions and commands that must never be lost or reordered:click,absoluteClick,key,text,combo,copy,paste,gamepad,ping/pong.
ConnectionProvider automatically routes each event to the correct channel and provides seamless fallback if a channel is temporarily unavailable.
To measure connection latency, ConnectionProvider 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 and reports the value to POST /api/debug/report-latency every 10 seconds.
InputHandler processes every incoming input message on the host before delegating to the native operating system injector:
-
Validation & Sanitization:
- Caps text length to
MAX_TEXT_LENGTH = 10000. - Clamps motion coordinates
dx,dy,deltato[-2000, 2000]. - Clamps gamepad axes
ax,ayto[-1, 1]. - Clamps normalized absolute coordinates
x,yto[0, 1]. - Validates mouse buttons and combo key arrays.
- Caps text length to
-
Rate Limiting & Throttling (8ms):
- Uses an 8ms leading-edge throttle with trailing timer flush for
move,scroll, andgamepad-axis. - Prevents OS input event queue saturation while ensuring the final position is always delivered.
- Uses an 8ms leading-edge throttle with trailing timer flush for
-
Motion Acceleration Curve (
applyMotion):- Computes non-linear acceleration for trackpad cursor movement based on velocity:
ax = Math.sign(dx) * Math.pow(Math.abs(dx), ACCEL_EXPONENT) * ACCEL_FACTOR * sensitivity
- Computes non-linear acceleration for trackpad cursor movement based on velocity:
-
Safety & Cleanup:
- Automatically releases mouse buttons if an injection call throws, preventing stuck clicks.
- Clears pending motion and stick timers on session destroy.
After sanitization and throttling, InputHandler dispatches commands to the platform driver implementing PlatformInjector. All native input injection is built on @imxade/inject, providing lightweight low-level system input primitives without C++ compilation requirements:
| Platform | Injector Class | Module | Native Injection Mechanism |
|---|---|---|---|
| Linux | LinuxInputInjector |
@imxade/inject/linux |
/dev/uinput devices (mouse, absolute pointer, keyboard, multitouch, Xbox 360 gamepad) |
| macOS | MacInputInjector |
@imxade/inject/mac |
CoreGraphics CGEvent system event injection & IOHIDUserDevice virtual gamepad |
| Windows | WindowsInputInjector |
@imxade/inject/windows |
Win32 SendInput, Synthetic Pointer API (POINTER_TYPE_INFO), & ViGEmBus Xbox 360 controller |
For complete implementation details on virtual devices, permissions, and report structures, see Platform Drivers.
Rein supports bidirectional clipboard sharing between the client and host:
-
HTTP Clipboard API (
POST /api/clipboard/copy&POST /api/clipboard/paste):-
/api/clipboard/copy: SynthesizesCtrl+C(orCmd+Con macOS), waits 100ms for the OS clipboard to populate, and reads plain text using native tools (pbpasteon macOS, PowerShellGet-Clipboardon Windows,wl-paste/xclip/xselon Linux). -
/api/clipboard/paste: Writes client text into the host system clipboard via stdin without command-injection vulnerabilities, then synthesizesCtrl+V(orCmd+Von macOS).
-
-
DataChannel Copy/Paste:
- The trackpad control bar provides one-tap Copy and Paste buttons which send
{ type: "copy" }and{ type: "paste" }shortcuts directly overinput-ordered.
- The trackpad control bar provides one-tap Copy and Paste buttons which send
export interface TouchContact {
id: number
x: number
y: number
state: "down" | "move" | "up"
}
export type MouseButton = "left" | "right" | "middle"
export type GamepadButtonId =
| "a" | "b" | "x" | "y"
| "lb" | "rb" | "lt" | "rt"
| "start" | "select"
| "dpad-up" | "dpad-down" | "dpad-left" | "dpad-right"
| "ls" | "rs"
export type InputMessage = {
dx?: number
dy?: number
config?: Partial<InputConfig>
key?: string
keys?: string[]
text?: string
delta?: number
contacts?: TouchContact[]
axis?: "ls" | "rs"
ax?: number
ay?: number
} & (
| { type: "move" }
| { type: "paste" }
| { type: "copy" }
| { type: "click"; button: MouseButton; press: boolean }
| { type: "absoluteClick"; button: MouseButton; press: boolean; x: number; y: number }
| { type: "scroll" }
| { type: "key" }
| { type: "text" }
| { type: "zoom" }
| { type: "combo" }
| { type: "touch" }
| { type: "gamepad"; button: GamepadButtonId; press: boolean }
| { type: "gamepad-axis" }
)