Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Glide

Your phone is the trackpad for your Mac.

Open a web page on your phone, run a small agent on your Mac, and drive the cursor. The two talk directly, peer to peer, so your movements never travel through a server.


license webrtc next swift platform


Glide in use: phone driving the Mac cursor with a live phosphor trace

The phone is a bare input surface. The green trail is the pad drawing your finger path in real time.


How it works

The web page is only the input surface. It is hosted on Vercel, but nothing on Vercel can move a cursor, so a small local agent does the actual input injection. The phone and the agent negotiate a direct WebRTC data channel and then talk straight to each other. Vercel carries only the few hundred bytes of handshake, never the pointer stream.

flowchart LR
  subgraph Cloud["Vercel (public)"]
    W["Static PWA"]
    S["POST /api/signal<br/>SDP exchange only"]
  end
  subgraph PhoneBox["Phone browser"]
    P["Trackpad PWA"]
  end
  subgraph MacBox["Your Mac"]
    A["Node agent"]
    I["Swift injector<br/>(CGEvent)"]
    C["Cursor and keyboard"]
  end
  P -.->|loads over HTTPS| W
  P -->|SDP only| S
  A -->|SDP only| S
  P ==>|"direct WebRTC DataChannel"| A
  A --> I --> C
Loading

Why WebRTC, not a plain WebSocket

The obvious design is to serve the page and open a ws://192.168.x.x socket to the Mac. It does not work: a page served over HTTPS is hard blocked from opening an insecure socket to a LAN IP (mixed content), and only localhost counts as a secure origin, not arbitrary LAN addresses.

WebRTC solves both at once. Its DTLS transport counts as a secure context, so HTTPS is satisfied, and on a LAN the peers resolve to host candidates and connect directly, typically 2 to 5 ms round trip. A cloud relay would have added 40 to 100 ms, which for a cursor is the entire product.

Two data channels carry the traffic:

  • ctrl, reliable and ordered, for auth, clicks, keystrokes, and telemetry.
  • input, unreliable and unordered, for movement and scroll. A dropped move packet is superseded a frame later, and retransmitting it would stall every packet behind it. That head of line blocking is the usual reason these apps feel laggy.

Quickstart

Requires macOS with the Xcode command line tools (for swiftc) and Node 20+.

git clone https://github.com/karimbabasf/glide.git
cd glide/agent
npm install
npm start

npm start compiles the Swift injector, starts the agent, and prints a QR code. Scan it with your phone. The pairing link carries a 128 bit secret and expires after three minutes; the agent issues a fresh one automatically.

Phone and Mac should be on the same network. Most home and office wifi connects directly with no setup; networks that block device to device traffic need a TURN relay (see Networks).

One time: Accessibility permission

macOS blocks synthetic input until the process posting it is trusted. Without it, the events are dropped silently: the agent pairs and reports no error while the cursor does not move.

Grant it under System Settings > Privacy & Security > Accessibility, to the terminal you run npm start from (Terminal, iTerm, VS Code, and so on), then restart the agent. macOS usually prompts on first run.

Gestures

Modeled on the Apple trackpad, tracked per touch so a two-finger scroll is never confused with a one-finger-held drag.

Input Action
One finger drag Move pointer
Tap Left click
Double tap Double click
Two finger tap Right click
Two finger drag Scroll
Three finger swipe up Mission Control
Three finger swipe down App Expose
Three finger swipe left or right Switch spaces
Hold, or tap then hold, then drag Drag lock
Two fingers, one anchored one moving Click and drag
Kbd button Keyboard passthrough

The pointer uses a nonlinear acceleration curve: near 1 to 1 when your finger moves slowly for precision, saturating on a flick so you can cross the screen in one swipe. Sens scales it, Natural flips scroll direction. Three-finger swipes fire the default macOS keyboard shortcuts, so they follow whatever those are set to.

Pairing and security

