v0.19.14
What's Changed
Added
-
An empty split panel can start a local shell of its own — until now an empty panel offered exactly one way forward: Select Tab, which moves a session that already exists somewhere else. A Local Shell button now sits beneath it for the case where nothing open is worth moving and what is wanted is a scratch shell beside the session being worked on. It is deliberately not the accented button — choosing an existing tab stays the panel's primary action — while sharing the same pill shape so the two read as one group.
The new session never appears in the tab bar: it is created, placed in the panel and its own tab parked in one go. Two details make that work rather than merely look like it works. Creating a session selects its tab, and parking that tab afterwards leaves
AdwTabViewto pick whichever page happens to be next, so the tab hosting the split is re-selected explicitly — otherwise asking for a shell would throw the user out of the split they were working in. And focus is moved into the new pane, because someone who asked for a shell here means to type here. If the shell cannot be started at all, the session is left in a tab of its own instead of vanishing into a panel that failed to accept it.
Fixed
-
Session transcripts lost command output and repeated whole screens (issue #247) — the transcript was reconstructed by reading text back out of the terminal widget and comparing it with the previous reading. That cannot be made to work, and the reason is worth recording: VTE rewraps its buffer whenever the window changes width, so a wrapped line stops occupying two rows and every row below it is renumbered underneath the reader. A session's terminal is rewrapped at least once on connect, when the widget receives its real allocation, and again on every window resize. Shifted one way the comparison re-reported lines already written, shifted the other it skipped lines that were never written — and with the earlier
skip(previous.lines().count())variant, output stopped being recorded altogether once the screen filled, solsordf -hleft no trace at all.RustConn now owns the pseudo-terminal instead. The child is started on a PTY that RustConn creates, a reader thread publishes each chunk the moment it arrives, and the transcript is written from those bytes; VTE keeps rendering, key handling, selection and scrollback, and gives back the input it would have written through its
commitsignal, which it emits whether or not it owns a descriptor. The log is therefore a copy of the session rather than an inference about it: in order, complete, with nothing sampled and nothing deduplicated. Output is buffered to line boundaries because a log is read by lines, splitting on\nalone so a progress bar redrawing itself with\rstays one line rather than hundreds; a trailing line that stops growing — a shell prompt, a password question, neither of which ends in a newline — is written after half a second so it is visible while a connection is still being diagnosed; and the last line of a session, where alogoutor an authentication failure lands, is written when the process exits. ANSI escape sequences are stripped in both writing paths, including the untimestamped one the default configuration actually uses, so a transcript opened in a text editor is readable, and because stripping runs before redaction an escape sequence in the middle of a prompt can no longer split the pattern redaction looks for.The behaviours this rests on are asserted against a real terminal rather than assumed, in
terminal::vte_contract_tests: thatcommitcarries input with no PTY attached, that no VTE signal announces a resize (which is why the window size is pushed down from a 250 ms poll), that cursor rows and text ranges share one coordinate space, and that widening the terminal renumbers rows. Three earlier attempts at this transcript reasoned about those instead of checking them. -
Closing one pane took the whole split layout apart — the close returned every remaining pane to its own tab, leaving the split empty. Layouts are looked up in a map keyed by session, and a layout is registered there under every session it displays, because several places need to find the layout a given session sits in. Only one of those sessions owns the layout, though: the one that asked for the split, on whose tab the split widget actually lives. Closing that tab does destroy the layout, so its guests have to be moved back to their own tabs first — and the tab-close handler ran that evacuation for whichever session it was handed, since a map lookup cannot tell owner from guest. Closing a guest therefore looked exactly like closing the owner. The layout now records which session hosts it and the evacuation only runs for that one. Removing a pane with Remove from Split was never affected: it moves a session without closing a tab.
-
Opening the Settings panel silently corrupted the KeePassXC database password configuration (issue #259) — the storage combo validation handler in the secrets tab treated a pending
secret-tooldetection result (None) as "unavailable", reverting the "System keyring" selection to "Don't save" before the dialog was even visible. On close the collected settings wrotekdbx_save_to_keyring = false, breaking database access until restart. The same mechanism also cleared the encrypted-file password blob for Scenario 1. The handler now only reverts when detection has confirmed absence (Some(false)), preserving previously-saved config values loaded before async detection completes. The mirror case is covered as well, and deliberately without touching the configuration: when detection finishes and reports thatsecret-toolreally is missing, a keyring selection restored from config stays exactly as it was and the tab explains the problem instead — rewriting that selection behind the user's back is what destroyed the setting in the first place — while the guard still blocks choosing the keyring from that point on.
Improved
-
One PTY path for every session on every platform (issues #175, #247) — session commands used to be started two different ways: VTE's
spawn_asyncon Linux, and a hand-writtenopenptypath on macOS, where VTE's own spawn never connects the child's output to the PTY in the Homebrew build. Every environment or controlling-terminal fix therefore had to be made, and verified, twice. Both platforms now run the same code, and it is the macOS mechanism that survived: create the PTY, size it beforeexecso a program that reads its geometry once (mc,vim,less) starts at the real size instead of correcting itself on the firstSIGWINCH, and spawn the child with the slave as its standard streams and apre_exechook that claims it as a controlling terminal — which is what letssshopen/dev/ttyto ask for a password.The environment is assembled in one place instead of two — extended
PATHfor CLI tools RustConn downloaded itself, SSH agent socket, the Flatpak and snap config-directory redirections,TERM, and the macOSSSH_ASKPASS_REQUIREguard from #161 — and it is now held in zeroizing buffers, because a jump host's password reachessshthrough it as an askpass variable and RustConn's own copy has no reason to outlive the spawn. Reading and writing the descriptor happen on their own threads, so neither a burst of output nor a large paste into a process that is not reading can block the window; output is bounded to half a megabyte in flight, after which the child is throttled by the kernel rather than the queue growing. Two smaller details: the spare descriptors handed to the child are close-on-exec, so a session's child no longer inherits three stray descriptors pointing at its own terminal, andCtrl+Spacestill reaches the child, which it would not have — the signal that carries input truncates at the NUL byte that key sends, so the byte is restored from the length the signal also reports. -
One copy of the split-panel placement sequence instead of two — moving a session into a pane is nine steps (refuse a detached session, clear it from any other layout, resolve its widget, reparent it, register the bridge, set the tab colour, park the standalone tab, suspend monitoring, refresh and re-wire broadcast), and every one of them matters: skip the park and a dead placeholder tab is left behind, skip the registration and the pane is invisible to the clipboard and broadcast lookups. That sequence existed twice, once per split orientation, which is how #252 happened — a fix applied to one copy and not the other. Both Select Tab callbacks and the new Local Shell button now share a single implementation, and the file lost 87 lines in the process.
Dependencies
nixis now a dependency ofrustconn-pty-sys— only thetermfeature, foropenpty(2); in exchange therustconnGUI crate dropped the directlibcdependency it no longer needs. No third-party crate version changed in this release.
Translations
- Ukrainian is complete again — regenerating the translation template surfaced four strings that had been added since it was last refreshed and were therefore missing from every catalogue: the US Dvorak RDP layout name, the session-toolbar and panel-actions reveal tooltips, and the jump-host SFTP fallback notice. All four are translated in Ukrainian along with the new Local Shell tooltip. The other fifteen catalogues now carry the five entries untranslated, which reads as English at runtime exactly as it did while the entries were absent.
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.14_amd64.deb
sudo apt-get install -f # Install dependencies if neededFedora (.rpm from this release)
sudo dnf install rustconn-0.19.14-1.fc44.x86_64.rpmAppImage
chmod +x RustConn-0.19.14-x86_64.AppImage
./RustConn-0.19.14-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