WebX11 – Turn any executable into a resizable web page

2 hours ago 2

Stream any GNU/Linux GUI application directly to a web browser with low(-enough) latency and automatic window resizing.

No VNC, no special client software, no websockify - just a simple HTTP server that makes desktop applications accessible through modern web protocols.

WebX11 creates virtual X11 displays and streams them to web browsers using WebTransport (with WebSocket fallback).

It's designed to be simple, fast, and easy to deploy, and only have dependencies you probably already have.

You're a sysadmin and need to open a GUI application on a remote server in a whim?

Need to expose a GUI app for a lab?

WebX11 has got you!

System Dependencies:

  • Xvfb - Virtual framebuffer X server
  • openssl (optional) - For generating TLS certificates (if you use WebTransport)
pip install git+https://github.com/lp1dev/WebX11.git
pip install aioquic # Optional, for an installation with webtransport support required

Optionally, create a settings.json file (see Configuration below)

Run the server:

python -m webx11.server (your_executable)

Key Features:

  • 🚀 Low latency streaming via WebTransport (HTTP/3) or WebSocket fallback
  • 📐 Automatic window resizing based on browser viewport
  • 🎯 Direct X11 integration - no VNC or intermediary protocols
  • 🔌 Simple HTTP API for creating and managing displays
  • Minimal dependencies - just Xvfb and OpenSSL
  • 🎮 Full input support - mouse, keyboard, scroll, dead keys
  • 📊 Built-in FPS counter (press F3 to toggle)

Note : Regarding the latency. Realistically, you're not going to do some heavy gaming on this. But I'm currently streaming my terminal on my 2k screen and the 12 FPS I have are more than enough for this usage, especially with the low input latency! Also, if you're able to do some optimizations and gain a few FPS, you're very welcome to make a PR \o/!

  1. WebX11 spawns virtual X displays using Xvfb
  2. Captures window content and encodes it as WebP
  3. Streams frames via WebTransport streams (or WebSocket)
  4. Sends input events (mouse/keyboard) back to X11
  5. Automatically resizes the X display to match browser window

Create a settings.json file in the project root:

{ "resize_mode": "resize-x11", "transport": "webtransport", "image_quality": 80, "dpi": 300, "max_width": 3440, "max_height": 1440, "max_fps": 30, "can_start_executables": false }

Note: Keep max_width and max_height values superior to the maximum size your windows will be resized to. High values do not actually have a significative impact on performance with the resize-x11 parameter.

Option Type Description
resize_mode string "resize-x11" enables automatic X display resizing, "none" disables it, "stretch" stretches the image without actual resizing
transport string "webtransport" (recommended) or "websocket"
image_quality number WebP quality (1-100), affects bandwidth and visual quality
dpi number Display DPI setting
max_width number Maximum display width in pixels
max_height number Maximum display height in pixels
max_fps number Maximum frames per second (1-60)
can_start_executables boolean Allow starting executables via API (security consideration)

The server will start on:

  • HTTP API: http://localhost:8080
  • WebSocket: ws://localhost:8081
  • WebTransport: https://localhost:4433

Option 1: Using the HTTP API

curl -X POST http://localhost:8080/display \ -H "Content-Type: application/json" \ -d '{"width": 1920, "height": 1080}'

Response:

{ "message": "OK", "display": 1 }

Option 2: Starting with an Application (if can_start_executables: true)

curl -X POST http://localhost:8080/display/1/run \ -H "Content-Type: application/json" \ -d '{"executable": "firefox"}'

Open your browser and navigate to:

or

http://localhost:8080/display/{id}

To access a specific display using its id.

For WebTransport (recommended):

  • Chrome/Edge with command-line flags (see below)
  • The browser must accept the self-signed certificate

Starting Chrome with WebTransport support:

google-chrome \ --origin-to-force-quic-on=localhost:4433 \ --ignore-certificate-errors-spki-list=<CERTIFICATE_FINGERPRINT> \ --test-type

The certificate fingerprint is printed when the server starts.

For WebSocket fallback:

  • Any modern browser (Firefox, Safari, Chrome, Edge)
  • No special configuration needed

List all active displays

Response:

[ { "display_id": 1, "width": 1920, "height": 1080, "windows": [...] } ]

Create a new display

Note: Make sure the display you create is always bigger than the max resize area possible. The X server will crash on resize otherwise.

Starting with a very large display size is not an issue, the first automatic resize will resize it into a smaller display.

Request:

{ "width": 1920, "height": 1080 }

Response:

{ "message": "OK", "display": 1 #Internal display ID }

Close a display

Response:

Start an executable on a display (requires can_start_executables: true)

Request:

{ "executable": "firefox" }

Response:

{ "message": "OK", "display": 1, #Internal display ID "process": 12345 #PID }

POST /resize/{display_id}/{width}/{height}

Manually resize a display

Response:

Access the web interface for a display

Returns an HTML page with the interactive display viewer.

If the server is started with no executable as a parameter, a display needs to be created via the HTTP API.

Built-in shortcuts:

  • F3 - Toggle FPS counter

WebTransport Mode:

  • Each frame is sent on a separate unidirectional stream
  • Control messages (input) use datagrams
  • 10-30 FPS depending on configuration
  • Lower latency than WebSocket

WebSocket Mode:

  • Frames sent as binary blobs
  • 8~25 FPS depending on configuration
  • Control messages as JSON
  • Works everywhere, no special setup needed
  1. Lower image_quality (60-80) for better performance on slow networks
  2. Reduce max_fps (15-20) if CPU usage is high
  3. Use WebTransport when possible for lowest latency
  4. Match max_width/max_height to your typical use case
  5. Enable resize mode to adapt to different screen sizes

This project is a small project, the code has not been designed with the utmost security in mind and as of today does not include authentication. It should NOT be exposed to the internet, especially on sensitive infrastructures! The WebSockets and HTTP APIs use no encryption, keep that in mind.

  • TLS Certificates: WebTransport uses self-signed certificates by default
  • can_start_executables: Set to false in production to prevent arbitrary code execution
  • No authentication: Consider adding authentication for production deployments
  • No encryption : Bring your own, by putting a HTTPS/WSS reverse proxy in front of WebX11
  • Network exposure: By default, only binds to localhost

WebTransport not connecting

  • Ensure Chrome is started with the correct flags
  • Check that port 4433 is not blocked
  • Verify the certificate fingerprint matches the generated certificates

Black screen or no frames

  • Check that Xvfb is installed and working
  • Verify that your executable is correctly running on the display
  • Check console logs for errors
  • Try reducing image quality
  • Click on the display area to ensure focus
  • Check browser console for errors
  • International keyboards should work automatically but some layouts are still a WIP
  • Reduce max_fps in settings
  • Lower image_quality
  • Check if multiple displays are running
webx11/ ├── main.py # Entry point ├── display.py # X11 display management ├── webtransport.py # WebTransport server ├── websocket.py # WebSocket server ├── api.py # HTTP API handlers ├── settings.py # Configuration management └── partials/ └── display.html # Client web interface

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Submit a pull request

GNU GPLv3

  • Built with aioquic for WebTransport support
  • Uses python-xlib for X11 integration
  • Inspired by the need for simple, clientless desktop streaming

For issues, questions, or feature requests, please open an issue on GitHub.

Read Entire Article