v0.18.0
What's Changed
RustConn 0.18.0 is a HiDPI and cleanup release. Headline changes: a new Native (full HiDPI) Display Scale option plus sharper RDP scaling and cursor rendering on 4K/retina screens (#207); embedded VNC now decodes Tight/JPEG and no longer leaves stale regions after a scroll or window move; and a large internal cleanup removes the abandoned native-SPICE experiment, an unused KeePassXC browser backend, dead render buffers, and a parallel tracing subsystem. Rounding it out are translation fixes (typographic strings now actually localise), fewer per-search allocations, and refreshed desktop-integration dependencies.
Changes are grouped by type in the sections below (Added, Performance, Removed, Changed, Internal, Fixed, Dependencies).
Added
- RDP/VNC Display Scale gained a "Native (full HiDPI)" option (#207) — the embedded Display Scale dropdown offered
Auto(logical resolution, bandwidth-saving) and fixed steps (125–400%), but to get a crisp "retina" remote desktop the user had to know their monitor's scale and pick the matching percentage by hand, which then broke if the window moved to a differently-scaled monitor. The newNativeoption follows the display's live scale factor, so a HiDPI screen gets a full-resolution image that adapts across monitors — the one-toggle "full retina" behaviour requested on #207, without displacing the bandwidth-savingAutodefault. Implemented by resolving the scale multiplier against the widget's runtimescale_factor()(a newScaleOverride::resolved_scale) instead of a compile-time constant
Performance
- Search result
matched_fieldsno longer allocates for the built-in field labels — each search hit recorded which fields matched (name,host,tags,group,username,description) by pushing freshly-allocatedStrings intoConnectionSearchResult.matched_fields. Those labels are compile-time constants, so the field is nowVec<Cow<'static, str>>: the fixed labels are borrowed&'static str(zero allocation) and only the per-connectioncustom_property:<name>entries allocate. A small win confined to matched results, not the hot scan path - Embedded VNC dropped its redundant per-frame
VncPixelBuffercopy — like the RDP widget, VNC wrote every frame update andCopyRectinto both the authoritative Cairo-backed buffer and a legacyVncPixelBufferthat was only read by a fallback draw path that never triggered (the Cairo buffer always has the data). The legacy buffer, its writes, and the dead fallback are removed, saving a full-frame copy per update. As part of this, the surface-dimension OOM guard now lives in the sharedCairoBackedBuffer(clamped to 16384 px/axis), so a server-requested resolution is bounded on both the RDP and VNC render paths instead of only inside the removed VNC buffer - Removed the dead legacy
PixelBufferfrom the embedded RDP widget — investigation confirmed the FreeRDP fallback renderer that was meant to feed this buffer is not wired up (on_end_painthad zero callers, the FreeRDP worker thread ignored itsframe_buffer, and FreeRDP sessions run in an externalxfreerdp3window instead). The IronRDP path renders exclusively through the Cairo-backed buffer, so thePixelBufferwas allocated, resized and cleared every connection/resize but never displayed. Removing it (the struct, the widget field, the never-taken fallback draw branch,on_end_paint, and the FreeRDP thread's unused frame buffer) deletes dead allocations and a whole render path that could never fire - Sidebar search no longer deep-clones every connection on each keystroke —
SearchEngine::search(and the debounced/benchmark variants) took an owned&[Connection], so the sidebar filter had to clone the entire connection list (all fields, tags, per-protocol config) on every search, only softened by the 100 ms debounce. The API now takes&[&Connection]and the sidebar passes the borrowed listlist_connections()returns directly — zero per-keystroke copies. Group scoring/filtering also switched from a lineargroups.iter().find()per connection (O(connections × groups)) to a singleHashMap<Uuid, &ConnectionGroup>lookup built once per search, and a redundantStringallocation in the tag-field dedup check was removed
Removed
- Dead tracing initialisation/config subsystem —
rustconn-core'stracingmodule carried a full parallel logging-setup API (TracingConfig+ builders,TracingLevel,TracingOutput,TracingError/TracingResult,init_tracing,get_tracing_config,is_tracing_initialized, thefield_namesconstants, and thetrace_operation!/trace_operation_debug!macros) that nothing ever called: the application initialisestracingdirectly viatracing_subscriber::fmt().init()inmain.rs, and the only references to this API were its own unit/property tests. Removed the subsystem and the tests that exclusively covered it, keeping the livespan_namesconstants (used across the export/import/search/session paths) and their tests - Unused
SplitSessionIdre-export alias —split::SessionIdwas re-exported at the crate root asSplitSessionId, but nothing imported that alias; the alias is dropped (the underlyingsplit::SessionIdtype is unchanged) - Unreachable "public API for future use" helper methods — six methods carried
#[expect(dead_code, reason = "Public API for …")]with no callers and no tracking issue:ConnectionSidebar::{is_connection_recording, recording_checker_rc},TerminalNotebook::{remove_tab_group, known_group_names, has_active_cluster_sessions}, andPlayback::state. Per the project rule that adead_codeallowance must point to a concrete plan, these speculative methods are removed (the underlying fields stay live via their real callers —set_recording_checker,set_tab_group, the cluster register/unregister/get helpers). Removingremove_tab_groupalso retired its now-orphaned privateclear_group_colorhelper - Dead
KeePassXCbrowser-integration backend —KeePassXcBackendimplemented theKeePassXCbrowser protocol (association handshake + credential store/retrieve/delete over a Unix socket), but it was never constructed anywhere in the application: every code path for theKeePassXc/KdbxFilebackend types resolves credentials through direct.kdbxfile access or falls back to libsecret/Keychain, and the only place the struct was instantiated was its own unit test. Itsdelete()returned an "unsupported" error that, being unreachable, never fired. The struct, itsSecretBackendimpl, the browser-protocol request/response types and association/socket code, and theKeePassXcBackendre-export are removed. The still-used KDBX-database keyring helpers (store/get/delete_kdbx_password_from_keyring) are kept and moved to a focusedkdbx_keyringmodule. No user-facing change —KeePassXCdatabases are, and were, opened via the direct-file backend - Dead Wayland-subsurface placeholders and an unused toolbar field — the embedded RDP and VNC widgets each carried a
WaylandSurfaceHandle/VncWaylandSurfaceskeleton whoseinitialize/commit/damage/cleanupmethods were all no-ops (rendering actually goes through a GTKDrawingArea+ Cairo), plus deadon_frame_update/on_copy_rectwidget methods with no callers. These placeholders and their wiring are removed; native Wayland compositing can be added directly if/when it is actually implemented. Also dropped the unreadPlaybackToolbar::search_entryfield (the widget stays alive through the toolbar it is appended to) - SPICE widget render buffers (
SpicePixelBuffer+ Cairo buffer) — with native embedding gone, the SPICE session always runs in an external viewer, so the widget'sDrawingAreaonly shows a status line. The frame buffers and the unreachable embedded draw branch they fed are removed; the widget now draws status text only - Native embedded SPICE client (the
spice-embeddedfeature) — a spike against the bundledspice-client0.2 confirmed that embedded SPICE cannot work without forking the crate: its public API exposes no inputs channel (keyboard/mouse could never be forwarded — the handlers only logged events) and no way to read raw display frames after the event loop starts (start_event_loopmoves the display channels into background tasks, and the only frame accessor is a WASM-oriented base64 data URL, not BGRA). The feature was already disabled by default in 0.17.10; it is now removed entirely, along with thespice-clientdependency and its transitive tree. SPICE sessions continue to open in an external viewer (virt-viewer/remote-viewer), which is unchanged and fully functional. This deletes the dead nativeSpiceClient, theSpiceClientEvent/SpiceClientCommand/SpiceRecttypes,is_embedded_spice_available(), and the never-reachable input/render code paths in the SPICE widget
Changed
- Operation-result feedback now uses toasts, and a data-loss error uses a dialog (GNOME HIG) — successful Delete / Export / Import previously popped a blocking
adw::AlertDialogthe user had to dismiss; these are now non-blocking toasts, matching the rest of the app. Conversely, failing to save a secret variable to the vault was reported with a transient toast even though the plaintext value is cleared from settings right after (so a missed toast meant silent secret loss); it is now a blockingadw::AlertDialognaming the variable and telling the user to re-enter it and check the secret backend
Internal
- Command Palette dialog now has an accessible title — the
adw::Dialogwas created with an empty title, leaving screen readers without a name for the window; it is now titled "Command Palette". Also wrapped the smart-folder example placeholders ("Prod SSH Servers", host pattern, example tags) ini18n()so they are translatable, and refreshed the translation template (16 languages)
Fixed
- Embedded VNC now decodes Tight/JPEG rectangles instead of showing noise — the most bandwidth-efficient VNC encoding (Tight, which sends photographic regions as JPEG) was disabled in 0.17.10 because its JPEG sub-rectangles were forwarded to the renderer as if they were raw BGRA pixels, painting garbage. The client now decodes each JPEG rectangle to BGRA (via the pure-Rust
zune-jpeg, already present transitively, so no new third-party crate enters the tree) and Tight is offered first again, falling back to ZRLE/CopyRect/Raw. Grayscale (Luma) and truecolor (RGB) JPEGs are both handled; a rectangle that fails to decode is skipped with a warning rather than tearing down the session - Embedded VNC left stale regions on screen after a server-side scroll or window move — the
CopyRectencoding (which tells the client to blit an already-received region to a new location instead of resending pixels) was applied only to the legacyVncPixelBuffer, but the widget's fast draw path reads the persistent Cairo-backed buffer. After anyCopyRect(common when scrolling a terminal or dragging a window on the remote) the moved region was correct in the unused buffer but stale in the one actually painted, leaving ghost/torn areas until that region happened to be repainted by a later full update.CopyRectis now mirrored into the Cairo buffer as well, via a newCairoBackedBuffer::copy_rectthat stages the source through a temporary buffer so overlapping copies stay correct in either direction. RDP is unaffected (IronRDP delivers moved regions as ordinary frame updates, which already write the Cairo buffer) - RDP display scale was lost on dynamic resize (#207) — the MS-RDPEDISP
SetDesktopSizepath sent the new resolution without a desktop scale factor (encode_resize(.., None, None)), so after any dynamic resize (e.g. toggling the sidebar with F9) the server reverted to 100% DPI and an explicitly-scaled HiDPI session shrank to a tiny UI. The requested scale is now threaded throughSetDesktopSize → encode_resizeand re-sent on every dynamic resize and on the initial settle-snap, so an explicit Display Scale (e.g. 200%) stays crisp across resizes. WithDisplay Scale = Autothe factor is 100% on the logical-sized desktop, as introduced in 0.17.10. Based on the contribution by @dwetscher - Embedded RDP HiDPI cursor was partly missing and mis-sized (#207) — on a scaled session the pointer bitmap arrives at the session DPI (2× on 200%) and was downscaled to logical size with a nearest-neighbor sampler that dropped every other row/column, erasing the thin 1px strokes of HiDPI cursors (the "half-missing" pointer). Cursor downscaling is now an alpha-weighted area average (box filter) over every covered source pixel, preserving thin strokes, with correct premultiplied-alpha edge blending and R↔B swap for GDK. At
Display Scale = Auto(100% session) it is an identity copy. Based on the contribution by @dwetscher - Several UI strings were never translated because their
\u{…}escapes leaked into the message catalog — user-facing strings that embedded typographic characters via Rust unicode escapes (e.g."Connection \u{201c}{}\u{201d} created","Advanced\u{2026}", the variable-setup prompt) were extracted byxgettext --language=C, which does not understand Rust's\u{XXXX}syntax and stored the literal backslash-escape as themsgid. At runtime the lookup key is the real character ("Connection "{}" created"), so it never matched the catalog and the string always fell back to English in every locale. The escapes are now written as the actual UTF-8 characters (… ‘ ’ " ") in the source, and the 16 translation catalogs were converted in place so their existing translations are preserved, then re-merged against the refreshed template
Dependencies
- Updated:
cpal0.17 → 0.18 (embedded-RDP audio output — migrated to the new by-valueStreamConfigargument ofbuild_output_stream; streams are still started explicitly viaplay()),muda0.16 → 0.19 andtray-icon0.20 → 0.24 (macOS menu-bar tray stack — drop-in for the menu/icon/builder API used here). No user-facing change; the stale transitivewindows-*0.42 crates were dropped fromCargo.lock - CLI downloads — no changes; all seven pinned Flatpak CLI tools verified current against upstream (
./scripts/check-cli-versions.sh→ all ✅)
Installation
Flatpak (Recommended)
flatpak install flathub io.github.totoshko88.RustConnSnap
sudo snap install rustconnDebian/Ubuntu (.deb from this release)
sudo dpkg -i rustconn_0.18.0_amd64.deb
sudo apt-get install -f # Install dependencies if neededFedora (.rpm from this release)
sudo dnf install rustconn-0.18.0-1.fc44.x86_64.rpmAppImage
chmod +x RustConn-0.18.0-x86_64.AppImage
./RustConn-0.18.0-x86_64.AppImagemacOS (Homebrew)
brew tap totoshko88/rustconn
brew install rustconn
open $(brew --prefix)/opt/rustconn/RustConn.appAll dependencies (GTK4, libadwaita, VTE, Adwaita icons) are installed automatically.
Requires macOS 13 (Ventura) or later.
OBS Repositories
Packages available at: https://build.opensuse.org/package/show/home:totoshko88:rustconn/rustconn
# Debian 13 (Trixie)
echo 'deb http://download.opensuse.org/repositories/home:/totoshko88:/rustconn/Debian_13/ /' \
| sudo tee /etc/apt/sources.list.d/rustconn.list
curl -fsSL https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/Debian_13/Release.key \
| gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/rustconn.gpg > /dev/null
sudo apt update && sudo apt install rustconn
# Ubuntu 24.04 LTS (Noble)
echo 'deb http://download.opensuse.org/repositories/home:/totoshko88:/rustconn/xUbuntu_24.04/ /' \
| sudo tee /etc/apt/sources.list.d/rustconn.list
curl -fsSL https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/xUbuntu_24.04/Release.key \
| gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/rustconn.gpg > /dev/null
sudo apt update && sudo apt install rustconn
# Ubuntu 26.04 LTS (Resolute)
echo 'deb http://download.opensuse.org/repositories/home:/totoshko88:/rustconn/xUbuntu_26.04/ /' \
| sudo tee /etc/apt/sources.list.d/rustconn.list
curl -fsSL https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/xUbuntu_26.04/Release.key \
| gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/rustconn.gpg > /dev/null
sudo apt update && sudo apt install rustconn
# Fedora 44
sudo dnf config-manager addrepo --from-repofile=https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/Fedora_44/home:totoshko88:rustconn.repo
sudo dnf install rustconn
# Fedora 43
sudo dnf config-manager addrepo --from-repofile=https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/Fedora_43/home:totoshko88:rustconn.repo
sudo dnf install rustconn
# openSUSE Tumbleweed
sudo zypper ar https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/openSUSE_Tumbleweed/ rustconn
sudo zypper ref && sudo zypper in rustconn
# openSUSE Leap 16.0
sudo zypper ar https://download.opensuse.org/repositories/home:/totoshko88:/rustconn/openSUSE_Leap_16.0/ rustconn
sudo zypper ref && sudo zypper in rustconnArch Linux (AUR)
yay -S rustconnFreeBSD (Ports)
pkg install rustconnFull installation guide: https://github.com/totoshko88/RustConn/blob/main/docs/INSTALL.md