PyVNCServer is a modern VNC/RFB server written in Python, built for protocol correctness, practical client interoperability, low-latency desktop capture and an implementation you can actually inspect and extend.
+⚡
+### Low-latency pipeline
+Shared capture production, incremental region detection, request coalescing and adaptive encoding keep avoidable work off the hot path.
+
+
+
+🧩
+### Real RFB encodings
+Raw, CopyRect, RRE, Hextile, Zlib, Tight and ZRLE are available, with optional JPEG and H.264 extension paths.
+
+
+
+🖥️
+### UltraVNC interoperability
+The server contains targeted fixes for Tight stream state and RRE fallback behavior discovered during UltraVNC compatibility testing.
+
+
+
+🌐
+### Browser transport
+Binary WebSocket transport supports noVNC-style browser connections with Origin allowlisting and bounded frame/message sizes.
+
+
+
+🔒
+### Security-conscious defaults
+Loopback binding, fail-closed configuration validation, TLS support, authentication throttling and connection admission limits are built in.
+
+
+
+🔬
+### Inspectable by design
+The protocol, capture, session, encoding and transport layers are Python code rather than a black-box native server.
+
+
+
+
+## Run it
+
+```bash
+# clone and install
+git clone https://github.com/xulek/PyVNCServer.git
+cd PyVNCServer
+python -m pip install -e .
+
+# start with the packaged safe configuration
+pyvncserver serve
+```
+
+The default bind is `127.0.0.1:5900`. To expose the server on another interface, configure authentication or explicitly opt into unauthenticated non-loopback operation. See [Security](security.md).
+
+## How the pieces fit together
+
+```mermaid
+flowchart LR
+ U[UltraVNC / RFB client] --> TCP[TCP transport]
+ N[noVNC browser] --> WS[WebSocket transport]
+ TCP --> H[RFB handshake + security]
+ WS --> H
+ H --> S[Client session]
+ C[Shared CaptureProducer] --> F[Framebuffer pipeline]
+ F --> E[Encoding manager]
+ E --> S
+ S --> I[Keyboard · pointer · clipboard]
+```
+
+## Choose your path
+
+| I want to… | Go to |
+| --- | --- |
+| install and connect for the first time | [Installation & quick start](getting-started.md) |
+| understand every TOML option | [Configuration](configuration.md) |
+| tune or debug UltraVNC | [UltraVNC guide](ultravnc.md) |
+| connect a browser/noVNC client | [noVNC & WebSocket](novnc.md) |
+| compare encodings | [Encoding reference](encodings.md) |
+| expose the server beyond localhost | [Security](security.md) |
+| understand internals | [Architecture](architecture.md) |
+| profile or benchmark the server | [Performance](performance.md) |
From 3bb74ce70b71f01bdb6e196d6ca6285a4b23fa93 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:32:12 +0200
Subject: [PATCH 05/20] docs: add getting started guide
---
docs/getting-started.md | 124 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 124 insertions(+)
create mode 100644 docs/getting-started.md
diff --git a/docs/getting-started.md b/docs/getting-started.md
new file mode 100644
index 0000000..3ab3ee9
--- /dev/null
+++ b/docs/getting-started.md
@@ -0,0 +1,124 @@
+# Installation & quick start
+
+This page takes you from a clean Python environment to a working local VNC session.
+
+## Requirements
+
+- Python **3.11+**
+- a supported desktop capture/input environment
+- a VNC viewer such as UltraVNC
+
+Windows is the primary platform for the optional DXCam/DXGI fast capture path. MSS provides the portable capture backend.
+
+## Install from source
+
+=== "Windows PowerShell"
+
+ ```powershell
+ git clone https://github.com/xulek/PyVNCServer.git
+ cd PyVNCServer
+ py -m venv .venv
+ .\.venv\Scripts\Activate.ps1
+ python -m pip install -U pip
+ python -m pip install -e .
+ ```
+
+=== "Linux / macOS"
+
+ ```bash
+ git clone https://github.com/xulek/PyVNCServer.git
+ cd PyVNCServer
+ python3 -m venv .venv
+ source .venv/bin/activate
+ python -m pip install -U pip
+ python -m pip install -e .
+ ```
+
+### Optional extras
+
+```bash
+# NumPy + DXCam on Windows
+python -m pip install -e ".[performance]"
+
+# H.264 extension path
+python -m pip install -e ".[h264]"
+
+# local development/test dependencies
+python -m pip install -e ".[dev]"
+```
+
+## Start the server
+
+```bash
+pyvncserver serve
+```
+
+Equivalent module invocation:
+
+```bash
+python -m pyvncserver serve
+```
+
+The packaged default configuration binds to:
+
+```text
+127.0.0.1:5900
+```
+
+!!! tip
+ Keeping the first test on loopback removes firewall, NAT and transport-security variables. Confirm the RFB path works locally before exposing the listener.
+
+## Connect with a viewer
+
+For UltraVNC:
+
+1. Open UltraVNC Viewer.
+2. Connect to `127.0.0.1:5900`.
+3. Leave the encoding on **Auto** for the first test.
+4. Once connected, try Tight, ZRLE, Hextile, Zlib and RRE individually if you want to compare behavior.
+
+See the [UltraVNC guide](ultravnc.md) for compatibility-specific notes.
+
+## Use a custom config
+
+Copy the example config and edit it:
+
+```bash
+cp config/pyvncserver.toml my-vnc.toml
+pyvncserver serve --config my-vnc.toml
+```
+
+On PowerShell:
+
+```powershell
+Copy-Item config/pyvncserver.toml my-vnc.toml
+pyvncserver serve --config my-vnc.toml
+```
+
+Override only logging verbosity:
+
+```bash
+pyvncserver serve --config my-vnc.toml --log-level DEBUG
+```
+
+## Expose it on your LAN
+
+Do **not** only change `host` and forget authentication. A minimal authenticated LAN example is:
+
+```toml
+[server]
+host = "0.0.0.0"
+port = 5900
+
+[security]
+password = "vncpass"
+```
+
+Classic VNC authentication is limited to 8 Latin-1 bytes and does not encrypt the session. Prefer TLS, SSH tunnelling or a VPN on untrusted networks. See [Security](security.md).
+
+## Next steps
+
+- [Configuration reference](configuration.md)
+- [UltraVNC](ultravnc.md)
+- [noVNC & WebSocket](novnc.md)
+- [Troubleshooting](troubleshooting.md)
From 85642c4d9fe581705efed12f3e068fb519602b69 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:32:32 +0200
Subject: [PATCH 06/20] docs: add configuration reference
---
docs/configuration.md | 150 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 150 insertions(+)
create mode 100644 docs/configuration.md
diff --git a/docs/configuration.md b/docs/configuration.md
new file mode 100644
index 0000000..aca46fd
--- /dev/null
+++ b/docs/configuration.md
@@ -0,0 +1,150 @@
+# Configuration
+
+PyVNCServer uses TOML. The packaged defaults live in `src/pyvncserver/default_config.toml`; the repository also contains `config/pyvncserver.toml` as an editable example.
+
+Configuration loading is intentionally **fail-closed**: missing/malformed files and invalid security-sensitive values stop startup instead of silently falling back to unsafe defaults.
+
+## Server
+
+| Option | Default | Meaning |
+| --- | ---: | --- |
+| `host` | `127.0.0.1` | TCP bind address |
+| `port` | `5900` | VNC/RFB TCP port, `1..65535` |
+| `frame_rate` | `30` | baseline target FPS, `1..240` |
+| `lan_frame_rate` | `90` | LAN target FPS, `1..240` |
+| `network_profile_override` | `auto` | automatic detection or `localhost`, `lan`, `wan` |
+| `scale_factor` | `1.0` | framebuffer scale; must be positive |
+| `capture_backend` | `auto` | capture backend selection |
+| `capture_probe_frames` | `0` | optional backend startup probe count |
+| `capture_probe_warn_ms` | `40.0` | slow-probe warning threshold |
+| `max_connections` | `10` | total admitted connections |
+| `max_connections_per_ip` | `4` | per-IP cap |
+| `max_unauthenticated_connections` | `4` | pre-authentication admission cap |
+| `handshake_timeout` | `5.0` | handshake timeout in seconds |
+| `client_socket_timeout` | `60.0` | client socket timeout |
+| `input_control_policy` | `single-controller` | `single-controller` or `shared` |
+
+## Security
+
+| Option | Default | Meaning |
+| --- | ---: | --- |
+| `password` | empty | full-control classic VNC password |
+| `read_only_password` | empty | read-only classic VNC password |
+| `allow_insecure_no_auth` | `false` | explicit opt-in for unauthenticated non-loopback bind |
+| `tls_enabled` | `false` | wrap the accepted connection in TLS |
+| `tls_cert_file` | empty | certificate path |
+| `tls_key_file` | empty | private-key path |
+| `auth_max_failures` | `5` | failures allowed in rate-limit window |
+| `auth_failure_window_seconds` | `30.0` | authentication failure window |
+| `auth_backoff_max_seconds` | `2.0` | maximum auth backoff |
+
+Passwords used by classic VNC authentication must be at most **8 Latin-1 bytes**. Full-control and read-only passwords must differ.
+
+## Features
+
+```toml
+[features]
+enable_region_detection = true
+enable_metrics = true
+enable_health_checks = true
+enable_request_coalescing = true
+enable_lan_adaptive_encoding = true
+enable_websocket = false
+enable_tight_extensions = true
+enable_cursor_encoding = false
+enable_copyrect_encoding = true
+enable_zrle_encoding = true
+enable_tight_encoding = true
+enable_jpeg_encoding = true
+enable_h264_encoding = false
+enable_parallel_encoding = true
+enable_capture_producer = true
+tight_stream_reset_for_ultravnc = false
+```
+
+`enable_capture_producer` uses one server-wide capture producer and publishes framebuffer generations to client sessions instead of asking every client thread to capture independently.
+
+`tight_stream_reset_for_ultravnc` is a compatibility switch. Leave it off unless you are troubleshooting Tight stream-state behavior with UltraVNC.
+
+## LAN tuning
+
+| Option | Default |
+| --- | ---: |
+| `raw_area_threshold` | `0.10` |
+| `raw_max_pixels` | `65536` |
+| `prefer_zlib` | `true` |
+| `zlib_area_threshold` | `0.08` |
+| `zlib_min_pixels` | `8192` |
+| `zlib_compression_level` | `2` |
+| `zlib_disable_if_request_gap_ms` | `1500` |
+| `jpeg_area_threshold` | `0.20` |
+| `jpeg_min_pixels` | `16384` |
+| `jpeg_quality_initial` | `84` |
+| `jpeg_quality_min` | `70` |
+| `jpeg_quality_max` | `95` |
+| `zrle_compression_level` | `3` |
+
+These settings influence adaptive encoding decisions; they do not override what the client advertised in `SetEncodings`.
+
+## WebSocket
+
+```toml
+[websocket]
+allowed_origins = []
+detect_timeout = 0.5
+max_handshake_bytes = 65536
+max_payload_bytes = 8388608
+max_buffer_bytes = 16777216
+max_message_bytes = 16777216
+```
+
+For browser clients, configure an explicit Origin allowlist. See [noVNC & WebSocket](novnc.md).
+
+## Limits
+
+```toml
+[limits]
+max_set_encodings = 1024
+max_client_cut_text = 16777216
+encoding_threads = 0
+tight_disable_for_ultravnc = false
+```
+
+`encoding_threads = 0` means automatic worker selection. `tight_disable_for_ultravnc` is a diagnostic fallback, not a recommended default.
+
+## Logging
+
+```toml
+[logging]
+log_level = "INFO"
+log_file = ""
+```
+
+Use `--log-level DEBUG` when diagnosing negotiation or encoding issues without editing the file.
+
+## Complete safe baseline
+
+```toml
+[server]
+host = "127.0.0.1"
+port = 5900
+frame_rate = 30
+lan_frame_rate = 90
+network_profile_override = "auto"
+scale_factor = 1.0
+capture_backend = "auto"
+max_connections = 10
+max_connections_per_ip = 4
+max_unauthenticated_connections = 4
+handshake_timeout = 5.0
+client_socket_timeout = 60.0
+input_control_policy = "single-controller"
+
+[security]
+password = ""
+read_only_password = ""
+allow_insecure_no_auth = false
+tls_enabled = false
+tls_cert_file = ""
+tls_key_file = ""
+```
From 343510f1f65bfa2dfad348058fde55249b7ff5a5 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:32:41 +0200
Subject: [PATCH 07/20] docs: add UltraVNC compatibility guide
---
docs/ultravnc.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 79 insertions(+)
create mode 100644 docs/ultravnc.md
diff --git a/docs/ultravnc.md b/docs/ultravnc.md
new file mode 100644
index 0000000..ae25897
--- /dev/null
+++ b/docs/ultravnc.md
@@ -0,0 +1,79 @@
+# UltraVNC
+
+UltraVNC Viewer is an important interoperability target for PyVNCServer. The server negotiates RFB 3.8 and only chooses rectangle encodings advertised by the client.
+
+## Recommended first connection
+
+1. Start PyVNCServer locally.
+2. Connect UltraVNC Viewer to `127.0.0.1:5900`.
+3. Use **Auto** first.
+4. If you are comparing encodings, test them one at a time after the baseline session works.
+
+## Encoding notes
+
+### Tight
+
+PyVNCServer includes compatibility work for two failure modes that matter in practice:
+
+- a rectangle must not be classified as Tight `FILL` unless it is actually uniform;
+- zlib stream reset state must stay synchronized with the Tight control byte when encoder state changes.
+
+If an UltraVNC session stops repainting only when Tight is selected, enable debug logging and try:
+
+```toml
+[features]
+tight_stream_reset_for_ultravnc = true
+```
+
+This is a compatibility switch, not a blanket recommendation.
+
+As a last-resort diagnostic:
+
+```toml
+[limits]
+tight_disable_for_ultravnc = true
+```
+
+If the problem disappears, capture the negotiation/update logs and compare the next selected encoding.
+
+### RRE
+
+RRE payloads begin with the subrectangle count and background pixel. Raw pixel bytes therefore cannot legally be returned while the rectangle header still advertises RRE.
+
+PyVNCServer avoids that stream-desynchronization failure by keeping the payload encoding ID consistent with the bytes actually emitted. Large RRE candidates are tiled so an individual tile can safely fall back to another negotiated encoding.
+
+### ZRLE / Hextile / Zlib / Raw
+
+These are useful control encodings when isolating a compatibility problem:
+
+- **Raw**: simplest protocol baseline, highest bandwidth;
+- **Hextile**: compatibility-oriented tiled encoding;
+- **Zlib**: compressed full rectangles;
+- **ZRLE**: tiled zlib/RLE, usually a good general-purpose option.
+
+## What to log
+
+Run:
+
+```bash
+pyvncserver serve --log-level DEBUG
+```
+
+Useful lines include:
+
+- client RFB version;
+- pixel format;
+- advertised encoding preference order;
+- negotiated rectangle encodings;
+- selected encoding per update/region;
+- socket reset/timeout messages.
+
+## Suggested compatibility matrix
+
+After a code change to an encoder, test a single connection while switching in this order:
+
+```text
+Raw → Hextile → Zlib → ZRLE → Tight → RRE → Auto
+```
+
+Also test reconnecting with each encoding selected from the start. State-related bugs can differ between an in-session switch and a fresh connection.
From 193a7269a7f0538b26b19a4ea9118018db36f902 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:32:49 +0200
Subject: [PATCH 08/20] docs: add noVNC and WebSocket guide
---
docs/novnc.md | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 71 insertions(+)
create mode 100644 docs/novnc.md
diff --git a/docs/novnc.md b/docs/novnc.md
new file mode 100644
index 0000000..9ca8076
--- /dev/null
+++ b/docs/novnc.md
@@ -0,0 +1,71 @@
+# noVNC & WebSocket
+
+PyVNCServer can transport RFB over a binary WebSocket connection for browser/noVNC-style clients.
+
+## Enable WebSocket detection
+
+```toml
+[features]
+enable_websocket = true
+
+[websocket]
+allowed_origins = [
+ "http://127.0.0.1:6080",
+ "http://localhost:6080",
+]
+```
+
+The allowlist should contain the exact browser Origin(s) serving your frontend.
+
+## Security properties
+
+The WebSocket implementation enforces or bounds:
+
+- masked client frames;
+- handshake size;
+- individual payload size;
+- assembled message size;
+- buffering;
+- control-frame fragmentation rules;
+- browser Origin allowlisting;
+- bytes pipelined immediately after the HTTP Upgrade request.
+
+!!! warning
+ WebSocket is a transport framing layer, not encryption. Use TLS (`wss://`) or another protected tunnel when traffic crosses an untrusted network.
+
+## noVNC frontend
+
+The repository contains browser client assets under `web/`. Serve the frontend with a normal static HTTP server or reverse proxy and point it at the PyVNCServer WebSocket endpoint.
+
+A typical topology is:
+
+```mermaid
+flowchart LR
+ B[Browser / noVNC] -->|HTTPS| P[Reverse proxy / static server]
+ B -->|WSS RFB| V[PyVNCServer]
+ V --> D[Desktop capture + input]
+```
+
+## Payload limits
+
+Defaults:
+
+```toml
+[websocket]
+max_handshake_bytes = 65536
+max_payload_bytes = 8388608
+max_buffer_bytes = 16777216
+max_message_bytes = 16777216
+```
+
+Keep `max_message_bytes >= max_payload_bytes`; configuration validation rejects an inconsistent value.
+
+## Troubleshooting
+
+If the TCP viewer works but noVNC does not:
+
+1. verify `enable_websocket = true`;
+2. inspect the browser console for Origin/Upgrade failures;
+3. ensure the Origin is explicitly allowlisted;
+4. confirm the frontend is using binary WebSocket RFB transport;
+5. if TLS terminates at a reverse proxy, verify the proxy forwards Upgrade/Connection headers.
From 70f8bdbcd41db027fbfe16cb8fd0d43aa955a836 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:33:01 +0200
Subject: [PATCH 09/20] docs: add encoding reference
---
docs/encodings.md | 50 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
create mode 100644 docs/encodings.md
diff --git a/docs/encodings.md b/docs/encodings.md
new file mode 100644
index 0000000..c981509
--- /dev/null
+++ b/docs/encodings.md
@@ -0,0 +1,50 @@
+# Encoding reference
+
+RFB clients advertise encoding IDs in `SetEncodings`. PyVNCServer chooses from the intersection of client-advertised and server-implemented encodings.
+
+| Encoding | ID | Characteristics | Good for |
+| --- | ---: | --- | --- |
+| Raw | `0` | no compression, simplest decoder path | debugging, fast LANs, baseline testing |
+| CopyRect | `1` | copies an existing framebuffer region | moves/scroll-like updates when metadata is available |
+| RRE | `2` | background + colored subrectangles | flat/simple regions |
+| Hextile | `5` | 16×16 tile-oriented encoding | broad compatibility |
+| Zlib | `6` | zlib-compressed pixel rectangles | general compressed updates |
+| Tight | `7` | Tight control/filter model + zlib/JPEG paths | common VNC viewers, mixed content |
+| ZRLE | `16` | 64×64 tiles + palette/RLE + zlib | strong general-purpose compression |
+
+Optional JPEG and H.264 paths exist as extension/feature paths and require compatible client behavior.
+
+## Adaptive selection
+
+The server can select different encodings for different changed regions. Selection considers:
+
+- what the client advertised;
+- connection/network profile;
+- region area and dimensions;
+- content characteristics;
+- configured thresholds;
+- optional codec availability.
+
+Adaptive selection never makes an unsupported encoding legal: the client capability list remains the upper bound.
+
+## Correct fallback semantics
+
+A framebuffer rectangle header carries the encoding ID. If an encoder decides its representation is inefficient and falls back, **the emitted encoding ID must change with the payload**.
+
+This is particularly important for RRE. A Raw payload behind an RRE header causes the client to interpret the first pixel bytes as RRE metadata and usually destroys stream synchronization.
+
+## Tight state
+
+Tight uses four logical zlib streams. The low bits of the compression-control byte can reset those streams. The encoder and client must agree on resets across consecutive rectangles and encoding transitions.
+
+## ZRLE
+
+ZRLE operates on tiles and can choose raw, solid, palette, packed-palette or RLE-style tile representations before zlib compression. The implementation includes optimized 32bpp paths and adaptive compression strategy selection.
+
+## Benchmark
+
+```bash
+PYTHONPATH=src python benchmarks/benchmark_encoders.py
+```
+
+Microbenchmarks are useful for regression detection, but end-to-end performance also depends on capture cost, changed-region size, socket behavior and the viewer's update request cadence.
From 0b7f52558fa6d36d5c917e8f3a5ae22763e38c15 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:33:10 +0200
Subject: [PATCH 10/20] docs: add security guide
---
docs/security.md | 89 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 89 insertions(+)
create mode 100644 docs/security.md
diff --git a/docs/security.md b/docs/security.md
new file mode 100644
index 0000000..aefd5b2
--- /dev/null
+++ b/docs/security.md
@@ -0,0 +1,89 @@
+# Security
+
+A remote framebuffer server processes network input and can inject keyboard/pointer events. Treat its exposure as a security boundary.
+
+## Safe defaults
+
+The packaged configuration binds to loopback:
+
+```toml
+[server]
+host = "127.0.0.1"
+port = 5900
+```
+
+With no configured password, a non-loopback bind is rejected unless you explicitly set:
+
+```toml
+[security]
+allow_insecure_no_auth = true
+```
+
+That opt-in should be restricted to controlled development environments.
+
+## Classic VNC authentication
+
+```toml
+[security]
+password = "vncpass"
+read_only_password = "readonly"
+```
+
+Important limitations:
+
+- classic VNC authentication uses the legacy DES challenge/response mechanism;
+- passwords are limited to **8 Latin-1 bytes** by this authentication scheme;
+- it authenticates the client but does **not encrypt** framebuffer or input traffic;
+- full-control and read-only passwords must be different.
+
+## TLS
+
+```toml
+[security]
+tls_enabled = true
+tls_cert_file = "server.crt"
+tls_key_file = "server.key"
+```
+
+When TLS is enabled, both files are required and must exist at startup.
+
+For internet-facing or otherwise untrusted paths, use TLS or place VNC behind a VPN/SSH tunnel.
+
+## Admission and authentication limits
+
+```toml
+[server]
+max_connections = 10
+max_connections_per_ip = 4
+max_unauthenticated_connections = 4
+handshake_timeout = 5.0
+
+[security]
+auth_max_failures = 5
+auth_failure_window_seconds = 30.0
+auth_backoff_max_seconds = 2.0
+```
+
+These controls reduce resource exhaustion and repeated authentication attempts before a session is established.
+
+## Browser Origin policy
+
+When WebSocket support is enabled for browser clients, configure explicit allowed Origins:
+
+```toml
+[websocket]
+allowed_origins = ["https://vnc.example.com"]
+```
+
+Do not treat Origin checking as transport encryption; use HTTPS/WSS as well.
+
+## Deployment checklist
+
+- [ ] bind only to the interface you actually need;
+- [ ] configure authentication for any non-loopback listener;
+- [ ] use TLS/VPN/SSH on untrusted networks;
+- [ ] keep WebSocket Origins explicit;
+- [ ] leave payload and handshake limits enabled;
+- [ ] do not log challenge/response secrets;
+- [ ] keep handshake timeout and per-IP admission limits enabled;
+- [ ] use a read-only password when input control is unnecessary.
From 762390a01f7655ee6a9693b2d41d8f14dac8c724 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:33:18 +0200
Subject: [PATCH 11/20] docs: add CLI and Python API reference
---
docs/api.md | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 73 insertions(+)
create mode 100644 docs/api.md
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 0000000..90a0b1d
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,73 @@
+# CLI & Python API
+
+## Command line
+
+The installed entry point is:
+
+```text
+pyvncserver
+```
+
+### Start the server
+
+```bash
+pyvncserver serve
+```
+
+### Custom configuration
+
+```bash
+pyvncserver serve --config path/to/config.toml
+```
+
+### Log level override
+
+```bash
+pyvncserver serve --log-level DEBUG
+```
+
+Accepted log-level values are handled by the server logging configuration; typical values are `DEBUG`, `INFO`, `WARNING`, `ERROR` and `CRITICAL`.
+
+Running without an explicit subcommand defaults to `serve`.
+
+## Public Python package surface
+
+The top-level package currently exports:
+
+```python
+from pyvncserver import (
+ __version__,
+ DEFAULT_CONFIG_PATH,
+ ServerSettings,
+ VNCServer,
+ VNCServerV3,
+ load_config_file,
+)
+```
+
+### Load and validate configuration
+
+```python
+from pyvncserver import ServerSettings
+
+settings = ServerSettings.from_file("config/pyvncserver.toml")
+print(settings.host, settings.port)
+```
+
+`ServerSettings` is immutable (`frozen`) and validates core/security values before use.
+
+### Flat compatibility mapping
+
+```python
+from pyvncserver import load_config_file
+
+config = load_config_file("config/pyvncserver.toml")
+print(config["host"])
+print(config["websocket_max_message_bytes"])
+```
+
+The loader flattens TOML sections into the mapping expected by the existing runtime while preserving typed validation through `ServerSettings`.
+
+## Stability note
+
+`pyvncserver` is the public package surface. `vnc_lib` remains an internal/compatibility implementation layer; new integrations should prefer imports from `pyvncserver` where an equivalent public symbol exists.
From d694b2600b7b138446046c9e88d34aae4ccd5fdd Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:33:27 +0200
Subject: [PATCH 12/20] docs: add architecture guide
---
docs/architecture.md | 70 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
create mode 100644 docs/architecture.md
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..4ba3ea5
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,70 @@
+# Architecture
+
+PyVNCServer separates listener/session orchestration from reusable protocol, capture, runtime and encoding components while retaining `vnc_lib` as a compatibility implementation layer.
+
+## Runtime flow
+
+```mermaid
+sequenceDiagram
+ participant C as VNC client
+ participant S as VNCServer
+ participant P as RFB protocol
+ participant CP as CaptureProducer
+ participant E as Encoding pipeline
+
+ C->>S: TCP / WebSocket connection
+ S->>P: RFB version + security negotiation
+ C->>P: ClientInit / SetPixelFormat / SetEncodings
+ C->>S: FramebufferUpdateRequest
+ CP-->>S: latest framebuffer generation
+ S->>E: changed regions + client capabilities
+ E-->>S: encoded rectangles
+ S-->>C: FramebufferUpdate
+```
+
+## Main package layout
+
+```text
+src/pyvncserver/
+├── app/server.py listener, admission, authentication, lifecycle
+├── session/ per-client state and message/update loop
+├── platform/ capture producer and platform integration facade
+├── runtime/ limits, registry, network/runtime helpers
+├── rfb/ public RFB protocol facade
+├── observability/ logging, metrics and profiling facade
+├── config.py typed TOML loading and fail-closed validation
+├── default_config.toml packaged safe defaults
+└── cli.py pyvncserver command
+
+src/vnc_lib/ protocol/encoder compatibility implementation layer
+```
+
+## Capture model
+
+With `enable_capture_producer = true`, one server-wide producer captures the desktop and publishes monotonically newer framebuffer generations. Client sessions consume the newest appropriate generation instead of independently serializing capture calls.
+
+Benefits:
+
+- capture work does not scale linearly with client count;
+- slow clients do not force every other client to capture at their pace;
+- changed-region history can be associated with produced frames.
+
+## Session state
+
+Per-client state includes negotiated pixel format, client-advertised encodings, framebuffer request state, input-control state and encoder/session-specific state. Keeping this scoped to the connection prevents one viewer's negotiation from mutating another viewer's protocol state.
+
+## Encoding pipeline
+
+The framebuffer path can:
+
+1. obtain the latest frame;
+2. determine whether the request is full or incremental;
+3. collect or detect changed regions;
+4. split large/problematic regions where required;
+5. choose only from negotiated encodings;
+6. encode regions, optionally in parallel;
+7. send one RFB FramebufferUpdate containing consistent rectangle headers/payloads.
+
+## Compatibility layer
+
+`src/vnc_lib/` remains in the repository because existing implementation and import paths still depend on it. Public package exports are provided through `pyvncserver`; future refactors can move implementation behind those public modules without forcing downstream callers to follow every internal reorganization.
From a15718261717bc7b61e484820217e4a4c19b346c Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:33:35 +0200
Subject: [PATCH 13/20] docs: add performance guide
---
docs/performance.md | 75 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100644 docs/performance.md
diff --git a/docs/performance.md b/docs/performance.md
new file mode 100644
index 0000000..293b5b0
--- /dev/null
+++ b/docs/performance.md
@@ -0,0 +1,75 @@
+# Performance
+
+PyVNCServer optimizes the path from desktop capture to encoded rectangle rather than relying on one codec alone.
+
+## Capture backends
+
+`capture_backend = "auto"` chooses from available backends.
+
+| Backend | Platform | Role |
+| --- | --- | --- |
+| DXCam / DXGI | Windows | optional fast Desktop Duplication capture path |
+| MSS | cross-platform | portable primary fallback |
+| Pillow ImageGrab | platform dependent | fallback capture path |
+
+Install the performance extras:
+
+```bash
+python -m pip install -e ".[performance]"
+```
+
+## Shared capture producer
+
+A server-wide producer captures once and distributes framebuffer generations to sessions. This avoids N clients causing N independent desktop captures.
+
+## Changed regions
+
+Incremental requests benefit from limiting work to changed regions. When a backend cannot provide native dirty metadata, PyVNCServer performs change detection above the backend.
+
+!!! note
+ Native DXGI dirty/move rectangle harvesting is not yet implemented in the current DXCam integration. CopyRect/dirty-region opportunities therefore depend on metadata available to the higher layers.
+
+## Request coalescing
+
+Viewers can generate update/input messages faster than the server should perform expensive work. Request coalescing reduces redundant framebuffer computations while preserving the most recent requested state.
+
+## Encoding workers
+
+```toml
+[limits]
+encoding_threads = 0
+```
+
+`0` enables automatic worker selection. More workers are not always faster: compression, memory bandwidth and Python/native-code behavior matter, and many tiny rectangles can lose to scheduling overhead.
+
+## Network profiles
+
+```toml
+[server]
+network_profile_override = "auto"
+frame_rate = 30
+lan_frame_rate = 90
+```
+
+Automatic profiling allows localhost/LAN/WAN tuning to diverge. Forcing `lan` everywhere can waste bandwidth or CPU on slower links.
+
+## Compression tuning
+
+Relevant LAN settings include zlib/ZRLE compression levels, raw thresholds and JPEG thresholds/quality. Lower zlib levels often reduce latency on fast LANs at the cost of additional bytes.
+
+## Benchmarks
+
+```bash
+PYTHONPATH=src python benchmarks/benchmark_encoders.py
+PYTHONPATH=src python benchmarks/benchmark_screen_capture.py
+PYTHONPATH=src python benchmarks/benchmark_screen_capture_methods.py
+PYTHONPATH=src python benchmarks/benchmark_lan_latency.py
+```
+
+For meaningful numbers:
+
+- benchmark on the target OS/GPU/display setup;
+- separate capture time from encode time;
+- test full-screen and small-region updates;
+- include the actual viewer over the intended network path;
+- record CPU usage and transmitted bytes, not only FPS.
From 48354fe2fdec436cad8412cbd7f9d80683017d1c Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:33:43 +0200
Subject: [PATCH 14/20] docs: add troubleshooting guide
---
docs/troubleshooting.md | 81 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
create mode 100644 docs/troubleshooting.md
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
new file mode 100644
index 0000000..e297036
--- /dev/null
+++ b/docs/troubleshooting.md
@@ -0,0 +1,81 @@
+# Troubleshooting
+
+## Viewer connects, then immediately disconnects
+
+Run with debug logs:
+
+```bash
+pyvncserver serve --log-level DEBUG
+```
+
+Check the last negotiated/selected encoding. If the disconnect happens immediately after one encoding is selected, retry with Raw or Hextile to establish whether the failure is codec-specific.
+
+For UltraVNC-specific cases, see [UltraVNC](ultravnc.md).
+
+## Tight connects but the image stops updating
+
+Try, in order:
+
+1. confirm Raw/Zlib/ZRLE updates continue;
+2. reconnect with Tight selected from session start;
+3. enable debug logging;
+4. try `tight_stream_reset_for_ultravnc = true` for UltraVNC;
+5. use `tight_disable_for_ultravnc = true` only as a diagnostic fallback.
+
+A server can remain connected even when an encoding stream is desynchronized, so absence of a socket exception does not prove the encoded rectangles are valid.
+
+## RRE disconnects the viewer
+
+A correct RRE rectangle must contain RRE-formatted payload bytes. If an encoder fallback emits Raw bytes, the rectangle header must advertise Raw too. Current PyVNCServer keeps the payload/header pair consistent and bounds RRE work with tiling.
+
+## Server refuses to start on `0.0.0.0`
+
+This is deliberate when no authentication is configured.
+
+Choose one:
+
+```toml
+[security]
+password = "vncpass"
+```
+
+or bind to loopback, or explicitly opt into unsafe no-auth operation:
+
+```toml
+[security]
+allow_insecure_no_auth = true
+```
+
+## Browser connection rejected
+
+For noVNC/WebSocket:
+
+- enable WebSocket support;
+- add the exact browser Origin to `allowed_origins`;
+- verify binary WebSocket transport;
+- check reverse-proxy Upgrade headers;
+- ensure message limits are not below the client workload.
+
+## Capture backend fails
+
+Use:
+
+```toml
+[server]
+capture_backend = "auto"
+```
+
+The capture layer can probe/fall back between available backends. If DXCam fails, confirm the optional performance dependencies are installed and retry with MSS to isolate the GPU/Desktop Duplication path.
+
+## Performance is unexpectedly low
+
+Check:
+
+- which capture backend was selected;
+- whether full-frame updates are being sent instead of incremental regions;
+- selected encoding and compression level;
+- client update request cadence;
+- network profile detection;
+- whether `encoding_threads` is oversubscribing the CPU.
+
+Use the scripts described in [Performance](performance.md) to separate capture and encoding costs.
From 1aa3c86610bdaed013eecdc2ed9d30d073311edb Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:33:51 +0200
Subject: [PATCH 15/20] docs: add development guide
---
docs/development.md | 81 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
create mode 100644 docs/development.md
diff --git a/docs/development.md b/docs/development.md
new file mode 100644
index 0000000..0b622fe
--- /dev/null
+++ b/docs/development.md
@@ -0,0 +1,81 @@
+# Development
+
+## Environment
+
+```bash
+python -m venv .venv
+```
+
+=== "Windows PowerShell"
+
+ ```powershell
+ .\.venv\Scripts\Activate.ps1
+ python -m pip install -U pip
+ python -m pip install -e ".[dev,performance]"
+ ```
+
+=== "Linux / macOS"
+
+ ```bash
+ source .venv/bin/activate
+ python -m pip install -U pip
+ python -m pip install -e ".[dev]"
+ ```
+
+## Tests
+
+```bash
+python -m compileall -q src tests
+python -m pytest -q
+```
+
+The suite covers protocol negotiation, encoders, WebSocket framing, configuration validation, security limits, capture producer behavior and end-to-end RFB communication.
+
+## Package build
+
+```bash
+python -m pip install build twine
+python -m build
+python -m twine check dist/*
+```
+
+## Documentation locally
+
+```bash
+python -m pip install -r requirements-docs.txt
+mkdocs serve
+```
+
+Open `http://127.0.0.1:8000/PyVNCServer/` or use the URL printed by MkDocs.
+
+Strict production build:
+
+```bash
+mkdocs build --strict
+```
+
+## GitHub Pages
+
+`.github/workflows/docs.yml` performs a strict documentation build on pull requests. On a push to `main`, it also uploads the generated site as a GitHub Pages artifact and deploys it through the `github-pages` environment.
+
+The repository Pages source must be configured for **GitHub Actions** in repository settings.
+
+## Encoder changes
+
+When changing an encoder:
+
+- test the byte-level format, not only compression ratio;
+- keep rectangle header encoding IDs consistent with fallback payloads;
+- test multiple consecutive rectangles when the codec has stream state;
+- test in-session encoding switches and fresh connections;
+- run UltraVNC compatibility checks for Tight/RRE/Hextile/Zlib/ZRLE.
+
+## Performance changes
+
+Run at least the encoder benchmark for codec work:
+
+```bash
+PYTHONPATH=src python benchmarks/benchmark_encoders.py
+```
+
+For capture changes, benchmark on the target desktop platform; headless CI cannot meaningfully validate desktop-capture latency.
From 5586915d9d33d42624efdb107c1406d7323205c7 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:33:59 +0200
Subject: [PATCH 16/20] docs: add documentation logo
---
docs/assets/images/logo.svg | 13 +++++++++++++
1 file changed, 13 insertions(+)
create mode 100644 docs/assets/images/logo.svg
diff --git a/docs/assets/images/logo.svg b/docs/assets/images/logo.svg
new file mode 100644
index 0000000..f3a8ce0
--- /dev/null
+++ b/docs/assets/images/logo.svg
@@ -0,0 +1,13 @@
+
From b3c33b77693f88612bec7388248856a96c3ef1d5 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:34:10 +0200
Subject: [PATCH 17/20] docs: add custom documentation styling
---
docs/assets/stylesheets/extra.css | 82 +++++++++++++++++++++++++++++++
1 file changed, 82 insertions(+)
create mode 100644 docs/assets/stylesheets/extra.css
diff --git a/docs/assets/stylesheets/extra.css b/docs/assets/stylesheets/extra.css
new file mode 100644
index 0000000..6ccf9d1
--- /dev/null
+++ b/docs/assets/stylesheets/extra.css
@@ -0,0 +1,82 @@
+:root {
+ --pvncs-accent: #22d3ee;
+ --pvncs-violet: #8b5cf6;
+ --pvncs-panel: rgba(99, 102, 241, 0.08);
+}
+
+.md-header {
+ backdrop-filter: saturate(140%) blur(12px);
+}
+
+.md-main__inner {
+ margin-top: 1.2rem;
+}
+
+.hero {
+ position: relative;
+ overflow: hidden;
+ padding: 3.2rem 2.4rem;
+ margin: .8rem 0 2rem;
+ border: 1px solid color-mix(in srgb, var(--pvncs-violet) 34%, transparent);
+ border-radius: 1.4rem;
+ background:
+ radial-gradient(circle at 85% 15%, rgba(34, 211, 238, .20), transparent 28rem),
+ radial-gradient(circle at 15% 85%, rgba(139, 92, 246, .20), transparent 26rem),
+ linear-gradient(135deg, rgba(99, 102, 241, .10), rgba(6, 182, 212, .04));
+}
+
+.hero::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ opacity: .18;
+ background-image: linear-gradient(rgba(255,255,255,.08) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.08) 1px, transparent 1px);
+ background-size: 28px 28px;
+ mask-image: linear-gradient(to bottom right, black, transparent 70%);
+}
+
+.hero > * { position: relative; z-index: 1; }
+.hero h1 { margin: 0 0 .55rem; font-size: clamp(2.3rem, 6vw, 4.6rem); letter-spacing: -.045em; line-height: .98; }
+.hero .lead { max-width: 50rem; font-size: 1.12rem; opacity: .9; }
+.hero .eyebrow { margin-bottom: 1rem; font-size: .76rem; font-weight: 700; text-transform: uppercase; letter-spacing: .16em; color: var(--pvncs-accent); }
+.hero-actions { display: flex; flex-wrap: wrap; gap: .7rem; margin-top: 1.5rem; }
+.hero-actions .md-button { border-radius: .65rem; }
+
+.status-row { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1.35rem; }
+.status-pill { padding: .3rem .65rem; border: 1px solid rgba(125, 211, 252, .28); border-radius: 999px; background: rgba(15, 23, 42, .18); font-size: .75rem; font-weight: 600; }
+
+.feature-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: .9rem;
+ margin: 1.4rem 0 2.2rem;
+}
+
+.feature-card {
+ padding: 1.15rem 1.15rem 1rem;
+ border: 1px solid var(--md-default-fg-color--lightest);
+ border-radius: .9rem;
+ background: var(--md-default-bg-color);
+ box-shadow: 0 12px 32px rgba(15, 23, 42, .04);
+}
+.feature-card h3 { margin: .1rem 0 .45rem; font-size: 1rem; }
+.feature-card p { margin: 0; font-size: .86rem; opacity: .82; }
+.feature-icon { font-size: 1.35rem; }
+
+.quick-command {
+ border-radius: .9rem;
+ overflow: hidden;
+}
+
+.docs-table td:first-child { white-space: nowrap; font-weight: 600; }
+
+@media (max-width: 900px) {
+ .feature-grid { grid-template-columns: 1fr 1fr; }
+ .hero { padding: 2.2rem 1.4rem; }
+}
+
+@media (max-width: 620px) {
+ .feature-grid { grid-template-columns: 1fr; }
+ .hero h1 { font-size: 2.55rem; }
+}
From e74adccc4e5da28e2c8715f71c17661a267a51db Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:35:13 +0200
Subject: [PATCH 18/20] docs: document noVNC submodule clone flow
---
docs/getting-started.md | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 3ab3ee9..0db4e6a 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -12,10 +12,12 @@ Windows is the primary platform for the optional DXCam/DXGI fast capture path. M
## Install from source
+PyVNCServer tracks noVNC as a Git submodule, so clone recursively if you want the bundled browser client assets too.
+
=== "Windows PowerShell"
```powershell
- git clone https://github.com/xulek/PyVNCServer.git
+ git clone --recurse-submodules https://github.com/xulek/PyVNCServer.git
cd PyVNCServer
py -m venv .venv
.\.venv\Scripts\Activate.ps1
@@ -26,7 +28,7 @@ Windows is the primary platform for the optional DXCam/DXGI fast capture path. M
=== "Linux / macOS"
```bash
- git clone https://github.com/xulek/PyVNCServer.git
+ git clone --recurse-submodules https://github.com/xulek/PyVNCServer.git
cd PyVNCServer
python3 -m venv .venv
source .venv/bin/activate
@@ -34,6 +36,12 @@ Windows is the primary platform for the optional DXCam/DXGI fast capture path. M
python -m pip install -e .
```
+If you already cloned the repository without submodules:
+
+```bash
+git submodule update --init --recursive
+```
+
### Optional extras
```bash
From 7cdd32d4769a55db85761058a84629af065d2e76 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:35:31 +0200
Subject: [PATCH 19/20] docs: document noVNC submodule integration
---
docs/novnc.md | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/docs/novnc.md b/docs/novnc.md
index 9ca8076..820ca7f 100644
--- a/docs/novnc.md
+++ b/docs/novnc.md
@@ -35,7 +35,19 @@ The WebSocket implementation enforces or bounds:
## noVNC frontend
-The repository contains browser client assets under `web/`. Serve the frontend with a normal static HTTP server or reverse proxy and point it at the PyVNCServer WebSocket endpoint.
+noVNC is tracked as the `web/noVNC` Git submodule. Clone recursively when you want the browser client assets:
+
+```bash
+git clone --recurse-submodules https://github.com/xulek/PyVNCServer.git
+```
+
+For an existing checkout:
+
+```bash
+git submodule update --init --recursive
+```
+
+Serve the noVNC frontend with a normal static HTTP server or reverse proxy and point it at the PyVNCServer WebSocket endpoint.
A typical topology is:
@@ -65,7 +77,8 @@ Keep `max_message_bytes >= max_payload_bytes`; configuration validation rejects
If the TCP viewer works but noVNC does not:
1. verify `enable_websocket = true`;
-2. inspect the browser console for Origin/Upgrade failures;
-3. ensure the Origin is explicitly allowlisted;
-4. confirm the frontend is using binary WebSocket RFB transport;
-5. if TLS terminates at a reverse proxy, verify the proxy forwards Upgrade/Connection headers.
+2. verify the `web/noVNC` submodule is initialized;
+3. inspect the browser console for Origin/Upgrade failures;
+4. ensure the Origin is explicitly allowlisted;
+5. confirm the frontend is using binary WebSocket RFB transport;
+6. if TLS terminates at a reverse proxy, verify the proxy forwards Upgrade/Connection headers.
From 231e9af1a67dc17f1c31af1c0502253d0114aa32 Mon Sep 17 00:00:00 2001
From: xulek
Date: Sun, 6 Sep 2026 22:37:19 +0200
Subject: [PATCH 20/20] docs: link Pages and align README with repository
---
README.md | 43 ++++++++++++++++++++++++++++++++-----------
1 file changed, 32 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 80c4914..e9d0195 100644
--- a/README.md
+++ b/README.md
@@ -13,10 +13,13 @@ RFB 3.8 · UltraVNC interoperability · Tight / ZRLE / Hextile / Zlib · WebSock
+
**PyVNCServer** is a Python implementation of a VNC/RFB server focused on protocol correctness, practical client interoperability, low-latency desktop streaming and a security-conscious default configuration.
+[**Documentation**](https://xulek.github.io/PyVNCServer/) · [**GitHub**](https://github.com/xulek/PyVNCServer)
+
---
@@ -43,6 +46,8 @@ Highlights:
- Metrics, health checks and session instrumentation.
- Test suite covering protocol, encoders, WebSocket handling, security, capture and end-to-end RFB communication.
+For the complete guides, configuration reference, architecture and troubleshooting documentation, see **https://xulek.github.io/PyVNCServer/**.
+
---
## Client compatibility
@@ -51,7 +56,7 @@ Highlights:
| --- | --- | --- |
| **UltraVNC Viewer** | ✅ Tested | Tight, RRE, Hextile, Zlib, ZRLE and Raw negotiation paths tested during 3.2.1 work |
| **Standard RFB 3.8 clients** | ✅ Supported | Client must advertise at least one encoding implemented by the server |
-| **noVNC / browser clients** | ✅ Supported transport | Enable WebSocket and configure an origin allowlist; serve the noVNC frontend separately |
+| **noVNC / browser clients** | ✅ Supported transport | noVNC is tracked as `web/noVNC`; enable WebSocket and configure an Origin allowlist |
| **Raw TCP VNC** | ✅ Supported | Default transport |
| **TLS-wrapped VNC** | ✅ Optional | Requires certificate and private key configuration |
@@ -70,13 +75,21 @@ Highlights:
### Install from source
+Clone recursively to initialize the bundled noVNC submodule:
+
```bash
-git clone
+git clone --recurse-submodules https://github.com/xulek/PyVNCServer.git
cd PyVNCServer
python -m pip install -U pip
python -m pip install -e .
```
+If you already cloned the repository without submodules:
+
+```bash
+git submodule update --init --recursive
+```
+
For the faster optional capture stack:
```bash
@@ -156,6 +169,8 @@ tight_disable_for_ultravnc = false
Use these as troubleshooting switches rather than enabling them automatically for every client.
+More details: [UltraVNC documentation](https://xulek.github.io/PyVNCServer/ultravnc/).
+
---
## noVNC / WebSocket mode
@@ -182,7 +197,9 @@ The WebSocket implementation accepts the binary transport used by noVNC and enfo
- bounded handshake size,
- preservation of bytes pipelined after the HTTP upgrade request.
-The repository contains noVNC assets under `web/noVNC/`. PyVNCServer provides the VNC WebSocket transport; the static noVNC frontend should be served with your preferred HTTP server or reverse proxy.
+noVNC is tracked as the `web/noVNC` Git submodule. PyVNCServer provides the VNC WebSocket transport; serve the noVNC frontend with your preferred HTTP server or reverse proxy.
+
+More details: [noVNC & WebSocket documentation](https://xulek.github.io/PyVNCServer/novnc/).
---
@@ -202,6 +219,8 @@ The repository contains noVNC assets under `web/noVNC/`. PyVNCServer provides th
The server negotiates only encodings advertised by the client and can select different encodings for different update regions.
+See the [encoding reference](https://xulek.github.io/PyVNCServer/encodings/) for protocol and fallback details.
+
---
## Capture pipeline
@@ -280,6 +299,8 @@ auth_backoff_max_seconds = 2.0
Additional protections include connection admission limits, handshake timeouts and WebSocket payload limits.
+See the full [security guide](https://xulek.github.io/PyVNCServer/security/).
+
---
## Configuration
@@ -332,6 +353,8 @@ max_client_cut_text = 16777216
`network_profile_override = "auto"` allows the server to classify the connection instead of forcing LAN tuning for every client.
+The complete option reference is available in the [configuration documentation](https://xulek.github.io/PyVNCServer/configuration/).
+
---
## Architecture
@@ -385,6 +408,8 @@ src/vnc_lib/ compatibility implementation layer
`vnc_lib` is retained for compatibility with existing imports while the public package surface is exposed under `pyvncserver`.
+More detail: [architecture documentation](https://xulek.github.io/PyVNCServer/architecture/).
+
---
## Performance design
@@ -469,14 +494,12 @@ Capture benchmarks depend on the host desktop environment and should be interpre
## GitHub Actions
-The repository ships with a complete CI/release setup:
+The repository currently contains these workflow definitions:
| Workflow | Purpose |
| --- | --- |
-| `CI` | Tests Python 3.11–3.13, includes a Windows test job, checks bytecode compilation and validates package builds |
-| `CodeQL` | Python static security analysis on pushes, pull requests and a weekly schedule |
-| `Release` | Validates the tag/version, builds wheel + sdist, creates a GitHub Release and optionally publishes to PyPI |
-| `Benchmarks` | Manual encoder benchmark run with downloadable results |
+| `CI` | Tests Python 3.11–3.13, includes a Windows test job, checks bytecode compilation, coverage and package builds |
+| `Documentation` | Strictly builds MkDocs documentation on PRs and deploys GitHub Pages from `main` |
---
@@ -524,7 +547,6 @@ python -m twine check dist/*
---
-
## Known limitations / roadmap
- Native DXGI dirty/move rectangle harvesting is not implemented yet.
@@ -533,11 +555,10 @@ python -m twine check dist/*
- Browser use requires a separately served noVNC frontend/static HTTP endpoint.
- `vnc_lib` remains as a compatibility layer and can be progressively folded into the `pyvncserver` package structure.
-
---