Skip to content

fizzi01/Perpetua

Repository files navigation

Perpetua Logo

Perpetua

License: GPL v3 GitHub release GitHub Downloads (all assets, latest release)

Perpetua is an open-source, cross-platform KVM software that lets you share a single keyboard and mouse across multiple devices. Inspired by Apple's Universal Control, it provides seamless cursor movement between devices, keyboard sharing, and automatic clipboard synchronization. All secured with TLS encryption.

Built with Python using uvloop (macOS/Linux) and winloop (Windows) as event loops and compiled with Nuitka, for low-latency and responsive input handling. This results in very high performance with just ~6% CPU usage under heavy load.

Perpetua Server View

Table of Contents

Getting Started

GitHub Downloads (all assets, latest release)

  • macOS: Extract the .zip, run xattr -c Perpetua.app and launch Perpetua.app.
  • Windows: Extract the archive and run Perpetua.exe inside the Perpetua folder.
  • Linux: Install the .deb package.

The GUI will guide you through choosing server or client mode and the initial configuration.

Note

macOS: Perpetua requires Accessibility permissions and Local Network access (Privacy & Security). At first launch, macOS will show prompts to grant these permissions. You can also manage permissions manually in System Settings > Privacy & Security.

First-Time Setup

Install Perpetua on both machines, then run through these steps once. After this, every reconnection is automatic.

Server is the machine with the physical keyboard and mouse you want to share. Client is the machine you want to control with them.

  1. On the machine that owns the keyboard and mouse, open Perpetua and pick Server. Press the power button to start it.
  2. On the other machine, open Perpetua and pick Client. Press the power button. The client auto-discovers the server on the local network.
  3. On the Server, an OTP card appears under the power button when the client asks to pair. Share the 6-digit code with the user of the client.
  4. On the Client, enter the OTP when prompted.
  5. The Server shows an Allow/Deny card with a screen position selector (top, bottom, left, right). Pick a position and press Allow.
  6. Done!

The OTP is shown only on the server's screen and entered manually on the client; it never travels over the network.

Tip

You can pre-register clients in Server > Clients instead. Pre-registered clients skip the Allow/Deny prompt and connect directly.

Note

If anything goes wrong and the cursor gets stuck on the wrong machine, press Ctrl + Shift + Q on the server to force-quit Perpetua. See Keyboard Shortcuts for the full list.

Platform Support

Server (controls other machines)

Feature macOS Windows X11 Wayland (GNOME) Wayland (KDE) Wayland (Others)
Mouse capture ✔️ ✔️ ✔️ ✔️ ✔️
Keyboard capture ✔️ ✔️ ✔️ ✔️ ✔️ ✔️
Clipboard sync ✔️ ✔️ ✔️ ✔️ ✔️ ✔️

Client (controlled by a server)

Feature macOS Windows X11 Wayland (GNOME) Wayland (KDE) Wayland (Others)
Mouse control ✔️ ✔️ ✔️ ✔️ ✔️
Keyboard control ✔️ ✔️ ✔️ ✔️ ✔️ ✔️
Clipboard sync ✔️ ✔️ ✔️ ✔️ ✔️ ✔️

Supported desktop environments: GNOME >= 45 or KDE Plasma >= 6.1.

Other Wayland compositors (wlroots-based, Hyprland, Sway, etc.) are not yet supported.

Note

Linux (Wayland): requires libei and liboeffis installed on the system.

Known Issues

Important

  • Windows: You can't control a Windows client if there is no real mouse connected to the machine.

  • Input Capture Conflicts: Perpetua cannot control the mouse when other applications have exclusive input capture (e.g., video games). This is an architectural limitation.

Usage

Background Mode

You can run Perpetua as a background service using the daemon mode:

# Run in daemon mode
Perpetua --daemon

# Automatically start as server
Perpetua --daemon --server

# Automatically start as client
Perpetua --daemon --client

For a full list of available commands and options:

Perpetua --help

Keyboard Shortcuts

The following hotkeys are available on the server machine to control input focus without moving the mouse to a screen edge.

Shortcut Action
Ctrl + Shift + P + ← Switch focus to the left client
Ctrl + Shift + P + → Switch focus to the right client
Ctrl + Shift + P + ↑ Switch focus to the top client
Ctrl + Shift + P + ↓ Switch focus to the bottom client
Ctrl + Shift + P + Esc Return focus to the server
Ctrl + Shift + Q Panic - force-quit Perpetua

Note

Client switch hotkeys require the server to be running and at least one client to be connected.

Configuration

Perpetua uses JSON to define client and server settings. The configuration file is automatically generated on first launch with sensible defaults, requiring minimal manual intervention for most use cases.

