-**PyVNCServer 3.2.1** · Python 3.11+ · RFB 3.8
+**PyVNCServer 3.2.1** · Python 3.11+ · RFB 3.8 · [Documentation](https://xulek.github.io/PyVNCServer/)
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.
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.
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 @@
+
+
+
RFB 3.8 · Python 3.11+ · UltraVNC · noVNC
+
+# Remote framebuffer, under your control.
+
+
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.
+
+
+
+
+ RFB 3.8
+ UltraVNC tested
+ Tight · ZRLE · Hextile · Zlib
+ WebSocket / noVNC
+ Safe defaults
+
+
+
+
+
+
+
+⚡
+### 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) |
diff --git a/docs/novnc.md b/docs/novnc.md
new file mode 100644
index 0000000..820ca7f
--- /dev/null
+++ b/docs/novnc.md
@@ -0,0 +1,84 @@
+# 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
+
+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:
+
+```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. 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.
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.
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.
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.
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.
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..7b0f9f7
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,92 @@
+site_name: PyVNCServer
+site_description: Modern, high-performance VNC / RFB 3.8 server written in Python
+site_url: https://xulek.github.io/PyVNCServer/
+repo_url: https://github.com/xulek/PyVNCServer
+repo_name: xulek/PyVNCServer
+edit_uri: edit/main/docs/
+copyright: Copyright © PyVNCServer contributors
+
+nav:
+ - Home: index.md
+ - Getting started:
+ - Installation & quick start: getting-started.md
+ - Configuration: configuration.md
+ - Client guides:
+ - UltraVNC: ultravnc.md
+ - noVNC & WebSocket: novnc.md
+ - Troubleshooting: troubleshooting.md
+ - Reference:
+ - Encodings: encodings.md
+ - Security: security.md
+ - CLI & Python API: api.md
+ - Architecture: architecture.md
+ - Performance: performance.md
+ - Development: development.md
+
+theme:
+ name: material
+ language: en
+ logo: assets/images/logo.svg
+ favicon: assets/images/logo.svg
+ icon:
+ repo: fontawesome/brands/github
+ features:
+ - navigation.tabs
+ - navigation.sections
+ - navigation.top
+ - navigation.indexes
+ - navigation.footer
+ - content.code.copy
+ - content.code.annotate
+ - content.tabs.link
+ - search.highlight
+ - search.suggest
+ palette:
+ - media: "(prefers-color-scheme: light)"
+ scheme: default
+ primary: indigo
+ accent: cyan
+ toggle:
+ icon: material/weather-night
+ name: Switch to dark mode
+ - media: "(prefers-color-scheme: dark)"
+ scheme: slate
+ primary: indigo
+ accent: cyan
+ toggle:
+ icon: material/weather-sunny
+ name: Switch to light mode
+
+plugins:
+ - search
+
+markdown_extensions:
+ - admonition
+ - attr_list
+ - md_in_html
+ - tables
+ - toc:
+ permalink: true
+ - pymdownx.details
+ - pymdownx.highlight:
+ anchor_linenums: true
+ - pymdownx.inlinehilite
+ - pymdownx.snippets
+ - pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format
+ - pymdownx.tabbed:
+ alternate_style: true
+ - pymdownx.tasklist:
+ custom_checkbox: true
+
+extra_css:
+ - assets/stylesheets/extra.css
+
+extra:
+ social:
+ - icon: fontawesome/brands/github
+ link: https://github.com/xulek/PyVNCServer
+ name: PyVNCServer on GitHub
diff --git a/requirements-docs.txt b/requirements-docs.txt
new file mode 100644
index 0000000..455b696
--- /dev/null
+++ b/requirements-docs.txt
@@ -0,0 +1,2 @@
+mkdocs>=1.6,<2
+mkdocs-material>=9.6,<10