- Rust 49.6%
- JavaScript 36.8%
- Nix 10.7%
- HTML 1.5%
- CSS 1.4%
| .forgejo/workflows | ||
| proto | ||
| react-app | ||
| src | ||
| .gitignore | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CLAUDE.md | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
| rust-toolchain.toml | ||
| shell.nix | ||
Remote Native Touchpad
Turn your phone into a real laptop touchpad + keyboard for your Linux desktop. The server creates a virtual multitouch touchpad and keyboard via /dev/uinput — libinput handles all gesture recognition (tap-to-click, two-finger scroll, pinch-to-zoom, compositor swipes) exactly as if it were a physical touchpad.
No gesture logic in the frontend, no sensitivity settings — it all comes from your compositor's libinput config.
Features
- Real multitouch touchpad — MT protocol B with slot tracking, libinput does the rest
- Keyboard with auto-shift — JS key events mapped to evdev, caps-aware
- Zero-config pairing — scan the QR code with your phone camera, the PWA opens and authenticates automatically via a one-time URL fragment (256-bit HMAC-SHA256 device key)
- Embedded PWA — the React frontend is compiled into the binary, nothing to serve separately
- Encrypted auth — each WebSocket connection requires HMAC challenge-response
- Resilient — auto-increments port if busy, reconnects on phone-side disconnect, releases all touches/modifiers on disconnect
- Single binary — no runtime dependencies beyond a libinput-based compositor (GNOME, KDE, Sway, Hyprland, etc.)
NixOS (easiest)
Add the flake input and import the module:
# flake.nix
{
inputs.remote-touchpad.url = "git+https://forge.vidov.space/vid/remote-native-touchpad";
outputs = { self, nixpkgs, remote-touchpad, ... }: {
nixosConfigurations.my-box = nixpkgs.lib.nixosSystem {
modules = [
remote-touchpad.nixosModules.default
({ pkgs, ... }: {
services.remote-native-touchpad = {
enable = true;
openFirewall = true; // opens port 8080
# host = "0.0.0.0"; // default
# port = 8080; // default, auto-increments if busy
};
})
];
};
};
}
The module handles everything — hardware.uinput.enable, user/group creation, systemd service with auto-restart, and optional firewall rule. No manual udev rules or group membership needed.
nixos-rebuild switch # then scan the QR printed in journal
remote-native-touchpad --pair
# OR
journalctl -fu remote-native-touchpad
Home Manager (non-NixOS or user service)
{
imports = [ inputs.remote-touchpad.homeManagerModules.default ];
services.remote-native-touchpad.enable = true;
}
Other Linux
# One-time: allow your user to write to /dev/uinput
sudo tee /etc/udev/rules.d/99-uinput.rules > /dev/null << 'EOF'
KERNEL=="uinput", MODE="0660", GROUP="input", TAG+="uaccess"
EOF
sudo modprobe uinput && sudo udevadm control --reload-rules && sudo udevadm trigger /dev/uinput
sudo usermod -aG input $USER
# re-login after this
# Build and run
cargo build --release
./target/release/remote-native-touchpad
Or with the Nix flake directly (no install, will not set up user and service):
nix run --refresh 'git+https://forge.vidov.space/vid/wlr-remote-mouse'
Pairing
The pairing URL is printed as a QR code in the server logs on startup (or run remote-native-touchpad --pair to re-display it).
- Scan the QR code with your phone's camera
- The PWA opens in your phone browser
- The 256-bit device key is embedded in the URL fragment (
#k=<base64url-key>) — it never touches a network, travels only optically via your camera - The PWA stores the key and connects to the WebSocket with HMAC-SHA256 challenge-response
- Done — touch and type
To revoke all paired devices, delete the key file (/var/lib/remote-native-touchpad/device-key on NixOS, ~/.config/remote-native-touchpad/device-key otherwise) and restart the server. A new key is generated and a new QR is printed.
Usage
remote-native-touchpad # Run the server
remote-native-touchpad --pair # Print the pairing QR and exit
remote-native-touchpad --port 9090 # Custom port
The server prints the local IP and pairing URL on startup. Access the PWA manually at http://<ip>:<port> if you can't scan the QR.
Architecture
Phone touches + keys → protobuf WebSocket → axum server (HMAC auth, decode)
→ /dev/uinput virtual touchpad (evdev MT) + virtual keyboard → libinput → compositor
No gesture code runs on the phone or in the server — libinput on the host sees standard evdev multitouch events and handles everything.
Source layout
src/touchpad.rs— virtual multitouch clickpad, evdev MT slot management, physical buttonsrc/keyboard.rs— virtual keyboard, JS key → evdev scancode mapping, auto-Shiftsrc/auth.rs— 256-bit device key, QR pairing URL, HMAC challenge-responsesrc/server.rs— Axum HTTP/WS server, embedded frontend, auto portreact-app/— React PWA, streams raw touches, zero gesture logicproto/remote_input.proto— shared protobuf wire format (prost + protobufjs)
Requirements
- Linux with
uinputkernel module, libinput-based compositor (GNOME, KDE, Sway, Hyprland, etc.) /dev/uinputwrite access (the NixOS module handles this; on other distros see udev rule above)- Rust 1.75+ and Node.js 24 (for building from source)