RFB's ServerCutText/ClientCutText messages are much simpler than RDP's
CLIPRDR channel (no format-list/format-request negotiation -- text is
just sent directly in both directions), so ServerCutText now emits
remoteClipboardTextChanged instead of being discarded, and
VncSessionBackend overrides setClipboardText to send ClientCutText
immediately. Extends session_tab.cpp's clipboard-sync gate to cover
VNC alongside RDP; the QClipboard wiring itself was already
protocol-agnostic. Latin-1 only, per RFB's wire format -- no Unicode
clipboard extension is in scope.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Previously an unrecognized rectangle encoding in a FramebufferUpdate
aborted the connection with a generic message, and there was nothing
tying the set of encodings we announce via SetEncodings to the set we
actually know how to decode. Introduces kAnnouncedEncodings as the
single source of truth for both, converts the rectangle dispatch to a
switch keyed off it, and gives the (still intentionally fatal --
there's no safe way to skip an unknown-length payload) fallback a
message that identifies it as a protocol violation rather than "not
supported". Adds a regression test asserting every announced encoding
has a working dispatch case, so future encodings (Hextile/ZRLE/Tight/
Cursor) can't be added to the announced list without matching decode
support. Also replaces scattered inline magic numbers for RFB
message-type constants with named constants, in prep for the
clipboard/cursor/compression work that follows.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements RFB (RFC 6143) directly against QTcpSocket. No permissively
licensed VNC client library exists to vendor the way FreeRDP was for RDP:
LibVNCClient is GPLv2, gtk-vnc is LGPL but GTK-tied, and vendoring either
would force a licensing decision on the whole (MIT) project. This is
from-scratch protocol code instead, threaded like SshSessionBackend (a
QObject on its own QThread driven by Qt's own async socket signals)
rather than RdpSessionBackend's manual worker-thread/blocking-loop
pattern, since QTcpSocket is already async.
Scope, matching SessionTab's existing SSH/RDP dispatch pattern
(session_backend_factory.cpp, session_tab.cpp's widget construction and
signal wiring) and VncDisplayWidget mirroring RdpDisplayWidget's
scale-to-fit rendering:
- Protocol handshake: RFB 3.3/3.7/3.8 negotiated explicitly (the
SecurityResult message only exists in 3.8; pre-3.8 servers signal auth
failure by closing the socket, which the disconnect handler accounts
for)
- VNC Authentication (DES challenge-response, via OpenSSL's classic DES
API) and no-auth security types
- Raw + CopyRect framebuffer decoding into a persistent QImage, requesting
a fixed 32bpp format whose byte layout matches QImage::Format_RGB32
directly (same zero-conversion trick RdpSessionBackend uses for
FreeRDP's GDI buffer)
- Keyboard (Qt key -> X11 keysym, including the Unicode-beyond-Latin-1
keysym convention) and mouse/wheel input forwarding
Explicit non-goals for this pass (see docs/PROGRESS.md for the full
list): Apple's Screen Sharing auth (so this can't yet reach macOS's
built-in VNC server), compression encodings beyond Raw/CopyRect, dynamic
resize, remote cursor shape sync, clipboard sync.
19 unit tests (tests/test_vnc_session_backend.cpp): pure-function
coverage (DES key prep verified against an independently documented test
vector for password "COW", X11 keysym mapping, socket-error mapping) plus
state-machine coverage against a scripted in-process fake RFB server
covering all three protocol-version handshake shapes, auth success/
failure, unsupported security types, and pixel-accurate Raw decoding.
That harness caught a real re-entrancy bug: QAbstractSocket::abort()
synchronously re-emits disconnected() before returning, so
failConnection() calling it was silently letting a second, generic
disconnected-socket handler overwrite an already-correct, specific error
message.
Also verified live against a real, independently implemented VNC server
(TightVNC on Windows): connect with VNC Authentication, correct
framebuffer dimensions and pixel data, clean disconnect, reconnect.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>