Skip to content

v0.22.7

Choose a tag to compare

@github-actions github-actions released this 27 Sep 02:10
· 15 commits to main since this release

What's Changed

Added

  • The Snap bundles FreeRDP's X11 client for external RDP (issue #342) — the snap had no external RDP client at all, so everything the embedded IronRDP client cannot do simply failed there: the legacy RDP security layer and TLS-only servers (e.g. Windows 2008 R2), RemoteApp, audio left playing on the remote computer, an RD Gateway whose target listens on a port other than 3389 (the embedded client tunnels the usual case through the gateway itself), and the External client mode with its Smart sizing and Dynamic resolution switches.
    The snap now stages xfreerdp3 from Ubuntu's freerdp3-x11 package (FreeRDP 3.31), and RustConn finds it inside the snap like any other bundled client.
    It is the X11 client because upstream deprecated wlfreerdp3 (issue #340), Ubuntu 24.04 packages no SDL client, and RemoteApp needs an X11 client; it runs through XWayland on a Wayland session.
    Its dependencies are plain Ubuntu 24.04 libraries, and the one GTK-stack library it links, libcairo, keeps coming from the GNOME platform as before.

Fixed

  • Terminal highlight was drawn in the wrong place, and a text colour looked like a background fill (issue #343) — the coloured-highlight overlay sits on a transparent layer above VTE and worked out each cell's size by dividing its own width and height by the column and row count, which assumes the terminal fills its widget edge to edge.
    It does not: VTE rounds every cell to a whole number of pixels and does not fill the widget edge to edge, and the overlay also spans a scrollbar that is not part of the terminal, so the highlight drifted further from the text the further along a line or down the screen the match sat.
    The overlay now uses VTE's real char_width()/char_height() and anchors the grid to the top-left of VTE's content box, which excludes the scrollbar, does not assume the grid is centred, and — unlike VTE's border box — accounts for VTE's one-pixel padding.
    That origin was measured rather than assumed: rendering a full-block character in VTE 0.84 to a texture puts its first pixel exactly there.
    Byte offsets are converted to columns with a new byte_offset_to_column helper in rustconn-core that counts an East-Asian wide character as two cells and a combining mark as none, fixing the drift of one cell per wide character after CJK text that the previous chars().count() caused.
    Its wide set is the East-Asian Width table of Unicode 18.0, the one glib and so VTE use, rather than a hand-picked list, so the ✅ and ❌ that build and test tools print count two cells and the variation selector in ⚠️ counts none — each used to shift the rest of the line by a cell; combining marks are covered for the common blocks, not for every script.
    It also advances a tab to the next tab stop: VTE keeps a tab written at the end of a line as a single cell spanning up to that stop, so every match after a tab — grep over indented code, a Java stack trace's \tat — was drawn up to seven cells too far left.
    Separately, a text colour (foreground rule) was shown as a translucent wash over the whole cell, which reads as a background tint — a user who set a red text colour saw a red background instead; a foreground rule is now a thick underline in the chosen colour, leaving the tinted cell background to background rules only, so the two rule kinds are no longer confused.
  • Typing a non-ASCII character into a highlight colour field crashed RustConn on every terminal connection (issue #343) — the #RRGGBB parser checked the length in bytes and then sliced by byte position, so a six-byte value holding a multi-byte character — #0а0ff with a Cyrillic а, an easy slip on a Ukrainian layout — split that character and panicked.
    The rules are compiled when a terminal session starts, so once such a value was saved, every SSH, Telnet, Serial, Kubernetes or Mosh connection took the application down until the rule was edited.
    The parser now rejects anything that is not six ASCII hex digits; this also stops #+f+f+f from being accepted as a colour.
  • The same crash was still in a connection's terminal colours (issue #343) — the per-connection foreground, background and cursor overrides were parsed by two more copies of that code, one on every terminal start and one when the connection editor opens, and both still sliced the value by byte position.
    The editor's colour buttons cannot produce such a value, but an import, a synced connection list or a hand-edited file can, and it then crashed RustConn on every connection to that host.
    Every colour parser, the built-in theme table's included, now goes through one parse_hex_channels in rustconn-core that checks each byte is an ASCII hex digit before slicing.
  • Highlights disappeared after moving a session, and piled up after a reconnect (issue #343) — the drawing layer was attached to the overlay that hosted the terminal when the rules were first applied, and never followed it.
    Moving the session to a detached window, into a split pane or back out of one wraps the terminal in a different overlay, so highlights vanished (in a split pane they never appeared).
    Replacing the rules — every reconnect — dropped only the bookkeeping, leaving the old layer, its signal handlers and its hover regexes in place, so each reconnect stacked another layer and background tints grew darker every time.
    The layer now re-homes itself on whatever overlay hosts the terminal whenever the terminal is shown, removes its layer, handlers and regexes when it is replaced, and also repaints when a font change or zoom resizes the cells.
  • Highlight settings reached only new sessions, and Zero Trust and Quick Connect sessions had no highlighting at all (issue #343) — saving Settings did not touch open terminals, so turning the built-in ERROR/WARNING/CRITICAL/FATAL rules off or editing a global rule changed nothing on screen until a reconnect; open sessions are now updated on save.
    The rules were applied by seven hand-copied blocks, one per protocol, and a Zero Trust session only received them on reconnect, while Quick Connect sessions never did; every terminal connection now goes through one function that applies the built-in, global and per-connection rules.
  • The highlight rule editors accepted any pattern or colour without a word, and called the underline a text colour (issue #343) — an invalid regular expression or a colour that is not #RRGGBB was stored and then drew nothing, which is how the report began; both editors now outline such a field in red and mark it invalid for screen readers too, so the error is not carried by colour alone, and hovering over an invalid pattern shows the regex engine's explanation.
    The Text colour field is now Underline colour, since the layer cannot recolour the terminal's own glyphs and never did; its tooltip points to an Output Filter such as ChromaTerm for recolouring the text itself.
    In the per-connection editor the colour fields read Underline and Background instead of the abbreviated "Bg", and every field in the row now has an accessible label.
  • A hand-written /smart-sizing custom argument still made the external RDP client exit (issue #341) — the reporter's own route to smart sizing, typing /smart-sizing into Custom arguments, still collided with the /dynamic-resolution that RustConn sends by default, and FreeRDP refused the pair with Command line parsing failed at 'smart-sizing', in the external client and in the embedded wlfreerdp one alike.
    Smart sizing now wins wherever it comes from: a custom /smart-sizing or /smart-sizing:WIDTHxHEIGHT suppresses the default /dynamic-resolution, and a custom dynamic-resolution argument is dropped with a warning while smart sizing is on.
    RustConn also sends the switch in its documented form, /smart-sizing, instead of +smart-sizing; the 0.22.5 note that FreeRDP 3 rejects the bare /smart-sizing was wrong — FreeRDP 3.31 accepts it, and what it rejects is the combination with dynamic resolution.
  • "Reconnect on Resize" did not say it only affects the embedded RDP client, and both sizing switches could look active at once (issue #341) — the reporter reached for Reconnect on Resize to control an external FreeRDP session, which never reads it; its subtitle now says "Embedded client".
    Dynamic resolution is greyed out while Smart sizing is on, since smart sizing overrides it.
  • Importing and exporting .rdp files ignored smart sizing and dynamic resolution (issue #341) — a profile for a legacy server carries smart sizing:i:1 and often dynamic resolution:i:0, and both were dropped on import, so the connection opened unreadably small on a HiDPI display; they are now imported, and exported as the combination RustConn actually uses.
  • The FreeRDP client reported as installed could differ from the one that was launched (issue #340) — the client detection and the launcher kept separate candidate lists, which disagreed on where wlfreerdp and xfreerdp3 go and ignored the X11-first order used on an X11 session; both now walk one list per session type in rustconn-core.
  • rustconn-cli connect could not open an RDP session where only FreeRDP 3 is installed, and ignored the client and sizing settings (issues #340, #341) — the CLI always ran xfreerdp, which Debian's and Ubuntu's freerdp3-x11 and the snap's bundled client (xfreerdp3) do not provide, so the command stopped at "Required program 'xfreerdp' not found".
    It now picks the client the way the GUI does — the connection's pinned FreeRDP client when it is installed, otherwise the first installed one in the shared launch order — and applies the Dynamic resolution and Smart sizing switches and the custom-argument filter with the same rules as every other FreeRDP launch.
  • Embedded RDP "Save N Files" never appeared after copying a file in the remote session (issue #74) — when the server announces a clipboard holding files, IronRDP only records the FileGroupDescriptorW format and leaves requesting the file list to the backend, but the backend requested only text, so the list was never fetched and the button could not appear against a real server.
    Had it been fetched, IronRDP parses the list itself and hands it to CliprdrBackend::on_remote_file_list, which was not implemented; the file-list path RustConn did have was keyed on CF_HDROP, a format it never requests, and only a test that set that state by hand ever reached it.
    The backend now requests the file list, matched by name, whenever the server offers one — after the text when a copy carries both, since CLIPRDR answers one request at a time — and forwards the parsed list to the toolbar; its own descriptor parser, which read the size and name four bytes past where they sit, is gone.
    Reaching the button exposed a second fault in the download: IronRDP refuses a RANGE request that runs past the file size in that list, and files are pulled in 1 MiB slices, so the only slice of a file under 1 MiB, and the last slice of any larger one, would have been refused before it went out; slices are now trimmed to the bytes that remain, and a zero-byte file completes after its size reply without a RANGE request.
    A new remote copy of anything other than files now withdraws the button instead of leaving it offering files the server no longer holds, and a refused clipboard request no longer blanks the local clipboard.
  • "Save N Files" held every file in memory at once, could stay on "Downloading…" for good, and could put binary data on the local clipboard (issue #74) — making the download reachable exposed four faults behind it.
    Every file of a batch was requested at once and buffered whole, up to 512 MiB each, and a saved file stayed in memory until the next batch; files are now fetched one at a time and released once written, a file whose size reply is over the limit is skipped before any of it is fetched, and a large folder copy no longer runs into IronRDP's limit of 1000 pending requests.
    The batch was built from the file list as it stood when the button was pressed, so a remote copy while the folder dialog was open could leave it waiting for files that were never requested; it now uses the list offered when the folder is picked, and once the remote clipboard changes mid-batch no further file is requested, since the server would answer from its new clipboard.
    A request the server never answered also left the button on "Downloading…", because IronRDP's 60-second timeout only fires when the session drives it, which it now does every five seconds.
    And a reply that arrived after the remote clipboard had changed was decoded as text and put on the local clipboard; replies owed to a superseded clipboard are now dropped, and a request that never reached the server is no longer waited for.

Security

  • "Save N Files" could create a hidden file chosen by the RDP server, such as .bash_profile, in the folder the user picked (issue #74) — the names in a remote clipboard's file list come from the server, and RustConn already cut them down to one path component and never overwrote an existing file, but it kept a leading dot.
    A malicious or compromised server could offer .bash_profile or .zshenv among ordinary files, and saving into the home folder left a hidden file that the next login shell runs; the button shows only how many files there are, never their names.
    The path became reachable in this release, now that the file list is fetched at all.
    Leading dots are now stripped, as a browser does for a download, and control and bidirectional-override characters are removed from the name, since they change what a file manager shows — U+202E makes fdp.exe read as exe.pdf.
  • The embedded wlfreerdp launch passed a connection's custom /shell:, /proxy: and password arguments through unfiltered — the external launcher drops, with a warning, a custom argument that carries a secret field or selects a shell or a proxy, because custom arguments can arrive in an imported or synced profile and a proxy or shell of the profile's choosing would change where the session goes or what it runs.
    The embedded wlfreerdp launch applied only the smart-sizing rule, so on that path the rest reached FreeRDP unchecked.
    Every FreeRDP launch now goes through one filter_extra_args in rustconn-core.

Documentation

  • Snap, install and user-guide corrections for the sandboxed builds and highlighting (issues #341, #342, #343) — docs/SNAP.md and docs/INSTALL.md describe the bundled xfreerdp3, state that SPICE is not available in the snap (the previous "needs a host remote-viewer" was not something strict confinement allows), and replace the claim that the viewers need host display access a snap cannot grant.
    docs/INSTALL.md lists the FreeRDP detection order for both Wayland and X11 sessions.
    The Snap Store description no longer promises a TigerVNC fallback or SPICE, neither of which the snap has, and names the bundled FreeRDP client.
    The docs no longer say that RD Gateway needs the external client: the embedded client tunnels through the gateway itself, and only a gateway target on a port other than 3389 goes to FreeRDP.
    The user guide's External Window notes still listed the FreeRDP clients in the order used before 0.22.5, wlfreerdp3 first; they now give the Wayland and X11 orders and name the snap's bundled client.
    The user guide's highlighting section now shows the real built-in patterns, the built-in switch, what the underline and background colours do, how invalid fields are flagged, and which sessions are highlighted; the RDP section covers a custom /smart-sizing and says which client Reconnect on Resize belongs to.

Dependencies

  • FreeRDP (Flatpak) 3.31.1 → 3.32.0 — a security release — upstream lists 62 security advisories against 3.32.0, most of them hardening against protocol violations from a malicious server, and the client now honours an explicitly disabled smart sizing or multi-monitor option on its command line.
    Only the Flatpak and Flathub builds bundle FreeRDP; the deb and RPM depend on the distribution's copy, and the snap stages Ubuntu's freerdp3-x11, which Ubuntu patches on its own schedule.
    The sha256 was taken from upstream's own published checksum beside the tarball and matches the archive as downloaded.
    No Cargo dependency had a compatible update, and cargo deny check advisories is clean.

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.22.7_amd64.deb
sudo apt-get install -f  # Install dependencies if needed

Fedora (.rpm from this release)

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

AppImage

chmod +x RustConn-0.22.7-x86_64.AppImage
./RustConn-0.22.7-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