A zero-configuration screen extender with automatic network discovery
Star this Project β’ Become a Sponsor β’ Report Issue
Your support helps maintain and improve RGM for everyone.
- Overview
- Features
- Architecture
- How It Works
- Display Modes
- System Requirements
- Installation
- Building from Source
- Usage Guide
- Network Configuration
- Performance Characteristics
- Troubleshooting
- Roadmap
- Support the Project
RGM (Ralefaso GlassMirror) is a lightweight, cross-platform screen extender that turns any networked machine into a wireless second monitor. It implements SSDP (Simple Service Discovery Protocol) to automatically detect receiver hosts on your local network, requiring zero manual IP configuration.
The receiver machine opens a borderless fullscreen window that presents itself as a natural display extension β positioned logically to the right of, or below, the sender's screen. The result looks and behaves like a real second monitor plugged into the sender machine.
A mirror mode (classic screen duplication) is also available for presentations and demos.
| Category | Capability |
|---|---|
| Screen Extension | Extend Right, Extend Below, or Mirror modes β chosen per session |
| Discovery | Zero-configuration SSDP automatic detection, no IP setup needed |
| Performance | 60 FPS streaming, 4 MB socket buffers, TCP_NODELAY optimisation |
| CPU Offload | RLE frame compression and colour correction offloaded to the receiver's CPU (TCP 8082) |
| Port Inspector | Full remote port inspection: list, query, and kill processes on any receiver port (TCP 8083) |
| Platform Support | Linux (X11), Windows 10/11, macOS 10.15+ |
| Display Handling | Auto-resolution handshake, borderless fullscreen, aspect-ratio scaling |
| Splash Screen | rcorp.jpeg corporate logo displayed at launch via SDL2_image |
| Monitoring | Real-time FPS, bandwidth, source/destination resolution, real measured offload timing |
| User Interface | Splash screen, menu-driven launcher, direct executable mode |
RGM/
βββ makefile # Cross-platform build (Linux/macOS/Windows)
βββ src/ # Source code
β βββ app.cpp # Launcher: splash + menu
β βββ sender.cpp # Screen capture, extender handshake, stream, port inspector client
β βββ receiver.cpp # Fullscreen display, SSDP advertiser, compute svc, port svc
β βββ discover.cpp # SSDP discovery engine
β βββ discover.h # Discovery API
β βββ gpu_accelerate.c # Remote CPU offload (RLE compress / colorfix) with real timing
β βββ gpu_accelerate.h # Compute offload API
β βββ ports.cpp # Remote port inspection service (server + client)
β βββ ports.h # Port inspector API
βββ assets/
β βββ icons/
β βββ rcorp.jpeg # Corporate splash logo β shown at startup
β βββ RGM.png # Fallback splash logo
βββ build/ # Compiled object files
βββ sender # Sender executable
βββ receiver # Receiver executable
βββ app # Launcher executable
βββ readme.md
| Executable | Role |
|---|---|
app |
Menu launcher β choose send / receive mode |
sender |
Captures local display, negotiates mode, streams frames, runs port inspector client |
receiver |
Advertises via SSDP, opens fullscreen window, renders frames, runs compute and port services |
| Port | Protocol | Purpose |
|---|---|---|
| 1900 | UDP multicast | SSDP discovery (M-SEARCH / NOTIFY) |
| 8081 | TCP | Video frame stream |
| 8082 | TCP | CPU compute offload service (RLE compress / colour fix) |
| 8083 | TCP | Remote port inspection service |
RGM uses the same multicast discovery protocol as UPnP/DLNA β no manual IP entry required.
Receiver β joins 239.255.255.250:1900
β sends periodic NOTIFY announcements
β listens for M-SEARCH queries
Sender β broadcasts M-SEARCH to 239.255.255.250:1900
β collects 200 OK responses
β lists discovered receivers
β user picks one
Once the user selects a receiver and a display mode, the sender opens a TCP connection to port 8081 and exchanges an extended handshake:
Sender β Receiver (16 bytes, network byte order)
uint32 sender_width
uint32 sender_height
uint32 fps
uint32 mode 0=mirror 1=extend-right 2=extend-below
Receiver β Sender (12 bytes, network byte order)
uint32 receiver_width
uint32 receiver_height
uint32 status 0=OK
The sender uses the receiver's reported resolution to display the combined virtual desktop layout in the terminal.
| Mode | Window behaviour |
|---|---|
| Extend Right / Below | SDL_WINDOW_FULLSCREEN_DESKTOP β borderless, covers the entire receiver display, appears as a physical second monitor |
| Mirror | Normal resizable window, scaled to fit |
In extend modes the receiver also draws a subtle 2-pixel blue edge glow on the side that logically joins to the sender's screen (left edge for extend-right, top edge for extend-below).
Every frame:
Sender β captures screen (X11 / GDI / CoreGraphics)
β optionally RLE-compresses via compute offload service
β sends [uint32 frame_size] [frame_bytes]
Receiver β reads size header
β reads frame_bytes
β RLE-decompresses if frame_size < raw_size
β SDL_UpdateTexture β SDL_RenderCopy β SDL_RenderPresent
The receiver runs a second TCP server on port 8082 (gpu_accelerate.c). The sender connects to it optionally before streaming begins. All work is performed on the receiver's CPU β the sender sends raw pixel data, the receiver processes it and sends back the result, so processing load is shifted off the sender machine.
Timing is measured with clock_gettime(CLOCK_MONOTONIC) on Linux/macOS and QueryPerformanceCounter on Windows, so the ms_elapsed values reported are real measured durations, not estimates.
| Operation | Code | Description |
|---|---|---|
| PING | 0xFF | Heartbeat / handshake check |
| COMPRESS | 0x01 | RLE-compress a raw RGB frame (receiver CPU) |
| COLORCONV | 0x03 | BGR β RGB channel swap (receiver CPU) |
If the compute service is unavailable the sender falls back silently to uncompressed local frames.
The receiver runs a third TCP server on port 8083 (ports.cpp). Once connected, the sender can interactively inspect every listening socket on the receiver machine:
| Operation | Description |
|---|---|
| LIST_TCP | All TCP sockets with PID, process name, state, addresses |
| LIST_UDP | All UDP sockets |
| LIST_ALL | TCP + UDP combined |
| GET_PORT | Details for one specific port number |
| KILL_PORT | Send SIGTERM to the process owning a port |
Port data is collected natively per platform:
| Platform | Method |
|---|---|
| Linux | /proc/net/tcp, tcp6, udp, udp6 + inodeβPID mapping via /proc/PID/fd |
| macOS | lsof -nP -iTCP -iUDP |
| Windows | GetExtendedTcpTable / GetExtendedUdpTable (iphlpapi) + CreateToolhelp32Snapshot |
βββββββββββββββ handshake ββββββββββββββββββββββββββββββββββββββββββββ
β SENDER β βββββββββββΊ β RECEIVER β
β β β β
β capture β frame data β RLE decode β SDL2 texture β
β (X11/GDI/ β βββββββββββΊ β β fullscreen borderless window β
β CG) β TCP 8081 β (extend-right / below / mirror) β
β β β β
β RLE via β compute protoβ gpu_service_run() on TCP 8082 β
β CPU offload β βββββββββββΊ β (RLE compress / color fix, real timing) β
β β TCP 8082 β β
β port cmds β port proto β ports_service_run() on TCP 8083 β
β interactive β βββββββββββΊ β (list/query/kill receiver ports) β
βββββββββββββββ TCP 8083 ββββββββββββββββββββββββββββββββββββββββββββ
The receiver's display appears logically to the right of the sender's screen. The receiver opens a borderless fullscreen window with a blue left-edge indicator showing where the screens join.
ββββββββββββββββββ¬βββββββββββββββββ
β β β
β SENDER β RECEIVER β
β (your machine)β (extended) β
β ββ blue edge β
ββββββββββββββββββ΄βββββββββββββββββ
The receiver's display appears below the sender's screen, with a blue top-edge indicator.
ββββββββββββββββββββββββββββββββββ
β SENDER β
ββββββββββββββββββββββββββββββββββ
β² blue edge
ββββββββββββββββββββββββββββββββββ
β RECEIVER β
ββββββββββββββββββββββββββββββββββ
The receiver displays an exact duplicate of the sender's screen in a normal resizable window. Use this for presentations.
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 1 GHz | 2 GHz dual-core |
| RAM | 256 MB | 512 MB |
| Network | 100 Mbps | 1 Gbps wired |
| Display | 800Γ600 | 1920Γ1080 |
| Requirement | Detail |
|---|---|
| Distribution | Ubuntu 18.04+, Debian 10+, Fedora 32+, Arch |
| Compiler | GCC 8+ (C++17) |
| Libraries | libX11-dev, libsdl2-dev, libsdl2-image-dev, pthread |
| Build tool | make |
| Requirement | Detail |
|---|---|
| Version | Windows 10 build 1903+ or Windows 11 |
| Compiler | MinGW-w64 (MSYS2) or MSVC 2019+ |
| Libraries | SDL2, SDL2_image (from MSYS2 packages) |
| SDK | Windows SDK 10.0+ (iphlpapi required for port inspector) |
| Requirement | Detail |
|---|---|
| Version | macOS Catalina 10.15+ |
| Compiler | Clang 12+ (Xcode 12+) |
| Libraries | brew install sdl2 sdl2_image |
| Screen capture | CoreGraphics (built-in) |
Note:
SDL2_imageis required on all platforms for the rcorp.jpeg splash screen.
git clone https://github.com/RR-Ralefaso/RGM.git
cd RGM
sudo apt update
sudo apt install -y g++ make libx11-dev libsdl2-dev libsdl2-image-dev
make install-deps
make
./appsudo dnf install gcc-c++ make libX11-devel SDL2-devel SDL2_image-devel
git clone https://github.com/RR-Ralefaso/RGM.git
cd RGM && make && ./appsudo pacman -S gcc make libx11 sdl2 sdl2_image
git clone https://github.com/RR-Ralefaso/RGM.git
cd RGM && make install-deps && make && ./app# Install Homebrew if needed
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install sdl2 sdl2_image
git clone https://github.com/RR-Ralefaso/RGM.git
cd RGM && make install-deps && make && ./app# In MSYS2 MinGW64 shell
pacman -S mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image
git clone https://github.com/RR-Ralefaso/RGM.git
cd RGM
make install-deps
make
./app.exe| Command | Description |
|---|---|
make |
Build all components |
make sender |
Build sender only |
make receiver |
Build receiver only |
make app |
Build launcher only |
make debug |
Build with -g -O0 -DDEBUG |
make clean |
Remove all build output |
make check |
Verify environment, sources, and assets |
make install-deps |
Install system dependencies |
RGM/
βββ sender (or sender.exe)
βββ receiver (or receiver.exe)
βββ app (or app.exe)
βββ build/
βββ sender.o
βββ receiver.o
βββ app.o
βββ discover.o
βββ gpu_accelerate.o
βββ ports.o
On the receiver machine (the machine that will act as the second monitor):
./receiverExpected output:
========================================
RGM RECEIVER v2.0.1
========================================
Local IP : 192.168.1.105
My display : 1920x1080
Stream TCP : 8081
Compute TCP: 8082
Ports TCP : 8083
SSDP UDP : 239.255.255.250:1900
GPU Stats : gpu_stats.json
Modes : extend-right | extend-below | mirror
========================================
Waiting for sender on TCP 8081 ...
On the sender machine (the machine whose screen you want to extend):
./senderInteractive session:
Discovering receivers...
Found: 192.168.1.105:8081 β testing...
Connection OK
Discovery complete: 1 receiver(s) found
RECEIVERS FOUND:
[0] 192.168.1.105:8081
Select display mode:
1 Extend Right (receiver = right monitor)
2 Extend Below (receiver = bottom monitor)
3 Mirror (duplicate screen)
Choice [1]:
Mode: Extend Right
Remote compute (CPU offload) active
Port inspector active (press 'p' during stream)
Extended desktop active:
Sender: 1920x1080
Receiver: 1920x1080
Layout: Extend Right
Total: 3840x1080
Streaming β Ctrl+C to stop
The receiver's display immediately goes fullscreen and begins showing the sender's screen content.
./appThe launcher shows the rcorp.jpeg splash screen then presents:
ββββββββββββββββββββββββββββββββββ
β RGM v2.0.2 β
β βββββββββββββββββββββββββββββββββ£
β β
β 1. SEND SCREEN β
β 2. RECEIVE SCREEN β
β 0. EXIT β
β β
ββββββββββββββββββββββββββββββββββ
| Key | Action |
|---|---|
| ESC or Q | Disconnect and exit |
| F11 | Toggle fullscreen (extend modes) |
| Close window | Stop receiving |
| Key / Input | Action |
|---|---|
| Ctrl+C | Graceful shutdown |
| Number at prompt | Select receiver from list |
| 1 / 2 / 3 at mode prompt | Choose Extend Right / Extend Below / Mirror |
p at stream prompt |
Open Port Inspector menu |
When the sender connects to a receiver, it also connects to the port inspection service on TCP 8083. Press p (or enter it at the prompt) to open the interactive menu:
ββββββββββββββββββββββββββββββββββ
β RECEIVER PORT INSPECTOR β
β βββββββββββββββββββββββββββββββββ£
β 1. List all TCP ports β
β 2. List all UDP ports β
β 3. List ALL ports β
β 4. Query specific port β
β 5. Kill process on port β
β 0. Back to stream β
ββββββββββββββββββββββββββββββββββ
The table output shows protocol, local address:port, remote address:port, TCP state, PID, and process name β all read live from the receiver OS. The kill option sends SIGTERM (Linux/macOS) or TerminateProcess (Windows) to the process owning the specified port.
sudo ufw allow 1900/udp comment 'RGM SSDP'
sudo ufw allow 8081/tcp comment 'RGM Video Stream'
sudo ufw allow 8082/tcp comment 'RGM Compute Offload'
sudo ufw allow 8083/tcp comment 'RGM Port Inspector'
sudo ufw reloadsudo iptables -A INPUT -p udp --dport 1900 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8081 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8082 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 8083 -j ACCEPTNew-NetFirewallRule -DisplayName "RGM SSDP" -Direction Inbound -Protocol UDP -LocalPort 1900 -Action Allow
New-NetFirewallRule -DisplayName "RGM Stream" -Direction Inbound -Protocol TCP -LocalPort 8081 -Action Allow
New-NetFirewallRule -DisplayName "RGM Compute" -Direction Inbound -Protocol TCP -LocalPort 8082 -Action Allow
New-NetFirewallRule -DisplayName "RGM Ports" -Direction Inbound -Protocol TCP -LocalPort 8083 -Action Allowsudo /usr/libexec/ApplicationFirewall/socketfilterfw --add /path/to/receiver
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add /path/to/sender- All devices must be on the same subnet
- Multicast must be enabled on network switches (IGMP snooping)
- Wired Ethernet recommended for 60 FPS at 1080p
- WiFi 5 GHz (802.11ac) workable for lower resolutions
| Resolution | 30 FPS | 60 FPS |
|---|---|---|
| 1280Γ720 | ~125 MB/s | ~250 MB/s |
| 1920Γ1080 | ~280 MB/s | ~560 MB/s |
| 2560Γ1440 | ~500 MB/s | ~1 GB/s |
CPU offload RLE compression typically reduces bandwidth by 30β70% for desktop content (text, UI). Video content compresses less. The compression ratio and time are reported accurately based on real measurements.
| Connection | Typical Latency |
|---|---|
| Wired 1 Gbps | < 5 ms |
| WiFi 5 GHz | 10β15 ms |
| WiFi 2.4 GHz | 20β35 ms |
The receiver writes live statistics to gpu_stats.json every 60 seconds and prints a summary to the console every 30 seconds. Stats include total operations, bytes processed in/out, real average compression ratio, actual milliseconds per operation, and per-client connection counts.
| Problem | Check | Solution |
|---|---|---|
| No receivers found | Network connectivity | Verify firewall allows UDP 1900 on receiver |
| Connection refused | Receiver running? | Check port 8081 is open; restart receiver |
| Black screen on receiver | Handshake exchange | Ensure both binaries are the same version |
| Low FPS | Network utilisation | Use wired Ethernet; enable CPU offload |
| Compute offload unavailable | Port 8082 blocked | Allow TCP 8082 in firewall on receiver |
| Port inspector unavailable | Port 8083 blocked | Allow TCP 8083 in firewall on receiver |
| rcorp.jpeg not showing | Asset path | Place rcorp.jpeg in assets/icons/; run make check |
| SDL_image not found | Missing library | Run make install-deps or install libsdl2-image-dev |
| Build fails on Windows | Missing iphlpapi | Ensure Windows SDK is installed; iphlpapi is linked automatically |
| Build fails on macOS | Homebrew paths | Run brew install sdl2 sdl2_image; check BREW_PREFIX in makefile |
# Verify receiver is listening on all four ports
netstat -tulpn | grep -E '8081|8082|8083|1900'
# Test TCP reachability
nc -zv <receiver-ip> 8081
nc -zv <receiver-ip> 8082
nc -zv <receiver-ip> 8083
#ensure all dependancies are installed
make install-deps
# Check assets and source files
make check
# Full rebuild
make clean && make- Screen mirroring (original)
- Screen extender β Extend Right
- Screen extender β Extend Below
- Remote CPU offload (RLE compression + colour correction)
- Real measured offload timing (clock_gettime / QueryPerformanceCounter)
- Extended handshake (resolution exchange)
- rcorp.jpeg splash via SDL2_image
- macOS CoreGraphics capture
- Remote port inspection service (TCP 8083)
- Per-client GPU stats tracking + JSON export
- app hosting onto receiver making receiver run the heavy tasks on it rather than everything running on sender
- H.264/H.265 compression for bandwidth reduction
- Audio capture and streaming
- TLS encryption
- Multi-monitor source selection
- Partial screen region selection
- Adaptive FPS based on network conditions
- Wayland display server support
- Mouse pointer handoff across display boundary
- remote access ,full or partial , to all the storage of the Receiver
| Action | Impact |
|---|---|
| Star on GitHub | Increases project visibility |
| Become a Sponsor | Funds ongoing development |
| Report Issues | Helps improve stability |
| Contribute Code | Accelerates feature development |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β "Seeking to solve complex business problems through β
β analytical precision and elegant code - on any platform." β
β β
β - RR-Ralefaso (polaris) β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Linux β’ Windows 10/11 β’ macOS β One codebase, all platforms.