Skip to content

v0.18.0

Choose a tag to compare

@github-actions github-actions released this 05 Jul 00:39

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 new Native option 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-saving Auto default. Implemented by resolving the scale multiplier against the widget's runtime scale_factor() (a new ScaleOverride::resolved_scale) instead of a compile-time constant

Performance

  • Search result matched_fields no longer allocates for the built-in field labels — each search hit recorded which fields matched (name, host, tags, group, username, description) by pushing freshly-allocated Strings into ConnectionSearchResult.matched_fields. Those labels are compile-time constants, so the field is now Vec<Cow<'static, str>>: the fixed labels are borrowed &'static str (zero allocation) and only the per-connection custom_property:<name> entries allocate. A small win confined to matched results, not the hot scan path
  • Embedded VNC dropped its redundant per-frame VncPixelBuffer copy — like the RDP widget, VNC wrote every frame update and CopyRect into both the authoritative Cairo-backed buffer and a legacy VncPixelBuffer that 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 shared CairoBackedBuffer (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 PixelBuffer from the embedded RDP widget — investigation confirmed the FreeRDP fallback renderer that was meant to feed this buffer is not wired up (on_end_paint had zero callers, the FreeRDP worker thread ignored its frame_buffer, and FreeRDP sessions run in an external xfreerdp3 window instead). The IronRDP path renders exclusively through the Cairo-backed buffer, so the PixelBuffer was 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 keystrokeSearchEngine::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 list list_connections() returns directly — zero per-keystroke copies. Group scoring/filtering also switched from a linear groups.iter().find() per connection (O(connections × groups)) to a single HashMap<Uuid, &ConnectionGroup> lookup built once per search, and a redundant String allocation in the tag-field dedup check was removed

Removed

  • Dead tracing initialisation/config subsystemrustconn-core's tracing module carried a full parallel logging-setup API (TracingConfig + builders, TracingLevel, TracingOutput, TracingError/TracingResult, init_tracing, get_tracing_config, is_tracing_initialized, the field_names constants, and the trace_operation!/trace_operation_debug! macros) that nothing ever called: the application initialises tracing directly via tracing_subscriber::fmt().init() in main.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 live span_names constants (used across the export/import/search/session paths) and their tests
  • Unused SplitSessionId re-export aliassplit::SessionId was re-exported at the crate root as SplitSessionId, but nothing imported that alias; the alias is dropped (the underlying split::SessionId type 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}, and Playback::state. Per the project rule that a dead_code allowance 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). Removing remove_tab_group also retired its now-orphaned private clear_group_color helper
  • Dead KeePassXC browser-integration backendKeePassXcBackend implemented the KeePassXC browser protocol (association handshake + credential store/retrieve/delete over a Unix socket), but it was never constructed anywhere in the application: every code path for the KeePassXc/KdbxFile backend types resolves credentials through direct .kdbx file access or falls back to libsecret/Keychain, and the only place the struct was instantiated was its own unit test. Its delete() returned an "unsupported" error that, being unreachable, never fired. The struct, its SecretBackend impl, the browser-protocol request/response types and association/socket code, and the KeePassXcBackend re-export are removed. The still-used KDBX-database keyring helpers (store/get/delete_kdbx_password_from_keyring) are kept and moved to a focused kdbx_keyring module. No user-facing change — KeePassXC databases 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/VncWaylandSurface skeleton whose initialize/commit/damage/cleanup methods were all no-ops (rendering actually goes through a GTK DrawingArea + Cairo), plus dead on_frame_update/on_copy_rect widget 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 unread PlaybackToolbar::search_entry field (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's DrawingArea only 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-embedded feature) — a spike against the bundled spice-client 0.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_loop moves 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 the spice-client dependency 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 native SpiceClient, the SpiceClientEvent/SpiceClientCommand/SpiceRect types, 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::AlertDialog the 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 blocking adw::AlertDialog naming 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::Dialog was 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) in i18n() 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 CopyRect encoding (which tells the client to blit an already-received region to a new location instead of resending pixels) was applied only to the legacy VncPixelBuffer, but the widget's fast draw path reads the persistent Cairo-backed buffer. After any CopyRect (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. CopyRect is now mirrored into the Cairo buffer as well, via a new CairoBackedBuffer::copy_rect that 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 SetDesktopSize path 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 through SetDesktopSize → encode_resize and re-sent on every dynamic resize and on the initial settle-snap, so an explicit Display Scale (e.g. 200%) stays crisp across resizes. With Display Scale = Auto the 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 by xgettext --language=C, which does not understand Rust's \u{XXXX} syntax and stored the literal backslash-escape as the msgid. 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: cpal 0.17 → 0.18 (embedded-RDP audio output — migrated to the new by-value StreamConfig argument of build_output_stream; streams are still started explicitly via play()), muda 0.16 → 0.19 and tray-icon 0.20 → 0.24 (macOS menu-bar tray stack — drop-in for the menu/icon/builder API used here). No user-facing change; the stale transitive windows-* 0.42 crates were dropped from Cargo.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.RustConn

Snap

sudo snap install rustconn

Debian/Ubuntu (.deb from this release)

sudo dpkg -i rustconn_0.18.0_amd64.deb
sudo apt-get install -f  # Install dependencies if needed

Fedora (.rpm from this release)

sudo dnf install rustconn-0.18.0-1.fc44.x86_64.rpm

AppImage

chmod +x RustConn-0.18.0-x86_64.AppImage
./RustConn-0.18.0-x86_64.AppImage

macOS (Homebrew)

brew tap totoshko88/rustconn
brew install rustconn
open $(brew --prefix)/opt/rustconn/RustConn.app

All 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 rustconn

Arch Linux (AUR)

yay -S rustconn

FreeBSD (Ports)

pkg install rustconn

Full installation guide: https://github.com/totoshko88/RustConn/blob/main/docs/INSTALL.md