v0.19.15
What's Changed
Fixed
-
Embedded RDP sessions in Automatic graphics mode showed a frozen desktop (issue #262) — against Windows 11 25H2 the embedded client connected, negotiated the GFX pipeline and then painted nothing at all: server-side
RemoteFX Graphicscounters read 0.00 output frames per second while the session reported itself as active. The cause was on our side, and it was an honesty problem rather than a decoding one.ironrdp-egfx0.3.0 has no AVC444 decoder — it matchesAvc444/Avc444v2and hands those surface updates to a catch-all callback with "AVC444 codec not yet implemented" — yet the capability set RustConn advertised was the crate's default, which includesV10_7and therefore tells the server AVC444 is available. Windows takes the client's best offer, so every frame of the desktop arrived in a codec that was then dropped on the floor. Nothing about 25H2 is special here; any host that prefers AVC444 produced the same frozen picture, and the reporter's own FreeRDP baseline confirmed the server side was healthy (5.25 ms per frame through the same pipeline).RustConn now advertises only what it can decode:
V8.1with AVC420 enabled, andV8for servers with no H.264 encoder at all. AVC420 is decoded, so Automatic mode paints again without giving up H.264. A test pins the requirement so re-addingV10_7after anironrdp-egfxupgrade is a deliberate act rather than an accident, and there is a matching note beside the dependency.Two safety nets should have caught this and did not, so both were repaired. The existing degraded-quality detector counts empty bitmap updates, and AVC444 content never reached that callback — so the counter sat at zero while the session was completely blank. Undecodable surface content is now reported in its own right: the codec is named in the log the first time it appears, and once updates keep being dropped the session retries without GFX, then falls over to the external client, on the same path the pipeline's other failures already used. Separately, the fallback was gated on never having received a first frame, and a single small uncompressed region or legacy bitmap was enough to latch that flag and disable every recovery path for the rest of the session — exactly the half-painted state this bug produced. Recovery no longer depends on that flag; the report fires once per failure run, so it cannot flap.
-
Scrolling, window drags and solid fills were silently discarded in GFX sessions (issue #262) — this is what produced the horizontal bands and unfilled rectangle outlines across an otherwise-working GFX desktop.
ironrdp-egfxdecodes wire codecs but does no compositing:SolidFill,SurfaceToSurface,SurfaceToCacheandCacheToSurfacecarry no pixels, only references to content the client is expected to already hold, and they are forwarded to handler callbacks and nothing else. RustConn left all four unimplemented, so the only EGFX content it could paint was AVC420 and uncompressed regions and everything else vanished. The handler now keeps an RGBA copy of every surface, including offscreen ones, plus the bitmap cache, and synthesises a frame update for each operation. Offscreen surfaces are stored but never pushed to the screen, which also fixes updates to unmapped surfaces being dropped with a warning instead of being remembered for the copy that follows. Both stores are bounded — the cache to the 16 MB ourSMALL_CACHEcapability declares — and past the ceiling the handler degrades to forwarding decoded bitmaps rather than growing without limit. -
A session with no OpenH264 opened a GFX channel it could not paint through (issue #262) — the same freeze by a different route, found while fixing the one above. When OpenH264 failed to load, the pipeline was still registered, just without a decoder, on the documented assumption that it would "fall back to uncompressed/RFX-progressive within the GFX channel". It does not: with no decoder
ironrdp-egfxadvertisesV8only, Windows answers with RFX Progressive, and there is no progressive decoder either — those PDUs are forwarded to a handler callback and the pixels are lost. The channel is now simply not opened when OpenH264 is missing, so the session stays on the RemoteFX path, which works. RFX Progressive is additionally reported through the same undecodable-content route as any other missing codec, so if a server sends it anyway the session recovers instead of freezing. -
The session status bar reported a graphics pipeline the session was not using (issue #262) — the mode shown next to the RTT reading came from a compile-time constant: any build with the
gfx-h264feature claimed "GFX + H.264", including sessions in Legacy or RemoteFX mode that never opened the GFX channel. It now reflects the capability set the server actually confirmed, and sessions that stay on the RemoteFX/bitmap path show the mode their advertised bitmap codecs imply instead. -
Pressing Backspace in a session could kill the whole window — the crash was an
abort()inside libvte rather than a Rust panic:map_erase_binding(): Assertion 'auto_mode != eTTY' failed. VTE's default erase binding isAuto, and it resolves that by readingVERASEout of the termios of the pseudo-terminal it owns — which, since RustConn began creating the PTY itself (#247), is no terminal at all. Upstream added a fallback for the no-descriptor case in November 2025, but the vte 0.84 that distributions ship still carries a second Backspace path without it: in the compiled library one call site testsm_ptyand falls back to^H, while the other passeseTTYunconditionally and walks into the assertion. An assertion in a library aborts the process that loaded it, so one keystroke ended every open session at once.RustConn now names both bindings on every terminal it creates instead of leaving them at
Auto, which puts that branch out of reach — the mapper answers from the constant and never goes looking for a PTY. The values are the ones VTE would have arrived at on its own: Backspace sends DEL (0x7f), theVERASEouropenptyPTY actually carries and therefore what the remote side'sstty eraseagrees with, and Delete sends the VT220 sequence\e[3~. Read-only terminals are covered too — the recording player disables input, and disabled input does not stop VTE from mapping the key before it checks. Telnet's Backspace sends and Delete sends set to Automatic selected exactly the crashingAutoand now select the same explicit pair; the explicit Backspace/Delete choices were never affected. A contract test next to the other #247 ones pins the requirement, because this failure mode arrives as a process abort rather than as a failing assertion. -
Embedded RDP sessions leaked a clipboard watcher that ran a selection read for every copy on the desktop (issue #261) — symbolising the reported coredumps against the same GNOME 50 runtime libraries the reporter ran places the fault on a GLib thread-pool worker:
g_thread_proxy→g_thread_pool_thread_proxy→g_task_thread_pool_thread→read_async_thread→ aGConverterInputStreamread → GTK's X11 text-list converter, which calls Xlib'sXmbTextPropertyToTextListdirectly. In GTK 4.22 that combination is reachable from exactly two places, both of them clipboard and drag reads, and only when the selection owner offersCOMPOUND_TEXT/TEXT/STRINGinstead ofUTF8_STRING. So the immediate fault is upstream and X11-only, which is why Wayland sessions and the External FreeRDP mode were never affected — but what put several of those conversions on worker threads at once was ours.On connect, the embedded IronRDP path subscribes to
changedon the clipboard belonging to theGdkDisplay, not to the widget.disconnect()— the methodDropand the tab-close path both call — cleared the resize handler but never that subscription; the only code that did was a helper reached solely from connection failure paths. Every session that connected successfully therefore left a live watcher behind for the rest of the process, each one starting aread_text_asyncfor every copy anywhere on the desktop, so the conversions multiplied with the number of RDP sessions ever opened. Teardown now clears it on every path. A second defect compounded it: the handler id lived in a single slot shared by all connection generations, so a reconnect overwrote it without disconnecting the old handler, and the superseded polling loop then disconnected the id it found — the live one. The slot now records which generation installed the monitor, stale loops only remove their own, and a new monitor replaces any predecessor explicitly.Clipboard sharing also genuinely honours the profile setting now.
Clipboardon an RDP connection reached only the CLIPRDR channel inrustconn-coreand the external FreeRDP argument; the GTK-side watcher, the server→local auto-sync that takes ownership of the local clipboard, and the reply to a server clipboard request all ran regardless. That is why the reporter found that disabling clipboard sharing on every profile changed nothing, and it is the setting to reach for if a session still misbehaves.This removes the amplifier rather than the upstream fault, and it is being shipped on that basis: the reconstruction identifies the crashing code and explains why embedded mode alone reached it, but pinning the exact memory error needs the retained coredumps read against debug symbols. That is the next step on the issue.
Installation
Flatpak (Recommended)
flatpak install flathub io.github.totoshko88.RustConnSnap
sudo snap install rustconnDebian/Ubuntu (.deb from this release)
sudo dpkg -i rustconn_0.19.15_amd64.deb
sudo apt-get install -f # Install dependencies if neededFedora (.rpm from this release)
sudo dnf install rustconn-0.19.15-1.fc44.x86_64.rpmAppImage
chmod +x RustConn-0.19.15-x86_64.AppImage
./RustConn-0.19.15-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