|
1 | | -# LocalTV Remote |
| 1 | +<div align="center"> |
2 | 2 |
|
3 | | -Turn your phone into a wireless mouse, keyboard, and media remote for your |
4 | | -Windows PC — over your local network, with no app store install on the phone |
5 | | -(it's a PWA you open in the browser). |
| 3 | +<img src="https://capsule-render.vercel.app/api?type=waving&color=0:6366f1,100:a855f7&height=200§ion=header&text=LocalTV%20Remote&fontSize=60&fontColor=ffffff&fontAlignY=38&animation=fadeIn&desc=Phone%20→%20Windows%20Remote%20Control&descSize=20&descAlignY=58&descColor=e2e8f0" width="100%" /> |
6 | 4 |
|
7 | | -This is the **open-source, remote-control-only** sibling of LocalTV. It contains |
8 | | -**no embedded browser, no DRM, and no TV/streaming mode** — just the remote |
9 | | -control daemon. As a result it runs as a single lightweight Node process instead |
10 | | -of a full Electron/Chromium app. |
| 5 | +<a href="https://readme-typing-svg.demolab.com"> |
| 6 | + <img src="https://readme-typing-svg.demolab.com?font=Fira+Code&size=20&pause=1000&color=6366F1¢er=true&vCenter=true&width=620&lines=Turn+your+phone+into+a+Windows+remote;Scan+a+QR+code+%E2%80%94+no+app+install+needed;Mouse+%E2%80%A2+Keyboard+%E2%80%A2+Scroll+%E2%80%A2+Volume;LAN-only+%E2%80%94+no+cloud%2C+no+accounts%2C+no+Chromium" alt="Typing animation" /> |
| 7 | +</a> |
11 | 8 |
|
12 | | -## Why it's small |
| 9 | +<br/> |
| 10 | +<br/> |
13 | 11 |
|
14 | | -The original LocalTV is an Electron app (~410 MB packaged) because it embeds |
15 | | -Chromium for DRM streaming. Remote control never needed any of that: it's an |
16 | | -HTTP + WebSocket server plus Win32 `SendInput`. This project ships only that, so |
17 | | -the runtime is a single Node process (~25–40 MB RAM at idle) and the download is |
18 | | -dominated by the Node runtime (~50–90 MB) rather than a bundled browser. |
| 12 | +[](LICENSE) |
| 13 | +[](https://github.com/creationsofm7/localtv-remote/releases) |
| 14 | +[](https://github.com/creationsofm7/localtv-remote/releases/latest) |
| 15 | +[](https://github.com/creationsofm7/localtv-remote/releases/latest) |
| 16 | +[](https://github.com/creationsofm7/localtv-remote/stargazers) |
19 | 17 |
|
20 | | -The desktop pairing window uses the **system WebView2 runtime** (already present |
21 | | -on Windows 10/11) — not a bundled Chromium — and falls back to your default |
22 | | -browser if WebView2 isn't available. |
| 18 | +</div> |
| 19 | + |
| 20 | +--- |
| 21 | + |
| 22 | +## What is this? |
| 23 | + |
| 24 | +**LocalTV Remote** is a lightweight Windows daemon that turns your phone into a wireless remote — mouse, keyboard, scroll wheel, and volume control — over your local Wi-Fi. Your phone opens a PWA in the browser (no app store install). The PC runs a single Node process at ~25–40 MB RAM. |
| 25 | + |
| 26 | +> This is the **open-source, remote-only** sibling of LocalTV. It contains no Chromium, no DRM, and no TV/streaming mode — just the remote control daemon. |
| 27 | +
|
| 28 | +--- |
| 29 | + |
| 30 | +## Features |
| 31 | + |
| 32 | +| | | |
| 33 | +|---|---| |
| 34 | +| 🖱️ **Mouse control** | Move, click, right-click, scroll — with DPI-aware scaling | |
| 35 | +| ⌨️ **Keyboard input** | Type text and send key combos from your phone | |
| 36 | +| 🔊 **Volume control** | Accurate system volume via Windows Core Audio (COM) | |
| 37 | +| 📱 **PWA — no install** | Phone opens a web page, installs as a home-screen app | |
| 38 | +| 🔗 **QR pairing** | Scan once, token is remembered for future sessions | |
| 39 | +| 🔒 **LAN-only** | No cloud relay, no accounts, no data leaves your network | |
| 40 | +| 🪶 **Tiny footprint** | ~27 MB installer, ~25–40 MB RAM at idle, no Chromium | |
| 41 | +| 🖥️ **System tray** | Sits in the tray; optional start-on-login toggle | |
| 42 | +| 🔌 **Auto port** | Free-port fallback if 3000 is occupied — QR auto-updates | |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +## Install |
| 47 | + |
| 48 | +### Option 1 — Installer (recommended) |
| 49 | + |
| 50 | +Download **[LocalTVRemote-Setup-0.1.0.exe](https://github.com/creationsofm7/localtv-remote/releases/latest)** from the latest release and run it. |
| 51 | + |
| 52 | +The setup wizard will: |
| 53 | +- Install to `%ProgramFiles%\LocalTV Remote` |
| 54 | +- Create a Start Menu shortcut |
| 55 | +- Add a Windows Firewall inbound rule (so your phone can reach the daemon without a popup) |
| 56 | +- Optionally create a desktop shortcut and enable start-on-login |
| 57 | + |
| 58 | +### Option 2 — winget *(coming soon)* |
| 59 | + |
| 60 | +``` |
| 61 | +winget install LocalTV.Remote |
| 62 | +``` |
| 63 | + |
| 64 | +--- |
| 65 | + |
| 66 | +## How to use |
| 67 | + |
| 68 | +1. **Launch** — double-click the tray icon or start from the Start Menu |
| 69 | +2. **Pair** — a pairing window opens with a QR code and a 6-digit PIN |
| 70 | +3. **Scan** — open your phone camera, scan the QR code |
| 71 | +4. **Control** — the phone browser opens the controller UI; swipe to move, tap to click |
| 72 | + |
| 73 | +Your phone remembers the pairing token — next time it reconnects automatically. |
| 74 | + |
| 75 | +--- |
23 | 76 |
|
24 | 77 | ## How it works |
25 | 78 |
|
26 | | -1. The daemon starts an Express + `ws` server on `0.0.0.0:3000`. |
27 | | -2. It shows a pairing window with a QR code and a stable 6-digit code. |
28 | | -3. On your phone (same Wi-Fi), scan the QR or open `http://<pc-ip>:3000` and |
29 | | - enter the code. |
30 | | -4. Input from the phone is injected OS-wide via koffi → Win32 `SendInput`. |
| 79 | +``` |
| 80 | +Phone browser (PWA) |
| 81 | + │ WebSocket + HTTP (LAN) |
| 82 | + ▼ |
| 83 | +LocalTVRemote.exe ←── Express + ws server on 0.0.0.0:3000 |
| 84 | + │ |
| 85 | + ├── Mouse / Keyboard → koffi → Win32 SendInput |
| 86 | + ├── Volume → PowerShell COM → IAudioEndpointVolume |
| 87 | + └── Pairing window → WebView2 (system runtime, not bundled Chromium) |
| 88 | +``` |
| 89 | + |
| 90 | +- Input is injected OS-wide via `SendInput` — works in any app including games and full-screen windows |
| 91 | +- Volume uses a persistent PowerShell STA process talking to Windows Core Audio COM directly — accurate to the system tray to within ±1% |
| 92 | +- The pairing window uses the **system WebView2 runtime** (pre-installed on Windows 11, auto-installed on Windows 10) — no bundled browser |
31 | 93 |
|
32 | | -### Security model |
33 | | -- LAN-only; binds the local network interface. |
34 | | -- Pairing requires a 6-digit code derived from the machine identity; trusted |
35 | | - sessions resume via a stored token. |
36 | | -- WebSocket upgrades are origin-checked; messages are rate-limited |
37 | | - (500/sec/client) with auth + inactivity timeouts. |
38 | | -- The pairing page (`/host`, `/api/state`, `/api/quit`) is restricted to |
39 | | - loopback, so the pair code is never served to LAN peers. |
| 94 | +--- |
40 | 95 |
|
41 | | -## Develop / run |
| 96 | +## Security |
| 97 | + |
| 98 | +- **LAN-only** — binds `0.0.0.0` for control but restricts pairing routes (`/host`, `/api/state`) to loopback, so the PIN is never served to other devices on the network |
| 99 | +- **6-digit pairing PIN** derived from machine identity; trusted sessions resume via a stored token |
| 100 | +- **WebSocket origin checks** on every upgrade |
| 101 | +- **Rate limiting** — 500 messages/sec/client; idle clients are disconnected after inactivity |
| 102 | +- **Single-instance lock** — second launch exits cleanly instead of conflicting |
| 103 | + |
| 104 | +--- |
| 105 | + |
| 106 | +## Build from source |
| 107 | + |
| 108 | +**Requirements:** Node.js 22+, Windows 10/11 |
42 | 109 |
|
43 | 110 | ```bash |
| 111 | +git clone https://github.com/creationsofm7/localtv-remote |
| 112 | +cd localtv-remote |
44 | 113 | npm install |
45 | | -npm run build |
46 | | -npm start |
47 | 114 | ``` |
48 | 115 |
|
49 | | -Or for a quick dev loop: `npm run dev` (uses `tsx`). |
| 116 | +**Dev mode** (TypeScript, no build step): |
| 117 | +```bash |
| 118 | +npm run dev |
| 119 | +``` |
| 120 | + |
| 121 | +**Full production build** (SEA exe + payload): |
| 122 | +```bash |
| 123 | +npm run dist # typecheck + bundle + branded exe + payload |
| 124 | +npm run installer # compile Inno Setup → release/LocalTVRemote-Setup-x.y.z.exe |
| 125 | +``` |
50 | 126 |
|
51 | | -Environment: |
52 | | -- `LOCALTV_REMOTE_PORT` — server port (default `3000`). |
53 | | -- `LOCALTV_REMOTE_STATIC_DIR` — override the controller PWA directory. |
| 127 | +**Environment variables:** |
54 | 128 |
|
55 | | -## Cross-platform |
| 129 | +| Variable | Default | Description | |
| 130 | +|---|---|---| |
| 131 | +| `LOCALTV_REMOTE_PORT` | `3000` | Preferred server port (auto-increments if occupied) | |
| 132 | +| `LOCALTV_REMOTE_STATIC_DIR` | `public/control` | Override the controller PWA directory | |
56 | 133 |
|
57 | | -Windows is the only supported target today. The input layer is abstracted behind |
58 | | -`SystemInputBackend` (`src/core/main/input/backends/input-backend.ts`); adding |
59 | | -macOS/Linux is a matter of implementing that interface and wiring it in |
60 | | -`SystemInputRouter`. |
| 134 | +--- |
61 | 135 |
|
62 | 136 | ## Project layout |
63 | 137 |
|
64 | | -- `src/core/` — modules shared with the (proprietary) LocalTV TV app, decoupled |
65 | | - from Electron. The server is typed against an `InputRouter` interface so it |
66 | | - runs with or without Electron. |
67 | | -- `src/daemon/` — the headless daemon: `SystemInputRouter`, the control server |
68 | | - with the desktop pairing routes, the WebView2 window shell, and the tray. |
69 | | -- `public/control/` — the phone controller PWA (served as-is). |
| 138 | +``` |
| 139 | +src/ |
| 140 | +├── core/ |
| 141 | +│ ├── server/ # Express + WebSocket control server |
| 142 | +│ ├── main/ |
| 143 | +│ │ ├── audio/ # Persistent PowerShell Core Audio host |
| 144 | +│ │ ├── input/ # SystemInputRouter + Win32 SendInput backend |
| 145 | +│ │ └── native/ # koffi FFI bindings, native-require loader |
| 146 | +│ └── shared/ # Protocol types, pairing logic |
| 147 | +└── daemon/ |
| 148 | + ├── index.ts # Entry: daemon mode or --webview child mode |
| 149 | + ├── tray.ts # systray2 tray icon + menu |
| 150 | + └── startup.ts # Run-at-login registry toggle |
| 151 | +
|
| 152 | +public/control/ # Phone controller PWA (HTML/CSS/JS + SW + manifest) |
| 153 | +scripts/ |
| 154 | +├── bundle.mjs # esbuild → build/localtv-remote.cjs |
| 155 | +├── build-sea.mjs # Node SEA blob + rcedit + PE subsystem flip |
| 156 | +├── assemble-payload.mjs # release/app/ with koffi stripped to win32_x64 |
| 157 | +└── build-installer.mjs # Inno Setup compile → release/LocalTVRemote-Setup-*.exe |
| 158 | +installer/ |
| 159 | +└── localtv-remote.iss # Inno Setup script |
| 160 | +``` |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +## Cross-platform |
| 165 | + |
| 166 | +Windows is the only supported target today. The input layer is abstracted behind `SystemInputBackend` (`src/core/main/input/backends/input-backend.ts`). Adding macOS/Linux means implementing that interface — contributions welcome. |
| 167 | + |
| 168 | +--- |
| 169 | + |
| 170 | +## Contributing |
| 171 | + |
| 172 | +1. Fork and clone |
| 173 | +2. `npm install && npm run dev` |
| 174 | +3. Make changes, run `npm run typecheck` to verify |
| 175 | +4. Open a PR — CI will typecheck on push |
| 176 | + |
| 177 | +--- |
70 | 178 |
|
71 | 179 | ## License |
72 | 180 |
|
73 | | -MIT. Contains no Widevine/CastLabs/DRM code. |
| 181 | +Apache 2.0 — see [LICENSE](LICENSE). |
| 182 | +Copyright 2026 Mudit Pandey. Contains no Widevine/CastLabs/DRM code. |
| 183 | + |
| 184 | +<div align="center"> |
| 185 | + |
| 186 | +<img src="https://capsule-render.vercel.app/api?type=waving&color=0:a855f7,100:6366f1&height=100§ion=footer" width="100%" /> |
| 187 | + |
| 188 | +</div> |
0 commit comments