Internal
Public Access
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>
24 KiB
24 KiB
OrbitHub Progress
Milestone 0 - Restart in C++/Qt Widgets
Status: Completed
Delivered:
- Fresh C++17/Qt6 Widgets scaffold with CMake
ProfilesWindow(QMainWindow) with search, profile list, and New/Edit/Delete controls- Double-click in Profiles opens a
SessionWindow SessionWindow(QMainWindow) withQTabWidget- Placeholder tab content showing
OrbitHub Native Surface main.cppwiring for application startup- Cross-platform build command guide in
docs/BUILDING.md
Git:
- Tag:
v0-m0-done
Milestone 1 - Storage and CRUD
Status: Completed
Delivered:
- SQLite integration via Qt SQL (
QSQLITE) - Persistent profile database bootstrap (
profilestable) - Profiles CRUD (New / Edit / Delete) in
ProfilesWindow - Search-backed profile listing from storage
- Double-click connect opens
SessionWindowtab with selected profile name
Git:
- Tag:
v0-m1-done
Milestone 2 - Profile Details and Connect Lifecycle
Status: Completed
Delivered:
- SQLite schema migration for profile details (
host,port,username,protocol,auth_mode) - New
ProfileDialogform for New/Edit profile workflows - Profiles list now shows endpoint metadata and supports search by name or host
- Connect now loads complete profile details into
SessionWindow - Session tab lifecycle status updates (
Connecting,Connected,Failed) via non-blocking timer flow
Git:
- Tag:
v0-m2-done
Milestone 3 - Real SSH Backend and Session Controls
Status: Completed
Delivered:
- Backend architecture introduced (
SessionBackend+ protocol-specific implementations) - Worker-thread backend execution for connection lifecycle operations
- Real SSH process backend (
ssh) with connect/disconnect/reconnect - Unsupported protocol backend with explicit not-implemented messaging (RDP/VNC)
- Session tab controls:
Connect,Disconnect,Reconnect,Copy Error - Connect-time credential flow (password prompt / private-key path selection)
- Session event log pane with timestamps and user-friendly error mapping
- SQLite profile schema migration for
private_key_pathandknown_hosts_policy
Git:
- Tag:
v0-m3-done
Milestone 4 - Interactive SSH Session UX
Status: Completed
Delivered:
- Embedded interactive SSH terminal using
KodoTerm+ vendoredlibvterm - Native in-terminal typing for SSH sessions (no separate input box)
- ANSI/color rendering with selectable terminal themes (
Dark,Light,Solarized Dark) - Cross-platform SSH auth path improvements (
ssh-askpasshandling and host-key policy wiring) - Session UX simplification: auto-connect on tab open, disconnect on tab close
- Tab-state indicators via tab color and state suffix (
Connecting,Connected,Disconnected,Failed) - Right-click tab menu for
Disconnect,Reconnect,Theme, andClear - Collapsible events panel retained as primary diagnostics surface; inline detail/status banners removed
- Terminal behavior polish: better fixed-width font selection, cursor visibility, backspace handling, and terminal-size negotiation stability
Git:
- Tag:
v0-m4-done
Milestone 5 - RDP Fully Working
Status: Completed
Delivered:
- Added
RdpSessionBackendand wired protocol selection soRDPno longer routes to unsupported backend - Pivoted RDP design to embedded-only integration (no external RDP process launches)
- Implemented embedded FreeRDP client thread with connect/disconnect lifecycle and event-loop handling
- Added in-window
RdpDisplayWidgetrendering surface with frame updates from FreeRDP GDI - Wired direct keyboard/mouse input from the embedded RDP surface to the backend
- Added RDP connect-time password prompt flow and settings wiring (host/port/user/password, desktop size)
- Added explicit profile
Domainsupport for RDP auth (withDOMAIN\usernamefallback parsing) - Updated session tab/context-menu behavior so terminal-only actions are hidden on RDP tabs
- Implemented dynamic in-session RDP resolution renegotiation from viewport resize events
- Enabled minimal FreeRDP client-channel build (
drdynvc+disp) and channel loading for runtime resize support - Added RDP profile-level security mode and performance profile options, wired into FreeRDP connection settings
- Hardened RDP lifecycle handling for disconnect/reconnect/abort flows to avoid false failure states on user-initiated stops
- Expanded RDP error/disconnect diagnostics with richer FreeRDP code mapping and raw disconnect detail events
- Pulled FreeRDP source for integration planning and API review
Git:
- Tag:
v0-m5-done
Milestone 6 - VNC Working (initial scope)
Status: Completed (initial scope; see gaps below)
Delivered:
VncSessionBackend: an original RFB (RFC 6143) client implementation againstQTcpSocket-- 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), so this is from-scratch protocol code, threaded likeSshSessionBackend(aQObjecton its ownQThreaddriven by Qt's own async socket signals) rather thanRdpSessionBackend's manual worker-thread/blocking-loop pattern- Full connect/disconnect/reconnect lifecycle, RFB protocol-version negotiation (3.3/3.7/3.8 handshake differences handled explicitly), VNC Authentication (DES challenge-response, using OpenSSL's classic DES API) and no-auth security types, Raw + CopyRect framebuffer decoding, keyboard (Qt key -> X11 keysym mapping) and mouse/wheel input forwarding
VncDisplayWidgetmirroringRdpDisplayWidget's scale-to-fit rendering and input-forwarding shape- 45 unit tests (
tests/test_vnc_session_backend.cpp): pure-function coverage (DES key prep verified against an independently documented test vector, keysym mapping, socket-error mapping) plus state-machine coverage against a scripted in-process fake RFB server (all three protocol-version handshake shapes, auth success/failure, unsupported security types, pixel-accurate Raw/Hextile/ZRLE/Tight decoding including a ZRLE zlib-stream-persistence test across two separateFramebufferUpdatemessages, a Tight stream-reset-flag test, cursor/ clipboard round trips, both Apple auth schemes) -- caught and fixed a real re-entrancy bug (abort()synchronously re-firingdisconnected()mid-failConnection(), silently overwriting a specific error with a generic one) and a real Apple-DH wire-order bug (see below) - 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, live cursor-shape and clipboard-send checks. That particular server consistently chose Raw for actual framebuffer content regardless of which compression encodings were announced, so Hextile/ZRLE/Tight's real-world decode path isn't independently confirmed live -- the unit tests are the primary correctness evidence for those
- Per-tab VNC-only display mode toggle (tab-bar right-click ->
Display Mode):Scale to Fit(default, matches RDP's behavior) orActual Size (Scrollbars)-- renders the remote framebuffer at its native pixel size inside aQScrollAreaso text isn't shrunk, at the cost of needing to scroll to see the whole screen. ReusesVncDisplayWidget::renderRect()'s existing scale-to-fit math unchanged: it degenerates to an exact 1:1 mapping once the widget's own bounds are fixed to the remote's size, so no separate rendering path was needed. Persisted across sessions like the terminal theme preference. - Fixed a real, separate bug found during Apple-auth live testing:
SessionTab::requestConnectOptions()never prompted for a password on VNC profiles at all (only SSH/RDP), so every VNC connection went out with an empty password regardless of what the server needed. VNC now gets its own prompt, with an empty password allowed through (unlike RDP's hard requirement) since some VNC servers are no-auth and there's no way to know that before the server's security-type negotiation - Profiles can now leave the username blank for every protocol (issue
#21): SSH/RDP previously hard-required one at profile-save time; that
validation is gone, and
SessionTabnow asks for it at connect time instead (reusing the existing password-prompt bar in unmasked mode), same as it already does for a blank password. VNC's username is only ever asked for if the server's negotiated auth method actually needs one -- plain VNC Authentication and no-auth never do, only the two Apple schemes (30/33) do -- which happens mid-connection, after the backend has already picked a security type, not before connecting like SSH/RDP. This needed a new async request/response pair onSessionBackend(usernameRequested()/provideUsername(), mirroring the existing SSH host-key-confirmation pattern):VncSessionBackendpauses its state machine mid-parse (without consuming the already-buffered response bytes) and emits the request, resuming onceSessionTabanswers; cancelling fails the connection cleanly rather than sending Apple auth a blank username. The value is kept on the tab's in-memory profile copy for its lifetime, not written back to the saved profile - The blank-username relaxation above initially only covered
ProfileDialog's own save-time validation;ProfileRepository:: isProfileValid()had the identical "username required for SSH/RDP" check independently, called directly byinsertProfile()/updateProfile()-- which is exactly the path mRemoteNG import uses (it buildsProfileobjects and inserts them directly, never going through the dialog), so importing any SSH/RDP entry without a username still failed outright until this second check was found and removed too - A third, independent username check was still live even after the two
above were removed:
SessionTab::validateProfileForConnect()(run at the very top ofconnectSession()/reconnectSession(), beforerequestConnectOptions()ever gets a chance to run its async prompt) had its own hard-fail "SSH/RDP username is required"QMessageBox, so a blank-username profile still couldn't connect at all -- it just told the user to go edit the profile instead of ever prompting inline. Removed; connect-time prompting is now the only username gate for SSH/RDP - Even with the three checks above gone, a username entered at the
connect-time prompt still never actually reached SSH or RDP
authentication:
SshSessionBackend/RdpSessionBackendare constructed with their ownProfilecopy up front (moved to a worker thread) and readprofile().usernamedirectly, which never seesSessionTab's later edit to its own in-memory profile once the user answers the prompt.SessionConnectOptions(which already carriespasswordthe same way) gained ausernamefield, populated bySessionTabfrom its profile copy on every connect attempt; both backends now preferoptions.usernameoverprofile().usernamewhen building the actual connect target/auth call. Covered by a new SSH regression test (tests/fixtures/fake_ssh.sh'srequireuserhost only accepts an exactprompted-user@requireusertarget, so the test fails unless the option, not the stale profile copy, is actually used) -- RDP has no equivalent fake-server test harness, so that side relies on mirroring the already-testedm_activeOptions.passwordpattern exactly - Issue #22: the inline username/password prompt bar used to just be a
plain
QWidgetwithsetAutoFillBackground(true)and no explicit color, which meant it rendered in the same color as everything else around it and was easy to miss -- especially on a tab that wasn't the active one, where there was previously no indication anything needed attention at all. Now uses a solidQPalette::Highlightfill withQPalette::HighlightedTextfor the label (the OS theme's own guaranteed-contrasting pair, so it stays correct under both light and dark themes without a hardcoded color) plus a hand-drawn "?" badge (not a themedQStyleicon, whose own colors are outside our control and could land close in hue to the bar's background); a background tab showing the prompt gets its title suffixed "(Needs input)" and its tab-bar text colored distinctly from the four connection-state colors. A first pass at the tab color (#6a1b9a) was reported unreadable in dark mode -- its perceived luminance was well below the four existing state colors -- and was replaced with#ab47bc, tuned to roughly match their visibility - Robustness fix: an unrecognized
FramebufferUpdaterectangle encoding used to abort the connection generically;kAnnouncedEncodingsis now the single source of truth for whatSetEncodingsannounces and what the rectangle-dispatchswitchcan decode, with a regression test pinning that every announced encoding has a working case - Clipboard sync (
ServerCutText/ClientCutText, Latin-1 only -- no Unicode extension) in both directions - Remote cursor shape sync via RFB's Cursor pseudo-encoding, mirroring
RdpDisplayWidget's cursor handling inVncDisplayWidget - Hextile, ZRLE, and Tight compression encodings, in addition to Raw +
CopyRect -- meaningfully reduces bandwidth over slower links versus Raw
alone; Tight in particular is the encoding most real VNC servers prefer
when the client offers it. Pure tile/pixel decode logic (including
Tight's three filters -- Copy, Palette, Gradient) lives in
src/vnc_pixel_codecs.h/.cpp, kept separate from the wire-sequencing state machine so it's unit-testable without a socket. ZRLE and Tight's Basic mode share the same "persistent zlib stream(s), decompress in-memory, decode synchronously" approach (Tight has 4 independent streams selected per-rectangle, individually reset via the compression-control byte's low 4 bits). Tight's JPEG sub-mode decodes via libjpeg-turbo directly (find_package(JPEG REQUIRED)->JPEG::JPEG), notQImage's own JPEG plugin, to avoid a packaging-dependent runtime failure mode. ZRLE/Tight linkZLIB::ZLIB(found via a fresh top-levelfind_package(ZLIB REQUIRED), independent of whether vendored FreeRDP's own internal zlib usage stays enabled) - Apple Screen Sharing authentication: two schemes, both undocumented by
Apple. Security type 30 (Diffie-Hellman + AES,
src/vnc_apple_dh_auth.h) is implemented and its wire format is confirmed correct against an independent, authoritative source (neatvnc'srfb-proto.hstruct definitions plus its full server-side verification code, cross-checked field-by-field: generator/key-length framing, the credentials-before-public-key send order -- an actual ordering bug caught this way and fixed -- shared-secret derivation and padding, AES key derivation, and the credential buffer layout all match exactly). Security type 33 (RSA + AES,src/vnc_apple_rsa_auth.h) is also implemented, sourced from theasyncvncPyPI package, as a fallback. Preference when both are offered: None > AppleDH(30) > AppleRSA(33) > VNCAuth(2). Covered by a DH round-trip test, fake-server integration tests for both schemes, a preference-order test, and a regression test built from real bytes captured off an actual macOS server
Known gaps and open issues (see issue #3 for follow-up tracking):
- Apple auth not yet confirmed working end-to-end against a real
server. Live-tested against a macOS Tahoe (26.6.2) Screen Sharing
server with Screen Sharing correctly enabled and the connecting account
allowed: type 33 gets rejected by the server immediately after the
client's initial host-key request (before any credentials are even
sent), and type 30 -- despite matching the authoritative reference
byte-for-byte, verified via multiple independent diagnostic scripts --
still gets rejected with a generic "Authentication or authorization
failure" from the server. macOS Tahoe was released after this
assistant's knowledge cutoff, so there may be a protocol or permission-
model change specific to that OS version neither reference source
reflects; a
screensharingdConsole.app log from the moment of rejection would be the next diagnostic step whenever this is picked back up. Until this is resolved, treat both security types as implemented-and-tested-in-isolation but not verified to actually authenticate against a real macOS server - Tight's Basic compression mode always assumes zlib-compressed payloads; the real protocol permits the server to skip compression for very small (filtered byte count under ~12) payloads, which this decoder doesn't special-case (the exact trigger/wire-signaling for that couldn't be verified with confidence against the RFC text alone). In practice this only affects rare, tiny rectangles -- solid or near-solid tiny areas are virtually always sent as Fill instead -- and fails that one rectangle's decode cleanly (disconnects with a clear error) rather than silently misinterpreting it
- Tight's Gradient filter is implemented from RFC 6143's description but is the least exercised/confirmed of the three filters against a real server in this pass (most real-world Tight traffic uses Copy or Palette)
- Packaging for the new
libjpeg-turbodependency: added todocs/BUILDING.md(apt/brew/vcpkg) and the.debcontrol file'sDepends:(libjpeg-turbo8). The Windows Inno Setup script already wildcards*.dllso no change was needed there, and macOS'smacdeployqt-based bundling picks up non-system dylibs automatically. The Flatpak manifests (packaging/flatpak/*.yml) were left unchanged on the assumption thatorg.kde.Platform/Sdk6.11 already bundles libjpeg-turbo (a standard Qt JPEG-plugin dependency) -- worth confirming the next time a Flatpak build is actually run, since it wasn't independently verified here - No dynamic resize (connects at the server's native resolution; the
Scale to Fit/Actual Sizetoggle changes how that fixed resolution is displayed locally, not what resolution is requested from the guest -- VNC has no equivalent of RDP's MS-RDPEDISP for that)
Milestone 7 - Cross-Platform Protocol Hardening
Status: Completed
Delivered:
- Validated SSH and RDP workflows on Linux, macOS, and Windows 11 (VNC is out of scope while Milestone 6 remains deferred)
- Windows: validated the full
docs/BUILDING.mdtoolchain end-to-end (Git/CMake/Ninja/VS Build Tools with the C++ workload/vcpkg for Qt6-OpenSSL-zlib); documented a VS Build Tools installer gotcha where the actual compiler is a "recommended", not "required", component of the VCTools workload - Fixed RDP keyboard input misreading punctuation keys on Linux (X11 keycode numbering was being treated as a PC/AT scancode; now uses FreeRDP's authoritative X11-keycode-to-scancode table)
- Added RDP clipboard sync (bidirectional, plain text) and RDP cursor/pointer shape sync (resize handles, text I-beam, etc. instead of a static arrow)
- Fixed Tab/Shift+Tab being intercepted by local UI focus navigation instead of reaching SSH/RDP sessions
- Fixed RDP key auto-repeat being dropped, so holding a key only ever sent a single keystroke to the remote machine
- Windows-specific RDP fixes: a crash on every connect attempt (FreeRDP's
signal-handling critical section was never initialized) and a host
resolution failure on every connect, including literal IP addresses
(Winsock was never initialized via
WSAStartup) - Windows: automatic build-time deployment of Qt's platform/SQL-driver
plugins and their runtime DLL dependencies, and marked the executable as a
GUI (
WIN32) app to remove a stray console window behind the UI - Windows and macOS: proper native app icon embedding (Windows
.rc/.icoresource; macOS.appbundle with.icns), replacing the generic default icon previously shown for the built executable/bundle - macOS: fixed Edit/New Profile dialog form fields collapsing to
sizeHintwidth due to the platform-defaultQFormLayoutfield growth policy
Git:
- Tag: Pending user approval (
v0-m7-done)
Milestone 8 - Profile and Session UX Completion
Status: Completed
Delivered:
- Added profile
tagsfield to storage + schema migration and profile editor UX - Added profile
folder_pathfield + nested folder/subfolder profile view mode - Added profile tree context actions (
New Folder,New Connection) and drag-to-folder profile moves with persistence - Added
Help -> About OrbitHubdialog with third-party library inventory and MIT/Apache-2.0 license links - Extended profile search to include tags/folder path and added profile sort controls (
Name,Protocol,Host) - Persisted profile list UX preferences (
search text,view mode, protocol/tag filters,sort order) across app restarts - Added protocol-aware profile validation/normalization for SSH/RDP/VNC (repository + dialog)
- Improved profile form protocol UX hints and SSH private-key path validation
- Added session events filtering and tab-context actions (
Show/Hide Events,Copy Events,Clear Events) - Added session diagnostics QoL: severity quick-filter (
All/Warnings/Errors) andExport Eventsaction - Persisted session UI defaults (
terminal theme,events panel visibility) for new tabs/windows - Added profile quick filters (
Protocol,Tag) with persistence to speed profile browsing
Validation:
- Local build verification passed (
cmake --build build) - No automated tests are currently configured in CTest
Git:
- Tag: Pending user approval (
v0-m8-done)
Milestone 9 - Packaging and Distribution
Status: Completed
Delivered:
- Linux
.deb(packaging/linux/build-deb.sh) and Flatpak (packaging/flatpak/build-flatpak.sh) packages, both verified installed and launched with working SSH/RDP - Renamed the app's reverse-DNS identity from
io.orbithub.OrbitHubtoorg.darksingularity.OrbitHub(desktop file, AppStream metainfo, Flatpak manifest, macOS bundle identifier) to reflect a domain actually owned by the project - Fixed Linux taskbar/panel pin matching (
StartupWMClass) so a pinned launcher merges with its running window instead of creating a duplicate entry - Replaced the stale, mismatched hand-authored launcher SVG with PNG icons rendered directly from the app's own
createOrbitHubAppIcon()at each hicolor theme size - Windows installer via Inno Setup (
packaging/windows/orbithub.iss,packaging/windows/build-installer.ps1), verified installed silently and launched cleanly with no crash events - macOS
.dmgviacmake --install+macdeployqt+hdiutil(packaging/macos/build-dmg.sh), verified installed and launched after fixing:- a launch crash caused by
macdeployqtinvalidating the code signature (fixed with ad hoc re-signing) - a missing-library crash caused by the Linux-only
$ORIGINrpath token and vendored dylibs installing outside the.appbundle (fixed withAPPLE-specific@executable_path/Contents/Frameworkslayout) - the dmg showing the generic disk icon instead of the app icon
- a launch crash caused by
- README and
docs/BUILDING.mdPackaging sections documented for all three platforms
Validation:
- All three installers built, installed, and launched successfully with working SSH/RDP sessions
- Code signing is ad hoc only (no purchased Apple Developer ID or Windows code-signing certificate), so macOS Gatekeeper and Windows SmartScreen still show first-run warnings by design
Git:
- Tag:
v0-m9-done - Release: v2026.9.8 (
v2026.9.8tag, installers for Windows/Linux/macOS) - Release: v2026.9.8.2 — same-day patch fixing RDP TLS certificate verification (was fully disabled) and preparing Flatpak packaging for Flathub submission
- Release: v2026.9.8.3 — same-day patch adding an in-app User Guide and standalone User Guide PDF
- Release: v2026.9.14 — fixes distorted RDP text on HiDPI monitors and reduces RDP resize-related display glitches
- Release: v2026.9.14.2 — same-day patch fixing RDP display corruption (missing/misplaced taskbar) after resizing the session window (the client never resized its own display buffer for channel-driven RDP resizes)
- Release: v2026.9.15 — adds Import/Export for profile lists (File menu)
Milestone 10 - v1.0 Stabilization
Status: Planned
Planned Scope:
- Run final regression and acceptance testing across all protocols
- Resolve release-blocking defects
- Finalize docs and publish v1.0 release notes/checklist