Server Configuration

The server configuration is managed automatically for basic setup (certificate generation, network binding). To accept client connections, you can:

  • Let the GUI handle it: when a new client tries to connect, the Server shows an Allow/Deny card with a screen position selector. Picking Allow adds the client to the allowlist with the chosen position.
  • Or pre-register each client manually in Server > Clients section (or in the config file) before they connect. Specify:
    • Client IPs and/or Hostname
    • Screen Position: The spatial arrangement relative to the server (left, right, top, bottom)

This configuration defines how devices are arranged in your workspace for a seamless cursor transition between them.

Client Configuration

Clients can find servers in two ways:

Auto Discovery (Default):

  • Scans the local network for available servers
  • Works out of the box, no configuration needed

Manual Configuration:

  • Set the server's hostname or IP address directly in the config file (or in the appropriate field in Client > Options)
  • Use this when auto-discovery doesn't work or you have a static network setup
First Connection and OTP Pairing

When a client connects to a new server for the first time, it needs to get the server's TLS certificate to establish a secure connection. Here's how it works:

  1. The Client starts the connection process and signals the Server it wants to pair ("Secure connection" must be enabled, it is by default).
  2. The Server generates an OTP automatically and shows it on the GUI under the power button.
  3. Share the OTP with the user of the Client and enter it when prompted.
  4. The Server shows an Allow/Deny card with a screen position selector. Picking Allow adds the client to the allowlist; picking Deny rejects the handshake.
  5. Done!

You can also generate the OTP manually from the Security section on the Server (the same card appears under the power button). This is useful when pre-registering clients without waiting for an incoming request.

The OTP is just for the initial certificate exchange. After that, connections to the same server authenticate automatically using the saved certificates. The OTP itself never travels over the network - it is shown only on the server's screen.

Configuration File Structure

The configuration file lives at:

  • macOS: $HOME/Library/Caches/Perpetua
  • Windows: %LOCALAPPDATA%\Perpetua
  • Linux: $HOME/.perpetua

The configuration json file is split into three sections: server, client, and general.

Server Section

streams_enabled controls what the server will manage on each connected client:

  • 1: Mouse
  • 4: Keyboard
  • 12: Clipboard

log_level sets the logging verbosity:

  • 0: Debug (detailed logs)
  • 1: Info (standard logs)

pairing_port is the port used for the initial OTP-based certificate exchange. Leave it null to derive it as port - 2. The value is advertised over mDNS so clients discover it automatically.

authorized_clients lists the clients that can connect. To add a new client, you only need to specify:

  • uid: Unique identifier
  • host_name and/or ip_addresses: Client's network identity
  • screen_position: Where the client is positioned relative to the server (left, right, top, bottom)

Other fields are automatically populated by the system. Clients can also be added on the fly from the GUI via the Allow/Deny prompt.

Client Section

The same field names have the same meaning as in the server section. The server_info block tells the client which server to connect to (leave it empty for auto-discovery or fill in the host field for manual configuration).

General Section

These parameters affect the application's internal behavior. Only modify them if you know what you're doing.

File Structure

{
    "server": {
        "uid": "...",
        "host": "0.0.0.0",
        "port": 55655,
        "pairing_port": null,
        "heartbeat_interval": 1,
        "streams_enabled": {
            "1": true,
            "4": true,
            "12": true
        },
        "ssl_enabled": true,
        "log_level": 1,
        "authorized_clients": [
            {
                "uid": "...",
                "host_name": "MYCLIENT",
                "ip_addresses": [
                    "192.168.1.66"
                ],
                "first_connection_date": "2026-02-02 19:09:00",
                "last_connection_date": "2026-02-02 19:16:12",
                "screen_position": "top",
                "screen_resolution": "1920x1080",
                "ssl": true,
                "is_connected": true,
                "additional_params": {}
            }
        ]
    },
    "client": {
        "server_info": {
            "uid": "",
            "host": "",
            "hostname": null,
            "port": 55655,
            "heartbeat_interval": 1,
            "auto_reconnect": true,
            "ssl": true,
            "additional_params": {}
        },
        "uid": "...",
        "client_hostname": "MYSERVER",
        "streams_enabled": {
            "1": true,
            "4": true,
            "12": true
        },
        "ssl_enabled": true,
        "log_level": 1
    },
    "general": {
        "default_host": "0.0.0.0",
        "default_port": 55655,
        "default_daemon_port": 55652
    }
}

Troubleshooting

The client doesn't show up on the server

Auto-discovery uses mDNS. Make sure:

  • The Server is running and Secure connection is enabled.
  • Both machines are on the same LAN/subnet.
  • UDP 5353 is not blocked by a firewall (see Firewall ports).

