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.
- macOS: Extract the
.zip, runxattr -c Perpetua.appand launchPerpetua.app. - Windows: Extract the archive and run
Perpetua.exeinside thePerpetuafolder. - Linux: Install the .deb package.
The GUI will guide you through choosing server or client mode and the initial configuration.
Tip
For detailed configuration options, including client-server pairing and security settings, see the Configuration section below.
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.
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 --clientFor a full list of available commands and options:
Perpetua --helpThe 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.
| Feature | macOS | Windows | X11 | Wayland (GNOME) | Wayland (KDE) | Wayland (Others) |
|---|---|---|---|---|---|---|
| Mouse capture | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
| Keyboard capture | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Clipboard sync | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| 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.
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.
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.
Configuration File Locations:
- macOS:
$HOME/Library/Caches/Perpetua - Windows:
%LOCALAPPDATA%\Perpetua - Linux:
$HOME/.perpetua
Server Configuration
The server configuration is managed automatically for basic setup (certificate generation, network binding). However, to accept client connections, you must manually add each client to the server configuration (or in Server > Clients section), specifying:
- 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:
- The client starts the connection process
- On the
Server(which must be running and listening), generate an OTP through the GUI in theSecuritysection ("Secure connection" must be enabled!) - Enter the OTP on the
Clientwhen prompted (the GUI walks you through this) - If the certificate exchange succeeds and the client is in the server's allowlist, the connection is established
- Done!
The OTP is just for the initial certificate exchange. After that, connections to the same server authenticate automatically using the saved certificates.
Configuration File Structure
The configuration json file is split into three sections: server, client, and general.
streams_enabled controls what the server will manage on each connected client:
1: Mouse4: Keyboard12: Clipboard
log_level sets the logging verbosity:
0: Debug (detailed logs)1: Info (standard logs)
authorized_clients lists the clients that can connect. To add a new client, you only need to specify:
uid: Unique identifierhost_nameand/orip_addresses: Client's network identityscreen_position: Where the client is positioned relative to the server (left,right,top,bottom)
Other fields are automatically populated by the system.
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).
These parameters affect the application's internal behavior. Only modify them if you know what you're doing.
{
"server": {
"uid": "...",
"host": "0.0.0.0",
"port": 55655,
"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
}
}Note
When multiple Perpetua servers are detected on the network (in auto-discovery mode), the GUI will present a selection dialog allowing the user to choose the desired server.
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
uvloopbrew install automake autoconf libtool ccache
- Xcode Command Line Tools
-
Windows:
- Microsoft C++ Build Tools: Install the "Desktop development with C++" workload from Visual Studio Build Tools
-
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.
The project includes both a build script and Makefile for convenient building.
-
Clone the repository:
git clone https://github.com/fizzi01/Perpetua.git cd Perpetua -
Install Python dependencies:
poetry install # or pip install . # or make install-build
-
Run the build:
poetry run python build.py # or make build
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.
-
Install Python dependencies:
poetry install
-
Start the daemon:
python launcher.py
-
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.
Note
Make sure all prerequisites and dependencies are installed before running the daemon or the GUI.
Advanced Build Options
# 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# 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 cleanManual Build Steps
For manual builds or troubleshooting, follow these steps:
Build GUI:
cd src-gui
npm install
cargo tauri buildBuild Daemon:
# From project root
poetry run python build.py --skip-gui- Linux support
- File transfers
- Advanced clipboard format support (including proprietary formats)
This project is licensed under the GNU General Public License v3.0.