Turn your phone into a trackpad for your Mac. Free, open‑source, zero‑setup. Stays on your Wi‑Fi, never leaves the room.
You're across the room. The Mac is plugged into the TV. The keyboard is buried under cables. Pick up your phone instead.
| Feature | What you get | |
|---|---|---|
| 🖱 | Trackpad mode | Smooth, sub‑frame pointer with two‑finger scroll & tap‑to‑click. |
| ⌨️ | Keyboard relay | Type from your phone. Modifier keys, arrows, the works. |
| 🔒 | LAN‑only by default | No accounts, no cloud, no telemetry. Pairs over the local network. |
| 📡 | Auto‑discovery | Bonjour / mDNS finds your Mac the moment the app opens. |
| 🌓 | Native everywhere | React Native macOS on desktop, Expo on mobile. One repo. |
The mobile app advertises itself over Bonjour / mDNS. The desktop daemon listens on the LAN, the phone connects, and from then on every gesture is a tiny WebSocket frame on your local network. No relay servers. No internet required. If your router goes down, Entangle keeps working.
The wire format is a single shared TypeScript package — @entangle/protocol — imported by both clients, so the desktop and the phone can never disagree about what a message looks like.
| Platform | Download |
|---|---|
| macOS (Apple Silicon) | Entangle.dmg |
| iOS | App Store |
| Android | Google Play · entangle.apk |
# 1. Clone
git clone https://github.com/gabrieldonadel/entangle.git
cd entangle
# 2. Install dependencies
pnpm install
# 3. CocoaPods for the desktop app (first clone only)
cd apps/desktop/macos && bundle install && bundle exec pod install && cd -
# 4. Run the desktop app
pnpm desktop:start # in one terminal — Metro on port 8090
pnpm desktop:macos # in another — build & launch the macOS app
# 5. Run the mobile app
pnpm mobile:start # Expo dev server
pnpm mobile:ios # iOS simulator / device
pnpm mobile:android # Android emulator / deviceThat's it. The phone will find the Mac on its own — pick it from the discovered list and start moving the pointer. macOS will ask for Accessibility permission the first time; the desktop UI is gated until you grant it.
| Node.js | ≥ 18 |
| pnpm | 10.x |
| Xcode | 15+ (for iOS & macOS builds) |
| CocoaPods | bundle install && bundle exec pod install inside apps/desktop/macos |
| Accessibility | macOS Accessibility permission — required to synthesize pointer / keyboard input |
entangle-monorepo/
├─ apps/
│ ├─ desktop/ ← React Native macOS 0.81 + Expo 55 (the macOS server)
│ ├─ mobile/ ← Expo 55 + Expo Router + RN 0.83 (iOS / Android client)
│ └─ website/ ← Vite + React 18 (marketing site)
├─ packages/
│ └─ shared/ ← @entangle/protocol — shared wire format (TypeScript)
├─ pnpm-workspace.yaml
└─ pnpm-lock.yaml
| App | Path | Platforms | Stack |
|---|---|---|---|
| Desktop | apps/desktop |
macOS | React Native macOS 0.81 + Expo 55, React 19.1 |
| Mobile | apps/mobile |
iOS / Android | Expo 55 + Expo Router + RN 0.83, React 19.2 |
| Website | apps/website |
Web | Vite + React 18 |
One workspace, one lockfile. Apps and shared packages all live in the same pnpm workspace. The root
.npmrcsetsnode-linker=hoistedso each app gets a flatnode_modulestree — conflicting versions (e.g. desktop'sreact-native@0.83.6vs mobile'sreact-native@0.83.4) get nested under the consuming app, while shared deps hoist to the root. Patches for the macOS port of Expo are pinned to specific versions inpnpm-workspace.yamlso they only touch the version desktop resolves to.
pnpm lint # eslint across every workspace
pnpm desktop test # jest (desktop unit tests)
pnpm desktop test -- path/to/file.test # single test file
pnpm desktop <cmd> # forward any command into apps/desktop
pnpm mobile <cmd> # forward any command into apps/mobilepackages/shared is the source of truth for messages on the wire — touch it once, both clients update. All wire messages carry v: 1; bump PROTOCOL_VERSION for breaking changes and the server will close mismatched clients with code 4001.
- Trackpad + scroll + tap‑to‑click
- Keyboard relay
- mDNS auto‑discovery
- Dock enumeration & app activation
- Media keys & system shortcuts
- Windows desktop client
- Apple Watch quick‑actions
See open issues for the live picture.
PRs welcome — small fixes don't need an issue first. For anything bigger, open an issue and let's chat.
- Fork & branch (
feat/your-thing) pnpm installat the root — that's it; apps share a single workspace- Commit with Conventional Commits
- Open a PR against
main
MIT © Gabriel Donadel. See LICENSE for details.
Enjoying Entangle? Drop a ⭐.
Made with React Native macOS + Expo · No trackers · No analytics · No nonsense