What's Changed
Fixed
-
The Session Logs dialog could not be made narrow — its file list and content pane were held apart by a
gtk::Paned, which keeps both children on screen at every width, so the dialog had a wide floor and could not sit beside anything on a small display. It is anadw::OverlaySplitViewnow, with a breakpoint that collapses the list into an overlay below 500sp and a header toggle that appears only once collapsed. Picking a file while collapsed dismisses the overlay, since otherwise the log just opened would stay hidden behind the list. The project's own HIG notes name this substitution directly — a collapsing sidebar rather than a fixed pane — and the main window had been following it for releases. -
Two boxed lists were drawn inside a second border, and an information row carried two icons — the workspace list sat in a
gtk::Framewhile already carrying theboxed-liststyle class, which draws its own border, so the list appeared with a frame around its frame. The Credentials Storage row in the export dialog hadℹtyped into its title and adialog-information-symbolicprefix, so the same idea was stated twice — once as a themed icon and once as a character, which does not follow the icon theme, cannot be translated, and is read out by a screen reader as part of the heading. -
Client detection reported its result by colour and a glyph alone — each row on Preferences ▸ Clients carried a bare
✓,✗or●styled with a CSS colour class and nothing else: no tooltip, no accessible label. A screen reader read out the glyph, which says nothing about whether the client was found, and the distinction between the three states rested on colour, which is precisely what the accessibility guidance rules out. Each indicator now carries the state as words — "Installed", "Not found", "Using embedded client" — as both its accessible label and its tooltip. The glyph and colour stay as the visual cue. -
Preferences showed internal action names to the user — every row on the Keyboard Shortcuts page used the action identifier as its subtitle, so the list read "Close Tab / app.close-tab", "Quit / app.quit" and so on. That string is what the code keys its accelerator lookups by; it is not a description, and it was the one untranslated piece of text on the page. The subtitle is gone — the row's title already names the action in the user's language.
-
Three switches in Preferences ▸ Secrets were not boxed-list rows — "Use API key authentication" (Bitwarden), "Use password" and "Use key file" (KeePassXC) were built as a bare
GtkSwitchdropped into anAdwActionRowsuffix, while every other switch on the page had already moved toAdwSwitchRow. They behaved correctly but sat slightly apart from their neighbours and needed a manualset_activatable_widgetto make the row clickable at all. All three areAdwSwitchRownow, which handles that itself, and the three intermediate rows they needed are gone. -
A smart folder whose icon was an icon name displayed the name as text — the folder row pushed whatever the icon field held straight into a
Label, so enteringfolder-symbolicshowed the literal string "folder-symbolic" instead of an icon. Connection rows in the same file already got this right, telling an emoji from a themed icon name withis_glyph_iconand falling back throughtheme_icon_orwhen the active theme lacks it; the folder row is on that path now. Its default stopped being a hardcoded 📁 glyph, and the "Smart Folders" section header stopped being a 🔍 one — both are symbolic icons, so they follow the icon theme and the high-contrast setting instead of the font. An emoji the user typed is still shown exactly as typed: that is a choice, not a default. -
The Smart Folders context menus were invisible to a screen reader and unusable from the keyboard — right-clicking a smart folder, or a connection inside one, opened a menu built by hand in
smart_folder_ui: aGtkBoxof plain buttons with noAccessibleRole::Menuon the container and noMenuItemon the items, so assistive technology announced a box of buttons rather than a menu. There was no arrow-key navigation, no Home/End, no Escape handler, no height cap, and no coordination with the rest of the application's popovers. The sidebar's own context menus have had all of that for several releases; these two were a parallel implementation that never received any of it. They now go through the sameshow_popoverbuilder, so the behaviour cannot drift again. Making that possible meant generalisingContextMenuItem: an item carries a list of actions to activate in order, each with an optional target value, which is what the smart-folder items need — "select this connection by id, then run the action that operates on the selection". Two side effects worth noting: the shared builder brings the fixes those menus had been missing (the grab handling from issues #157/#299, the early-dismissal retry, the focus-loss close from #93, the menu height cap from #298), andDeletein the sidebar's menu is now styled as destructive, which only the hand-rolled copies had been doing. -
The embedded VNC status screen was English-only — the placeholder drawn before the framebuffer arrives ("Disconnected", "Connecting…", "Connected", "Connection error", "Session running in external window") passed bare literals straight to Cairo, so it stayed in English in every locale. The RDP equivalent had been going through
i18n()all along, which is what made the gap easy to miss: the two screens look the same and only one of them was translated. The host placeholder shown when nothing is configured yet was untranslated too, on both sides — VNC's "Not configured" and RDP's "No connection". All of it is wrapped now, and VNC's "Connecting..." picked up the proper ellipsis while it was being touched. -
Copy in an embedded VNC session did nothing and said nothing — the button was wired to a handler that read the local clipboard, logged its length at debug level, and stopped. Nothing was transferred, no status appeared, and the button was enabled from the moment the toolbar was built, so pressing it on a fresh session was indistinguishable from a button with no handler. The RDP toolbar next to it had the full version — cache the text the server pushed, put it in the local clipboard on demand, report "Copied N chars" or "No remote clipboard data" — and VNC had none of it. It does now. The one structural difference from RDP is worth stating, because it decides what the button can mean: RFB clipboard transfer is push-only, so there is no request a client can send to read the remote clipboard. The text the server volunteered through
ServerCutTextis the only copy that exists locally, soEmbeddedVncWidgetretains it and Copy re-asserts it into the local clipboard — which is what you want when something else has since overwritten it. The automatic mirror on arrival stays, since without it a remote copy would never reach the local clipboard at all. The button now starts insensitive with a tooltip saying it is waiting for remote data, exactly as the RDP one does, and becomes active when the first text arrives. It also gained the accessible label it was missing.show_status_brieflymoved fromembedded_rdp::clipboardtoembedded_trait, where both toolbars can reach it — the absence of a shared helper is part of why the VNC side had no feedback to give. -
The wizard threw away the icon when you pressed "Advanced…" — typing an emoji or icon name on the wizard's Authentication page and then continuing into the full editor lost it, so the field opened empty and the connection was saved without it. Save and Save & Connect kept the icon, which is what made this hard to spot: the icon was read in
build_connection, the method those two buttons use, andcollect_partial— the one that feedsWizardResult::OpenAdvanced— never asked for it.PartialConnectionhad noiconfield at all, so there was nowhere for it to go even if it had been read. The field exists now and the fallback to the selected template's icon is resolved once, insidecollect_partial, rather than separately inbuild_connection; both paths read the same value, so they cannot disagree about it again.from_connectioncarries the icon in the other direction too, which fixes the same omission in "Duplicate via Wizard…" — it dropped the original's icon on the way into the wizard, andAuthPagehad a getter for the field but no setter, so nothing could have pre-filled it. -
The connection editor kept calling a URL a Host, and hid fields by leaving an empty row behind — selecting the Web protocol was supposed to retitle the Host row to URL, and the protocols that have no host or port — Serial, Kubernetes, and every Zero Trust provider but Custom Command — were supposed to drop those rows entirely. Neither worked. The General tab created seven
gtk::Labelwidgets for Host, Port, Username, Domain, Tags, Password Source and Value, stored them onBasicTabWidgets, and never added a single one to any container. The visible text of a boxed-list row is the row's ownAdwActionRow::title, set separately when the row was built, so the labels were orphans from the moment they were constructed.apply_general_field_visibilitythen spent its whole body acting on them:host_label.set_text("URL")retitled a widget nobody could see, andhost_label.set_visible(false)hid the same one. What made the second half look like it worked is that the entries were being hidden alongside — so a hidden field left its row on screen, titled "Host" or "Port", with nothing in it to type into. Visibility and titles now act on the rows, which is also why the paired*_entry.set_visible()calls are gone: an entry is a suffix inside the row, so hiding the row already takes the entry with it, and toggling both invited the two to disagree. The orphaned labels are deleted rather than wired up, along with theValueone that had no reader at all. Three of them turned out to be the only thing keepingusername_entry,tags_entryanddomain_entryalive in the visibility struct, and the whole#[expect(dead_code)]onBasicTabWidgetswent with them — an attribute that had been silently absorbing this bug, since "these fields are never read" was the compiler pointing straight at it.
Changed
-
Every ellipsis in the interface is now one character instead of three periods — GNOME's writing-style guidance calls for
…, and the codebase had been using both spellings side by side: 111 user-facing strings across 31 files still spelled it..., next to strings that already carried…. All of them are…now. Only strings the user reads were touched:Displayimpls,tracingmessages, doc comments, icon names and action identifiers keep their periods, because the first two end up in log files a person greps and the last two are identifiers the toolkit matches literally.The two spellings were not separated by dialog, which is the part worth recording, because it means two strings had been living in the catalogues twice.
dialogs/connection/dialog/build.rssets a button label toTesting...while testing the connection and toTesting…while testing a post-login script — two buttons in one dialog, two msgids, seventeen translators each asked the same question twice.Verifying...in the KeePassXC unlock dialog andVerifying…in the portable-storage one are the same duplication across two files. The answers diverged: forVerifyingseven catalogues chose a different verb for each copy (Dutch "Verifiëren" against "Controleren", Polish "Weryfikowanie" against "Sprawdzanie", and the same split in Danish, Italian, Georgian, Slovak and Swedish), and Ukrainian did it forTestingtoo — «Випробовування…» on one button and «Тестування…» on the other, so which word appeared depended on which code path built it. Normalising the spelling merges each pair down to one msgid, and the entry kept is the one that already carried the proper ellipsis.Regenerating the template also surfaced five strings that had never reached the translators at all. Four are from earlier in this release — the Credentials Storage row that lost its
ℹ, the "Show log file list" toggle the Session Logs dialog gained, and the "Using embedded client" / "Not found" wording the Clients page now speaks out loud — where the source string changed and the catalogues were not resynced in the same commit. The fifth,No connection, is older: it is the host placeholder an embedded RDP session draws before anything is configured, present in the code since v0.8.4 and missing from the template ever since, so it had never been translatable in any release. All five are translated in all seventeen languages, and the punctuation-only differences were carried across without afuzzyflag, since an ellipsis is not a linguistic change.
Removed
- Around 390 lines of unreachable UI code, including one control that lied about what it did — the audit for this release went looking for dead widgets and found that most of what it turned up was not merely unused but a spare copy of something already written elsewhere, kept alive by an
#[expect(dead_code)]whose stated reason had stopped being true.embedded_traitheld three:EmbeddedWidgetState, a state-and-callbacks helper no widget ever adopted because RDP, VNC and web each keep their own fields;create_embedded_toolbar, which built a toolbar nobody asked for and was actively misleading, since it set accessible labels on its buttons and so made the VNC toolbar look as though it had merely forgotten to, when it had never been built from there at all; anddraw_status_overlay, a third copy of the pre-framebuffer placeholder alongside the one RDP calls and the one VNC draws inline.sidebar_typesheldDragDropData, marked "Fields used by drag-drop callback system" when no callback system referenced it, plus the index-tracking half ofDropIndicator— two fields written and never read, a setter only ever called withNone, and ashow()whose row index its one caller passed as0beneath a comment saying it was unused; all of it left over from before the drop visual moved to CSS classes.connection::webheldvalidate_web_options, reserved for "upcoming connection-editor validation wiring" that had in fact already been done, better, inConnectionDialogData::validate, where a refusal produces a message the user sees rather than a barebool. The one with a user-visible edge isSessionControls: its fullscreen button flipped a privatebooland called no window API whatsoever, so it could not have done what its tooltip promised. It was never on screen — the onlyEmbeddedSessionTabever built is theforce_externalone that owns a spawned viewer process and is never added to the notebook — but a button that cannot work is worse than no button, so it is gone rather than fixed. Fullscreen for a real session iswin.toggle-fullscreen.
Dependencies
- Updated: crossbeam-channel 0.5.16 → 0.5.17, crossbeam-deque 0.8.7 → 0.8.8, crossbeam-epoch 0.9.20 → 0.9.21, crossbeam-utils 0.8.22 → 0.8.23, der 0.8.1 → 0.8.2, ipnet 2.12.1 → 2.12.2. All in-range patch/minor updates within existing requirements;
cargo deny check advisoriesclean.dirswas held at 6.0 rather than taken to 7.0: the API is identical for the functions in use, buttray-icon 0.24still pinsdirs = "^6", so bumping would duplicate the crate in the tree for no functional gain.
Installation
Flatpak (Recommended)
flatpak install flathub io.github.totoshko88.RustConnSnap
sudo snap install rustconnDebian/Ubuntu (.deb from this release)
sudo dpkg -i rustconn_0.21.7_amd64.deb
sudo apt-get install -f # Install dependencies if neededFedora (.rpm from this release)
sudo dnf install rustconn-0.21.7-1.fc44.x86_64.rpmAppImage
chmod +x RustConn-0.21.7-x86_64.AppImage
./RustConn-0.21.7-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