As a fallback, set the server's hostname or IP directly in the Client > Options section.

OTP card never appears on the server

The OTP only appears when the Client actively asks to pair. Check that:

  • Secure connection is enabled on the Server (it is by default).
  • The pairing port on the server (default port - 2) is reachable from the client (see Firewall ports).
  • The client is actually trying to connect - its power button is green and it has discovered the server.

You can also generate the OTP manually from the Security section on the Server.

Cursor doesn't cross to the other screen

Check the client's screen position in Server > Clients. The position (top, bottom, left, right) defines which edge of the server screen the cursor uses to enter the client. If the client is offline, the cursor stays on the server side.

If the cursor gets stuck on the wrong machine, press Ctrl + Shift + Q on the server to force-quit Perpetua.

"Port already in use" on server start

The configured TCP port (default 55655) is occupied by another process. Open Server > Options and pick a different value, then retry.

Pairing port collision

The pairing listener (default port - 2) auto-falls-back to the next free adjacent port when busy. The actual port is advertised over mDNS so clients pick it up automatically.

To pin a specific port, set pairing_port in the server config.

GUI can't reach the daemon

The daemon writes its endpoint to <config-dir>/runtime/daemon.endpoint, and the GUI reads it on startup. You can override it with the PERPETUA_DAEMON_ENDPOINT environment variable:

# Linux / macOS
PERPETUA_DAEMON_ENDPOINT=unix:///tmp/my-perpetua.sock Perpetua

# Windows
PERPETUA_DAEMON_ENDPOINT=tcp://127.0.0.1:55700 Perpetua

Stale endpoint files left behind by a crash are harmless. The GUI falls back to the platform default and the daemon rewrites the file on next start.

Firewall ports

For a LAN-only setup only the Server needs inbound rules:

  • TCP 55655 (port): main TLS data channel.
  • TCP 55653 (pairing_port): plaintext OTP-based pairing.
  • UDP 5353: mDNS auto-discovery.

The Client only makes outbound connections.

Development / Building from Source

This section is for contributors and people building from source. End users can grab the prebuilt binaries from Releases.

Prerequisites
  • Python Environment:

    • Python 3.11 or 3.12
    • Poetry
  • GUI Framework:

    • Node.js
    • Rust
Platform-Specific Requirements
  • macOS:

    • Xcode Command Line Tools
      xcode-select --install
    • Dependencies needed to build uvloop
      brew install automake autoconf libtool ccache
  • Windows:

  • Linux:

        sudo apt-get update
        sudo apt-get install -y \
            libgtk-3-dev \
            automake \
            libtool \
            libwebkit2gtk-4.1-dev \
            build-essential \
            curl \
            wget \
            file \
            libxdo-dev \
            libssl-dev \
            libayatana-appindicator3-dev \
            librsvg2-dev \
            fakeroot

Note

Windows versions prior to Windows 10 (1803) require Microsoft Edge WebView2 Runtime to be installed manually.

Click to expand build instructions

Quick Start

The project includes both a build script and Makefile for convenient building.

  1. Clone the repository:

    git clone https://github.com/fizzi01/Perpetua.git
    cd Perpetua
  2. Install Python dependencies:

    poetry install
    # or
    pip install .
    # or
    make install-build
  3. Run the build:

    poetry run python build.py
    # or
    make build

Development Setup

In development mode the two components run independently - the Rust GUI launches via cargo tauri dev and the Python daemon is started manually in a separate terminal.

  1. Install Python dependencies:

    poetry install
  2. Start the daemon:

    python launcher.py
  3. Install GUI dependencies (optional, if you need to modify the GUI):

    cd src-gui
    npm install   # first time only
    cargo tauri dev

    The Tauri dev server supports hot-reload for the frontend. In debug builds the Rust binary does not spawn the daemon automatically.

Advanced Build Options
Using Poetry
# Debug build
poetry run python build.py --debug

# Skip GUI build (build daemon only)
poetry run python build.py --skip-gui

# Skip daemon build (build GUI only)
poetry run python build.py --skip-daemon

# Clean build artifacts before building
poetry run python build.py --clean
Using Make
# Debug build
make build-debug

# Build daemon only
make build-daemon

# Build GUI only
make build-gui

# Release build with clean
make build-release

# Clean build artifacts
make clean
Manual Build Steps

For manual builds or troubleshooting, follow these steps:

Build GUI:

cd src-gui
npm install
cargo tauri build

Build Daemon:

# From project root
poetry run python build.py --skip-gui

Roadmap

  • Linux support
  • File transfers
  • Advanced clipboard format support (including proprietary formats)

License

This project is licensed under the GNU General Public License v3.0.