Commit Graph
21 Commits
Author SHA1 Message Date
ksmithandClaude Sonnet 5 1506d87719 Make username/password connect prompts hard to miss (issue #22)
The inline prompt bar previously had no explicit styling and rendered
in the same color as the rest of the tab; a tab showing the prompt in
the background had no indication anything needed attention. The bar
now uses a solid QPalette::Highlight fill with HighlightedText for the
label and a hand-drawn contrasting badge, and a background tab gets a
"(Needs input)" title suffix plus a distinct tab-bar color.

A first pass at the tab color (#6a1b9a) was reported unreadable in
dark mode; replaced with #ab47bc, tuned to match the visibility of the
existing connection-state colors.

Bump version to v2026.9.16.6.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 09:18:43 -06:00
ksmithandClaude Sonnet 5 b3cedcfa48 Fix connect-time username prompt never reaching SSH/RDP authentication
The username entered at the connect-time prompt (added for issue #21)
was updating SessionTab's own in-memory Profile copy, but
SshSessionBackend/RdpSessionBackend are constructed with -- and only
ever read from -- their own separate Profile copy on a worker thread,
which never saw that edit. Authentication was still built from the
original (blank) username regardless of what was typed into the
prompt.

SessionConnectOptions gains a username field, populated by SessionTab
on every connect attempt and threaded through the same way password
already is; both backends now prefer options.username over
profile().username. Covered by a new SSH regression test using an
exact-match fixture host that only succeeds for a specific
user@host target.

Bump version to v2026.9.16.5.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 08:40:34 -06:00
ksmithandClaude Sonnet 5 0cdf930303 Fix connect-time username prompt being unreachable for SSH/RDP
validateProfileForConnect() still hard-failed with a blocking
QMessageBox for a blank SSH/RDP username, running before
requestConnectOptions() ever got a chance to prompt for it inline --
so the connect-time username prompt added for issue #21 was dead code
in practice; users just got told to go edit the profile instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 08:28:41 -06:00
ksmithandClaude Sonnet 5 776db5ec04 Allow blank usernames, asking for one at connect time instead
Closes #21. SSH and RDP profiles previously hard-required a username
to even save the profile; that validation is dropped, and
SessionTab::requestConnectOptions() now prompts for it at connect
time when blank, reusing the existing password-prompt bar in
unmasked mode -- the same pattern already used for a blank password.

VNC's username is trickier: most VNC servers never use one (plain VNC
Authentication and no-auth don't), only the two Apple auth schemes
(security types 30/33) do, and which auth method gets used isn't known
until mid-connection, after the server's security-type list has been
negotiated -- too late for the pre-connect prompt SSH/RDP uses. Adds a
new async request/response pair to SessionBackend, usernameRequested()
signal / provideUsername() slot, mirroring the existing SSH host-key-
confirmation pattern. VncSessionBackend pauses its state machine right
before computing an Apple-auth response if no username is available --
without consuming the already-buffered prime/host-key bytes, so
resuming re-parses them identically -- emits the request, and resumes
via provideUsername(). Cancelling (or submitting blank) fails the
connection cleanly instead of sending Apple auth an empty username.

The username is kept on the tab's in-memory profile copy for its
lifetime, not written back to the saved profile, matching how
passwords are already handled.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 08:01:57 -06:00
ksmithandClaude Sonnet 5 e80fe7d634 Fix Apple DH auth wire-order bug, add type 33 fallback, fix missing VNC password prompt
Live testing against a real macOS Screen Sharing server surfaced two
real bugs, independent of each other:

1. SessionTab::requestConnectOptions() never prompted for a password
   on VNC profiles (only SSH/RDP) -- every VNC connection went out
   with an empty password regardless of what the server needed. VNC
   now gets its own prompt; an empty password is allowed through
   (unlike RDP's hard requirement) since no-auth VNC servers exist and
   there's no way to know client-side before the security-type
   negotiation happens.

2. VncSessionBackend's Apple DH (type 30) response sent the client's
   public key before the encrypted credentials. Cross-checking against
   neatvnc's rfb-proto.h (an independent, authoritative reference: both
   the wire struct definitions and the full server-side verification
   code, matched field-by-field against this implementation) showed
   the correct order is credentials first, then public key -- exactly
   backwards from what was implemented. Fixed, with a new regression
   test that decrypts the credentials back out using the trailing
   public-key bytes to derive the shared secret, which would fail if
   the fields were swapped again.

Also adds security type 33 (RSA + AES, src/vnc_apple_rsa_auth.h) as a
fallback Apple auth scheme, sourced from the `asyncvnc` PyPI package.
Preference when multiple are offered: None > AppleDH(30) >
AppleRSA(33) > VNCAuth(2).

Neither scheme has been gotten working live yet against the specific
macOS Tahoe (26.6.2) server available for testing -- type 30's wire
format is now verified correct byte-for-byte against the independent
reference above, but the server still rejects it with a generic
"Authentication or authorization failure"; type 33 is rejected even
earlier, right after the initial host-key request. macOS Tahoe was
released after this assistant's knowledge cutoff, so there may be a
protocol or permission-model change specific to it that isn't
reflected in either reference. Documented as an open issue in
docs/PROGRESS.md rather than claimed as working.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-16 03:49:06 -06:00
ksmithandClaude Sonnet 5 9dd1af21d6 Add VNC remote cursor shape sync
Implements RFB's Cursor pseudo-encoding (RFC 6143 SS7.8.2, type -239):
a FramebufferUpdate rectangle carrying a cursor shape instead of
screen content (x/y are the hotspot, not position; width/height are
the cursor image size), decoded into an ARGB32 QImage using the
rectangle's RGB pixel data plus its opacity bitmask, then never
painted into the framebuffer. A 0x0 rectangle means "hide the
cursor" per spec.

VncDisplayWidget gains RdpDisplayWidget's setCursorImage/Hidden/
Default() + applyCursor() shape, reusing its own renderRect()/
effectiveRemoteSize() so cursor scaling works correctly in both the
scale-to-fit and actual-size display modes with no special-casing.
VNC never emits cursorReset() (RFB's Cursor pseudo-encoding has no
"reset to system default" signal, unlike RDP's SetDefault callback) --
setCursorDefault() exists for symmetry but is unused today.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 20:34:20 -06:00
ksmithandClaude Sonnet 5 3dd894407a Add VNC clipboard sync
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>
2026-09-15 20:31:03 -06:00
ksmithandClaude Sonnet 5 0b5454197c Add VNC-only scale-to-fit vs actual-size display toggle
VNC has no equivalent of RDP's MS-RDPEDISP to request a different
resolution from the guest, so a high-resolution remote desktop
previously always got shrunk to fit the window, making text
illegible. Adds a per-tab "Display Mode" choice (tab-bar right-click)
between the existing scale-to-fit behavior and a new actual-size mode
that renders the framebuffer at its native pixel size inside a
QScrollArea. Reuses VncDisplayWidget's existing scale-to-fit render
math unchanged -- it degenerates to an exact 1:1 mapping once the
widget is fixed to the remote's own size. Persisted like the terminal
theme preference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-15 18:13:43 -06:00
ksmithandClaude Sonnet 5 6da9dc6ca5 Add VNC support (Milestone 6, issue #3)
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>
2026-09-15 18:02:49 -06:00
ksmithandClaude Sonnet 5 92d8b62820 RDP: fix distorted text on HiDPI monitors, reduce resize glitches
The RDP session pipeline never accounted for display scale: it
requested a desktop canvas sized in Qt logical pixels (never
multiplied by devicePixelRatio()), and FreeRDP_DesktopScaleFactor/
DeviceScaleFactor were read but never actually set anywhere. On a
HiDPI monitor this meant the remote session rendered assuming a
96 DPI / 100% display, and the resulting canvas got stretched
locally — ClearType's subpixel hinting doesn't survive that kind of
resampling, producing distorted glyph shapes and color fringing
rather than plain blur.

RdpDisplayWidget now reports physical pixel dimensions and the real
devicePixelRatio (recomputed on resize and on screen changes, e.g.
dragging the window to a different-DPI monitor). RdpSessionBackend
maps that to the nearest FreeRDP-legal scale value ({100, 140, 180},
per MS-RDPEDISP and FreeRDP's own reference client) and sets it at
both connect time and on every dynamic resize, including the
FreeRDP_MonitorOverrideFlags required for the values to actually be
honored rather than silently ignored.

While testing this against real infrastructure, found and fixed two
related (pre-existing, not caused by this change) resize issues:
- A stale-frame race where the old frame could be drawn at the wrong
  scale for a moment after a resize, before a correctly-sized one
  arrives — now the frame is cleared during that transition instead.
- No debounce on outgoing resize requests — every single resize event
  fired an immediate request to the server, which can visibly
  contribute to host-side redraw glitches during rapid layout churn
  (e.g. right after connecting). Coalesced into one request per burst,
  plus an explicit refresh-rect request after each resize completes
  as a best-effort nudge for hosts that don't fully repaint on their
  own.

A separate, deeper issue was also found during testing (the remote
guest's actual resolution sometimes not changing despite the resize
channel reporting success) and is tracked separately, not fixed here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-14 21:16:41 -06:00
ksmithandClaude Sonnet 5 793fdd9366 RDP session fixes: correct keyboard scancodes, clipboard sync, cursor shapes
Fix RDP keyboard input using FreeRDP's authoritative X11-keycode-to-scancode
table instead of ad hoc bit math, which misread punctuation keys as unrelated
letter keys (e.g. apostrophe as B) because X11 keycode numbering only
coincidentally overlaps PC/AT scancodes.

Add bidirectional clipboard sync (CF_UNICODETEXT) over the cliprdr channel,
and RDP pointer/cursor shape sync so the local cursor reflects what the
remote OS wants displayed (resize handles, text I-beam, etc.) instead of
staying a static arrow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 14:53:03 -06:00
ksmithandClaude Sonnet 5 27cc3a3bb2 Session UX: inline password prompt and terminal font size controls
Replace the modal RDP/SSH password dialog with an inline prompt bar
embedded in the session tab instead of a separate popup window. Add
per-tab terminal font size controls (increase/decrease/reset/set
exact point size) via the tab context menu, with the chosen size
persisted across sessions.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 12:02:03 -06:00
ksmith eadcdd7f10 Milestone 8 UX: folder tree workflows, about dialog, and app icon polish 2026-03-03 20:07:41 -07:00
ksmith 36006bd4aa Milestone 5: deliver embedded RDP sessions and lifecycle hardening 2026-03-03 18:59:26 -07:00
ksmith 2b25f805cd Complete Milestone 4 interactive SSH session UX 2026-03-01 11:00:31 -07:00
ksmith 776ddc1a53 Integrate KodoTerm for SSH terminal sessions 2026-03-01 10:36:06 -07:00
ksmith c3369b8e48 Improve terminal theming, cursor UX, and size negotiation 2026-03-01 10:14:43 -07:00
ksmith 20ee48db32 Add ANSI color rendering and terminal themes 2026-03-01 10:08:04 -07:00
ksmith 2b4f498259 Allow direct terminal typing and collapsible session panels 2026-03-01 09:58:21 -07:00
ksmith 2ea712db36 Start Milestone 4 interactive SSH terminal and host-key flow 2026-03-01 09:50:03 -07:00
ksmith 6a4bcb75eb Add threaded session backend architecture with real SSH backend 2026-03-01 09:37:34 -07:00