No description
  • Rust 49.6%
  • JavaScript 36.8%
  • Nix 10.7%
  • HTML 1.5%
  • CSS 1.4%
Find a file
Vid Plavšić 9afaaf4198
All checks were successful
Build / build (push) Successful in 8m29s
Release / release (push) Successful in 2m58s
Switch dot colors back to green
2026-08-19 12:54:49 +02:00
.forgejo/workflows Workflow updates 2026-06-18 15:02:27 +02:00
proto Yuge update, creates native touchpad now 2026-06-17 22:12:13 +02:00
react-app Switch dot colors back to green 2026-08-19 12:54:49 +02:00
src Migrate to vite-rs over rust-embed for better dev experience 2026-06-25 00:19:35 +02:00
.gitignore gitignore 2025-12-22 22:26:43 +01:00
build.rs Migrate to vite-rs over rust-embed for better dev experience 2026-06-25 00:19:35 +02:00
Cargo.lock Migrate to vite-rs over rust-embed for better dev experience 2026-06-25 00:19:35 +02:00
Cargo.toml Migrate to vite-rs over rust-embed for better dev experience 2026-06-25 00:19:35 +02:00
CLAUDE.md Yuge update, creates native touchpad now 2026-06-17 22:12:13 +02:00
flake.lock Cleanup flake 2026-06-25 00:37:35 +02:00
flake.nix Cleanup flake 2026-06-25 00:37:35 +02:00
README.md Update README.md 2026-06-27 13:06:04 +00:00
rust-toolchain.toml PPPPPPin rust to 1.96.0 2026-06-18 11:54:36 +02:00
shell.nix Cleanup flake 2026-06-25 00:37:35 +02:00

Remote Native Touchpad

Remote Native Touchpad

Build Status Release

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).

  1. Scan the QR code with your phone's camera
  2. The PWA opens in your phone browser
  3. 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
  4. The PWA stores the key and connects to the WebSocket with HMAC-SHA256 challenge-response
  5. 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 button
  • src/keyboard.rs — virtual keyboard, JS key → evdev scancode mapping, auto-Shift
  • src/auth.rs — 256-bit device key, QR pairing URL, HMAC challenge-response
  • src/server.rs — Axum HTTP/WS server, embedded frontend, auto port
  • react-app/ — React PWA, streams raw touches, zero gesture logic
  • proto/remote_input.proto — shared protobuf wire format (prost + protobufjs)

Requirements

  • Linux with uinput kernel module, libinput-based compositor (GNOME, KDE, Sway, Hyprland, etc.)
  • /dev/uinput write access (the NixOS module handles this; on other distros see udev rule above)
  • Rust 1.75+ and Node.js 24 (for building from source)