Penput turns your mobile/tablet into a wireless touchpad for your PC. The app serves a minimal UI over HTTP, streams touch coordinates via WebSocket, and maps them to absolute mouse positions on the host machine.
- Low-latency absolute mouse movement from mobile touch
- Single client guard: only one active connection at a time
- Approval flow: CLI prompt to approve/reject new connections
- Fullscreen connect: user taps Connect → fullscreen entry
- Connection state feedback: Connected / Disconnected / Failed
- Automated release: commit messages containing
releasetrigger GitHub Actions to runcargo build --release, create a GitHub Release, and upload the binary
- Rust (tokio, axum, tokio-tungstenite, enigo, display-info, tracing)
- Frontend: static HTML/CSS/JS
- CI: GitHub Actions (
.github/workflows/release-build.yml)
- HTTP server (
--port, default 8080): serves HTML/JS/CSS. - WebSocket server (
--ws-port, default 9001):- receives
initJSON → captures client screen size - receives 4-byte big-endian binary (
x:u16 | y:u16) → maps to host absolute mouse position
- receives
- UDP server (iOS native) (
--udp-port, default 9002):- receives
HELLO/MOVE/PINGbinary packets from the iOS native client - enforces the same single-client + CLI approval flow as WebSocket
- receives
- Approval: new connections require CLI
y/n. - Mouse movement: computed and executed on a dedicated worker thread to keep WS handling lean.
rustup default stable
cargo build
# or
cargo install penputcargo run -- --port 8080 --ws-port 9001 --udp-port 9002--auto-approve: skip manual approval
This project now supports an iOS native UDP client (recommended when WebKit-based browsers stutter).
- UDP:
udp://<PC_IP>:9002by default
Client → Server:
HELLO(0x01):[0x01][w:u16][h:u16]MOVE(0x02):[0x02][x:u16][y:u16]PING(0x03):[0x03][t:u64](client timestamp in ms)
Server → Client:
ACCEPT(0x10):[0x10]REJECT(0x11):[0x11]BUSY(0x12):[0x12](another client is already connected)PONG(0x13):[0x13][t:u64](echoed timestamp)
Notes:
- The server keeps only one active UDP session at a time.
- If the server doesn't receive traffic for ~5 seconds, the session is released.
The repository contains an iOS SwiftUI skeleton under:
ios/PenputIOSClient/
It uses:
Network.framework(NWConnectionover UDP)- a
UIViewRepresentabletouch surface for low-overhead touch capture CADisplayLinkto pace sends (latest-value only)
iOS requires a local network usage prompt for LAN UDP/TCP traffic.
Add NSLocalNetworkUsageDescription to Info.plist in your Xcode project, e.g.:
- "Penput needs local network access to send touchpad data to your PC."
iOS apps must be signed. Without the paid Apple Developer Program you can still test, but usually the app expires every ~7 days.
Practical approach while you don't have a Mac:
- Build the iOS app using a remote macOS environment (later: your own MacBook).
- Install on iPad using a Windows sideload tool (e.g. Sideloadly/AltStore).
- Re-sign / refresh weekly.
- Start the server and note the URL (e.g.,
http://192.168.0.10:8080). - On mobile (same LAN), open
http://<PC_IP>:8080/?ws=9001. - Tap Connect → fullscreen → approve in PC CLI → state turns Connected.
- Move the mouse by touching the pad. Use Exit (✕) to leave fullscreen and disconnect.
- Init (JSON):
{"type":"init","width":<u16>,"height":<u16>} - Move (Binary): 4 bytes, big-endian,
x:u16,y:u16(client viewport absolute coords)
- Shows
[HH:MM:SS] 📱 Connection request from <IP> y/yes→ approve, anything else/EOF → reject
- WS loop: parse coords and enqueue to channel (minimal locking)
- Mouse moves: dedicated worker thread calls enigo; avoids blocking WS handler
- Frontend:
requestAnimationFrame-bounded sends; normalized coords packed tou16
Workflow: .github/workflows/release-build.yml
- Trigger: push with commit message containing
release - Steps: checkout → install Rust → cache →
cargo build --release→ create GitHub Release (tagrelease-<sha>) → uploadtarget/release/penput - Secrets: uses default
GITHUB_TOKEN
- Mobile cannot reach: ensure same LAN; open 8080/9001 in firewall.
- Connection failed: check
?ws=<port>matches actual WS port. - No approval prompt: check server terminal, focus the CLI window.
- Choppy movement: verify network quality. Worker thread + RAF already minimize jitter.
- Single-client slot: new connections are rejected while one is active.
- Coordinates are absolute only (no gestures/relative moves).
- Current build target assumes Windows host (enigo on Windows).