sequenceDiagram
  participant Ph as Phone
  participant Sig as Vercel /api/signal
  participant Ag as Mac agent
  Ag->>Ag: make room id + 128 bit secret
  Ag->>Sig: publish(offer, sha256(secret))
  Ag-->>Ph: QR code carries room + raw secret
  Ph->>Sig: fetch(room, sha256(secret))
  Sig-->>Ph: offer
  Ph->>Sig: answer(room, sha256(secret), SDP)
  Ag->>Sig: poll(room)
  Sig-->>Ag: answer
  Ph->>Ag: ICE connects, data channels open
  Ph->>Ag: auth(raw secret) over the channel
  Ag->>Ag: verify secret a second time
  Ag-->>Ph: ok
  Note over Ph,Ag: pointer packets now flow peer to peer
Loading

The signaling server only ever sees sha256(secret). The raw secret travels in the QR code and is checked again by the agent over the data channel. A fully compromised signaling server cannot drive your Mac: it never holds a value it could replay. Pairing records are one shot and expire after 180 seconds. The agent opens no listening port; it dials out.

Testing

Three harnesses, no physical phone required for the first two:

# Signaling protocol against production, including the rejection paths
node scripts/test-signal.mjs

# Is Accessibility actually granted? Moves the cursor 60px and back.
node scripts/test-injector.mjs

# Full loopback: plays the phone on the Mac through real signaling,
# proving handshake, auth, and data flow end to end.
cd agent && node test-e2e.mjs

Networks

Glide fetches its ICE config at runtime from /api/ice, so connectivity scales with how the network is set up:

  • Any network that allows device to device traffic (most home and office wifi): the phone and Mac connect directly over host candidates, 2 to 5 ms, no relay, nothing to configure.
  • Client-isolated networks (some cafe, co-working, and guest wifi block peer to peer): a direct link is impossible, so you need a TURN relay. Configure one (below) and it works there too, at a latency cost since the stream is relayed. The phone rail shows DIRECT or RELAY so you always know which path you are on.

Enabling TURN

Set either of these on the Vercel project, then redeploy. No code change; the phone and agent both read it from /api/ice.

Cloudflare (free, recommended). Create a TURN key in the Cloudflare dashboard, then set:

CF_TURN_TOKEN_ID=...
CF_TURN_API_TOKEN=...

Or any static-credential TURN (self-hosted coturn, Twilio):

TURN_URLS=turn:your.host:3478,turns:your.host:5349
TURN_USERNAME=...
TURN_CREDENTIAL=...

There is no working free zero-config TURN anymore (the old public relays have shut down), which is why this is opt-in rather than baked in.

Status

The transport, signaling, and input injection are each verified, including a loopback harness that completes the full pairing and pushes commands to the injector. Direct connections work on any network without client isolation; isolated networks need TURN, above. Bluetooth is not an option: iOS Safari has no Web Bluetooth API, and iOS blocks the HID peripheral role even for native apps, so a phone cannot present itself as a mouse over Bluetooth.

Roadmap

  • Optional Upstash Redis for the signaling store, so the handshake never depends on a warm serverless instance.
  • Native iOS client if the web input surface hits a feel ceiling.

Layout

agent/
  index.js        WebRTC peer, pairing, routes input to the injector
  injector.swift  CGEvent injection, reads newline delimited JSON on stdin
  test-e2e.mjs    loopback pairing probe
web/
  app/page.tsx            trackpad surface, per-touch gesture engine, telemetry
  app/api/signal/route.ts SDP exchange
  lib/store.ts            memory or Upstash backend
scripts/
  test-signal.mjs   signaling protocol test
  test-injector.mjs Accessibility permission check

Stack

Next.js and React for the PWA, deployed on Vercel. Node with node-datachannel for the agent side of WebRTC. A single Swift file using CoreGraphics for input injection, chosen over a Node native module because the maintained options were either unpublished or stale, and swiftc ships with the Xcode command line tools.

License

MIT. See LICENSE.

About

Turn your phone into a precision wireless trackpad and keyboard for your Mac, over a direct WebRTC data channel. Next.js PWA on Vercel + a local Node/Swift agent.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages