A lightweight screen-sharing tool that streams directly between peers over QUIC.
Unlike WebRTC-based solutions, Qup does not rely on STUN/TURN infrastructure or NAT traversal services. The host exposes a port and clients connect directly using the host's IPv4 address.
It started as an experimental project to explore P2P networking using decentralized networks and DHT protocols. The initial goal was to have 2 peers exchange a perpetual InfoHash (just like a phone number), and then have 2 daemons running in parallel that both announced and searched over the Bittorrent DHT for the other peer's Infohash. After finding each other's public IP address the 2 would have established a P2P connection via UDP hole punching but since most ISPs nowadays provide Symmetric NAT connections and other strict conditions like MAP-e, traversing was practically unreliable.
IPv6 would have been an alternative but adoption is still low, especially in Italy (around ~21%), and the unpredictability of relay servers or STUN/TURN overhead would have been against the initial goal.
So the solution was just to rely on a direct IPv4 connection with minimal manual configuration.
It ended up being an efficient way to share a screen/window.
Note
The code was generated entirely with Claude Opus 4.6 with heavy steering and human feedback. A human-written design file was provided to the agent with the main architecture and goals of the project.
Test environment:
- Host: 300mbps upload speed
- Client: 20mbps download speed
- Stream: 1920x1080p 60fps 3000bps
| CPU | Intel Core Ultra 7 258V |
| GPU | Intel ARC 140V |
| RAM | 32GB |
| OS | Fedora (Wayland) |
| Transport | QUIC (via UDP) |
| CPU (Host) | 2% – 5% |
| RAM (Host) | ~30MB |
| CPU (Viewer) | ~3% |
| RAM (Viewer) | ~120MB |
| Setup | Manual (Port Forwarding) |
| NAT Traversal | None (Direct IP) |
Tested locally up to 4k 60fps with no issues.
- QUIC protocol: Streams video/audio payloads via QUIC Datagrams (utilizing
quic-go). - Per-App Audio Isolation: Prompts users with active audio apps (queried via PipeWire/PulseAudio
pw-dump) and creates a virtual null-sink to isolate and stream only the selected application's audio stream. - X11 & Wayland Dual Support:
- X11: Direct screen capture via GStreamer (
ximagesrc). - Wayland: Native PipeWire capture using GStreamer (
pipewiresrc) by establishing a D-Bus ScreenCast portal handshake powered by wayland-portal-go.
- X11: Direct screen capture via GStreamer (
- Hardware Acceleration:
- NVIDIA NVENC (
nvh264enc). - Intel/AMD VA-API (
vah264enc). - Automatic fallback to (
x264enc).
- NVIDIA NVENC (
flowchart LR
subgraph Bob["Host (Bob)"]
subgraph Wayland["On wayland"]
APP[Qup]
DBUS[D-Bus]
PORTAL[xdg-desktop-portal<br/>wayland-portal-go]
PW[PipeWire]
end
X11[X11 Capture<br/>ximagesrc]
CAP[Screen Capture]
AUDIO[Audio Capture<br/>PipeWire / PulseAudio]
ENC[H.264 Encoder<br/>NVENC / VA-API / x264]
TS[MPEG-TS Packaging]
QUIC[QUIC Datagrams<br/>TLS 1.3]
end
subgraph Internet["Internet"]
ROUTER[IPv4 Router<br/>Port Forwarding]
end
subgraph Alice["Client (Alice)"]
RX[QUIC Receiver]
PLAYER[ffplay / VLC]
OUT[Video + Audio Playback]
end
APP --> DBUS
DBUS --> PORTAL
PORTAL --> PW
PW --> CAP
X11 --> CAP
CAP --> ENC
AUDIO --> TS
ENC --> TS
TS --> QUIC
QUIC --> ROUTER
ROUTER --> RX
RX --> PLAYER
PLAYER --> OUT
Important
Qup utilizes direct IPv4 peer-to-peer connections. The host (Bob) must have an open port forwarded on their router to accept incoming client connections. Data is encrypted using TLS 1.3, as specified by the QUIC standard.
Ensure the following dependencies are installed on your Linux system:
- OS: Linux
- Go (v1.22 or higher)
- GStreamer (required for capturing and encoding:
gst-launch-1.0,gst-plugins-base,gst-plugins-good,gst-plugins-bad) - PipeWire / PulseAudio (for application audio capturing)
- pactl & pw-link (for null sink creation and port mapping)
- OS Any
- Media Player:
- ffplay (included with standard FFmpeg installs), either:
- available in the system
PATH, or placed as an - executable in the same directory as the
qupexecutable
- available in the system
- ffplay (included with standard FFmpeg installs), either:
- VLC (fallback)
Clone the repository and build the unified qup binary:
go build -o qup main.goBob is the sender sharing his screen. Start the host application on a port of choice (e.g., 50001):
./qup share -port=50001Note: If running in a terminal, Qup will interactively prompt you to choose whether to share the default system audio or select a specific running application (like Firefox or Chrome) to capture audio from.
Alice is the receiver watching the stream. Connect by specifying Bob's target IP and port:
./qup connect [BOB_IP]:50001(Note: You can also use the legacy flag-style format like ./qup -share -port=50001 or ./qup -connect [BOB_IP]:50001 if preferred.)
- VA-API Aspect Ratio Stretching: When utilizing Intel/AMD hardware-accelerated encoding (vah264enc), sharing a window that has been dynamically resized may cause the output image to stretch abnormally to fit the target resolution. This behavior stems from an upstream driver limitation with specific VA-API implementations.
- Workaround: Maintain standard aspect ratio dimensions or fallback to software encoding (-codec=x264enc).
| Flag | Type | Default | Description |
|---|---|---|---|
-port |
int |
50001 |
UDP port to listen on for incoming client QUIC connections. |
-bitrate |
int |
8000 |
Target video bitrate in kbps (e.g., 8000 = 8 Mbps). |
-fps |
int |
60 |
Captured frame rate. A value of 60 offers buttery smooth streams. |
-size |
string |
"1920x1080" |
Capture resolution width x height. |
-codec |
string |
"auto" |
GStreamer H.264 encoder element (e.g. x264enc, vah264enc, nvh264enc). Auto-probes hardware acceleration by default. |
-g |
int |
30 |
GOP size. Small default is crucial for instant client startup sync. |
-preset |
string |
"ultrafast" |
x264 software encoder speed preset (e.g., ultrafast, veryfast, medium). |
-tune |
string |
"zerolatency" |
x264 software encoder latency tuning mode. |
-volume |
float |
150.0 |
Audio volume amplification factor. |
-audio-app |
string |
"" |
App name to capture audio from (bypasses interactive prompt). |
-display |
string |
$DISPLAY |
X11 display string (only applicable in X11 environments). |
-test |
bool |
false |
Synthetic mode using GStreamer's videotestsrc and audiotestsrc instead of capturing screen. |
-mock-portal |
bool |
false |
Simulates a mock ScreenCast portal in background (for testing). |
-headless |
bool |
false |
Bypasses physical capture and uses synthetic GStreamer test sources. |
-debug |
bool |
false |
Prints verbose shell command lines, encoders, and network output metrics. |
| Flag | Type | Default | Description |
|---|---|---|---|
-probesize |
int |
5000000 |
ffplay buffer size in bytes to probe streams. |
-analyze-duration |
int |
2000000 |
Duration (in microseconds) to analyze stream properties before displaying. |
-low-delay |
bool |
true |
Tells the player to ignore standard container synchronization buffers. |
-framedrop |
bool |
true |
Drop late video frames if decoding lags behind. Prevents stream drift. |
-hwaccel |
string |
"vaapi" |
Hardware-accelerated decoding method (options: vaapi, none). |
-hwaccel-device |
string |
"/dev/dri/renderD128" |
Device render node used for hardware decoding. |
-loglevel |
string |
"warning" |
Verbosity of the player logs (quiet, error, warning, info, debug). |
-test |
bool |
false |
Headless client mode. Measures network packet receipt without spawning a GUI window. |
If running on a modern Wayland desktop environment with an Intel, AMD, or NVIDIA GPU, starting Bob with default options will automatically activate GStreamer-native hardware GPU capture and encoding:
./qup shareIf streaming over a high-speed local network or fiber link, you can push the quality to the absolute limit by raising the target bitrate:
./qup share -bitrate=15000 -size=1920x1080 -fps=60If Bob's upload connection is constrained, reduce the framerate and lower the target bitrate to maintain smoothness:
./qup share -bitrate=3000 -fps=30 -preset=veryfastQup includes a self-contained integration test suite to verify pipeline construction and end-to-end communication loopbacks:
./test.shThe script builds both binaries, sets up a temporary local D-Bus bus, and executes loopback streaming under headless X11 and Wayland (using the mock ScreenCast portal).