An optional 'Nexus Terminal' printer (IronRDP's PostScript driver
announce) appears on the remote desktop; each job is written to
'Nexus print <time>.ps' in Downloads, with a toast saying where. Jobs
are capped at 256 MiB and empty jobs leave no file. The shared drive and
the printer now share one RDPDR backend (rdp_devices); the printer
works on every platform, the drive on Linux and macOS.
Tested with a unit test driving create/write/close; xrdp has no printer
redirection, so not exercised against a server.
- Audio: the RDPSND channel with IronRDP's cpal playback backend (PCM;
Opus left out to avoid another native library). On by default for new
RDP profiles. Building now needs the ALSA headers on Linux
(libasound2-dev), added to CI, release and fuzz jobs and the build docs.
- Shared folder (Linux/macOS): one local folder appears on the remote
desktop as a drive. IronRDP's native backend joins server paths onto
the share unchecked, so it is wrapped in ConfinedDrive: create, list
and rename paths with '..' components, NUL bytes, or that resolve
through a symlink outside the share are refused (ACCESS_DENIED);
tests cover the escapes.
Verified against xrdp (with FUSE): the remote read the shared file, and
writes, mkdir and rename landed in the local folder. Not verified:
audio playback (the test container has no sound module), and xrdp lists
the drive as 'ignored' and can't list its directories (an IronRDP/xrdp
naming and listing mismatch, fixed upstream for the name).
- Protocol::Rdp with RdpConfig (domain, password mode, certificate
policy, TLS-only fallback, fixed or tab-sized resolution, SOCKS5/HTTP
proxy, SSH tunnel); migration 008 adds rdp_config and drops the
protocol CHECK constraint so new protocols no longer need a rebuild.
- Session manager: connect_rdp dials directly, via the shared proxy
module (proxy password from the secret store) or an SSH tunnel; the
server certificate goes through the known-hosts check (key type
rdp-tls); a tab-sized desktop uses the tab's reported pixel size, and
resizes wait until the server opens Display Control. Rejected NLA
passwords count as auth failures, so a saved one is asked again.
- The VNC viewer becomes DesktopView for both protocols; a small adapter
turns its keysyms and pointer masks into RDP scancodes, Unicode keys,
buttons and wheel events. For RDP, Paste Clipboard types the text.
- Profile editor RDP section, Quick Connect, badges, docs.
Verified against xrdp in the app: logon, desktop, input, and connections
through SOCKS5 and HTTP CONNECT relays. Not verified here: Windows/NLA
servers, the SSH tunnel route, and Display Control resizing (xrdp
0.9.24 never opened the channel).
SshConfig no longer has proxyPassword or totpSecret fields, so neither
can sit in clear in nexus.db or travel in an export. The proxy password
is a SecretKind (profile:<id>:proxy-password) edited like other saved
credentials and purged with the profile; the session manager hands both
it and the TOTP seed to the SSH client from the keychain/vault at
connect time.
This drops compatibility with Go-era profiles and .link/.nexus files
that carry these values inline: they are ignored on read and must be
re-entered (SEC-20261012-011).
- A JSON export without secrets now blanks login-script 'send' steps that
answer a password-like 'wait' prompt (SSH and Telnet), not just the
proxy password and inline TOTP seed (SEC-20261012-011, partial).
- Credential kinds were string literals in the session manager, profile
editor and secret store; they are now SecretKind with one ALL list, so
the profile-delete purge can't miss a kind (F19).
Moving proxyPassword out of the profile record is left open: it changes
.link/.nexus compatibility with the Go app and needs an owner decision.
Every shared lock was std::sync::Mutex with lock().unwrap() (~150 sites),
so a panic in any session's reader or callback poisoned the lock and the
next UI frame panicked too, dropping every open session. Locks are now
parking_lot::Mutex (no poisoning; already in the dependency tree), and a
panic hook reports worker panics as an error toast instead of the session
silently stopping (F3, F4).