{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/touch-pc",
  "slug": "engines/touch-pc",
  "kind": "capabilities",
  "bucket": "package",
  "title": "@leumas/touch-pc",
  "name": "@leumas/touch-pc",
  "eyebrow": null,
  "chip": null,
  "summary": "Devices engine — turn a phone/tablet into a LAN remote for the host PC (mouse/keyboard input over a WebSocket) and drive multi-display setups (kiosk scenes on each monitor). Input synthesis comes...",
  "keywords": [
    "touch-pc",
    "tablet",
    "multi-display",
    "kiosk",
    "createtouchpcrouter",
    "touch pc api",
    "leumas touch pc",
    "how to use touch pc"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "# @leumas/touch-pc\n\nThe **Devices** engine. Two hardware surfaces in one package:\n\n1. **Remote control** — turn a phone/tablet into a LAN remote for the host PC. A device pairs with a\n   rotating **PIN**, gets a session token, then streams mouse/keyboard frames over a WebSocket; the\n   host applies them via an **input engine** (`robotjs`, or a `noop` fallback when native input isn't\n   available). Remote layouts (touchpad, keyboard, controller, joystick, gyro, hybrid) are served to\n   the device.\n2. **Multi-display** — register the host's monitors and throw full-screen **kiosk scenes** onto them.\n   Scenes are saved JSON layouts; the actual browser launch is delegated to an injectable `launcher`.\n\n**Absorbs (DRY-consolidated from):** `tools/leumas-touch-pc` (input engine + WS protocol + PIN\npairing / sessions + remotes registry + LAN security) and `tools/infinity-displays` (multi-display\nkiosk scenes / slideshows).\n\n> **LAN-only — gate hard.** This engine synthesizes *real host input* and controls displays. The\n> router runs a `lanGuard` first and unconditionally rejects non-private callers, and the WS bridge\n> must only be attached to a LAN-bound listener. `robotjs` is lazy-loaded, so on hosts without it the\n> engine degrades to a safe `noop` instead of crashing.\n\n## Usage (in leumas-api)\n\n```js\nimport { createTouchPcRouter, createPinPairing, createSessionStore } from '@leumas/touch-pc';\nimport { createTouchPcWsServer } from '@leumas/touch-pc/ws';\nimport { requireAuth } from '@leumas/auth';\n\nconst pinPairing = createPinPairing();       // rotating 6-digit PIN\nconst sessionStore = createSessionStore();   // in-memory device sessions\n\napp.use('/api/devices', createTouchPcRouter({ pinPairing, sessionStore, requireAuth }));\n\n// Attach the input WebSocket to the SAME http server. LAN-only.\ncreateTouchPcWsServer({ server, pinPairing, sessionStore, inputEngine: 'auto' });\nconsole.log('Pairing PIN:', pinPairing.getPin());\n```\n\n## Routes (`/api/devices`)\n\n| Method | Path | Purpose |\n| --- | --- | --- |\n| GET | `/config` | client bootstrap `{ wsPath, pairingRequired, remotes }` |\n| POST | `/pair` | `{ pin, deviceName }` → `{ sessionToken, sessionId }` |\n| GET | `/sessions` | paired devices + pairing status |\n| GET | `/remotes` | available remote layouts |\n| GET/POST | `/displays` | list / register monitors |\n| POST | `/displays/:id/scene` | assign + launch a scene on a display |\n| GET/POST | `/scenes` | list / upsert scenes |\n| DELETE | `/scenes/:id` | remove a scene |\n\n## The input engine\n\n`createInputEngine({ inputEngine })` returns one object implementing `moveMouse / moveMouseAbs /\nmouseDown / mouseUp / click / scroll / keyTap / keyDown / keyUp / typeText`.\n\n- `'auto'` (default) → try `robotjs`, else `noop`.\n- `'robotjs'` → require `robotjs` (lazy); `noop` if it can't load.\n- `'noop'` → swallow everything (safe default for headless/CI).\n\n## The WS protocol\n\nClient frames are JSON `{ type, payload }`; `type ∈ { auth, mouse.move, mouse.down, mouse.up,\nmouse.click, mouse.scroll, key.tap, key.down, key.up, text.type, system.ping }`. The server\nrate-limits (token bucket, 120/s), requires an `auth` frame with a valid session token when pairing\nis on, then applies each frame to the input engine. `system.ping` → `system.pong`.\n\n## The display launcher contract\n\n```\nlauncher.launch({ display, sceneId }) → { ok, launched, ... }\n```\n\nDefault is a `noop` launcher. In a host app (Electron / a kiosk agent) inject one that spawns a\nfull-screen browser at the scene URL positioned on `display.bounds`.\n",
  "source": {
    "path": "shared/engines/hardware/touch-pc/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 3813,
    "hash": "1076dc665ca0ea2c237a6bd2613937dc0a9bf108"
  },
  "urls": {
    "html": "/p/engines/touch-pc",
    "json": "/docs/engines/touch-pc.json",
    "md": "/docs/engines/touch-pc.md"
  },
  "links": {
    "composes": [],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  },
  "exports": null
}
