From f4d7427725f1b501f634dd67a09209af932a242f Mon Sep 17 00:00:00 2001 From: CrepuscularIRIS Date: Mon, 11 May 2026 20:35:13 -0400 Subject: [PATCH 1/2] fix(settings): enable low-motion for Termius and SSH sessions (#1433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Termius users connecting over SSH experience screen flickering because the 120 FPS redraw rate issues rapid cursor-repositioning sequences that the SSH round-trip latency cannot flush fast enough. The same root cause was fixed for VS Code in #1356 (TERM_PROGRAM=vscode → low_motion). Extend apply_env_overrides() with three additional triggers: - TERM_PROGRAM=Termius — Termius desktop sets this on connect - SSH_CLIENT set — sshd exports client IP:port for all sessions - SSH_TTY set — sshd exports PTY path for interactive logins Any of the three is sufficient to cap the frame rate at 30 FPS and disable fancy animations, preventing the cursor-cycling flicker. Tests follow the lock_test_env() / term_program_test_guard() pattern already used for the vscode and NO_ANIMATIONS cases. Signed-off-by: CrepuscularIRIS --- crates/tui/src/settings.rs | 78 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/crates/tui/src/settings.rs b/crates/tui/src/settings.rs index aef6f4bf..57da1eb4 100644 --- a/crates/tui/src/settings.rs +++ b/crates/tui/src/settings.rs @@ -325,6 +325,21 @@ impl Settings { self.low_motion = true; self.fancy_animations = false; } + // Termius and SSH sessions exhibit the same 120 FPS flickering as + // vscode: the SSH round-trip latency means rapid cursor repositioning + // races ahead of what the remote renderer can flush, producing the + // cursor-cycling flicker reported in #1433. Enable low-motion + // automatically when: + // • TERM_PROGRAM=Termius (Termius desktop client sets this on connect) + // • SSH_CLIENT is set (sshd exports client IP:port for all sessions) + // • SSH_TTY is set (sshd exports PTY path for interactive logins) + if std::env::var("TERM_PROGRAM").as_deref() == Ok("Termius") + || std::env::var_os("SSH_CLIENT").map_or(false, |v| !v.is_empty()) + || std::env::var_os("SSH_TTY").map_or(false, |v| !v.is_empty()) + { + self.low_motion = true; + self.fancy_animations = false; + } } /// Save settings to disk @@ -971,6 +986,69 @@ mod tests { } } + #[test] + fn termius_term_program_forces_low_motion_on() { + let _g = term_program_test_guard(); + let prev = std::env::var_os("TERM_PROGRAM"); + // SAFETY: serialised by the guard. + unsafe { + std::env::set_var("TERM_PROGRAM", "Termius"); + } + let mut settings = Settings::default(); + assert!(!settings.low_motion, "default is animated"); + settings.apply_env_overrides(); + assert!( + settings.low_motion, + "TERM_PROGRAM=Termius must enable low_motion to prevent flickering (#1433)" + ); + assert!( + !settings.fancy_animations, + "TERM_PROGRAM=Termius must disable fancy_animations" + ); + // SAFETY: cleanup under the guard. + unsafe { + match prev { + Some(v) => std::env::set_var("TERM_PROGRAM", v), + None => std::env::remove_var("TERM_PROGRAM"), + } + } + } + + #[test] + fn ssh_session_forces_low_motion_on() { + let _g = term_program_test_guard(); + let prev_client = std::env::var_os("SSH_CLIENT"); + let prev_tty = std::env::var_os("SSH_TTY"); + for (var, val) in [ + ("SSH_CLIENT", "192.168.1.100 50000 22"), + ("SSH_TTY", "/dev/pts/0"), + ] { + // SAFETY: serialised by the guard. + unsafe { + std::env::remove_var("SSH_CLIENT"); + std::env::remove_var("SSH_TTY"); + std::env::set_var(var, val); + } + let mut s = Settings::default(); + s.apply_env_overrides(); + assert!( + s.low_motion, + "{var}={val:?} must enable low_motion to prevent flickering in SSH sessions (#1433)" + ); + } + // SAFETY: cleanup under the guard. + unsafe { + std::env::remove_var("SSH_CLIENT"); + std::env::remove_var("SSH_TTY"); + if let Some(v) = prev_client { + std::env::set_var("SSH_CLIENT", v); + } + if let Some(v) = prev_tty { + std::env::set_var("SSH_TTY", v); + } + } + } + // ──────────────────────────────────────────────────────────────────────── // TuiPrefs tests // ──────────────────────────────────────────────────────────────────────── From 4a1d2d1ef36fac0e39ad79e17d16f58a60ff5d6d Mon Sep 17 00:00:00 2001 From: CrepuscularIRIS Date: Mon, 11 May 2026 20:37:32 -0400 Subject: [PATCH 2/2] test(settings): assert fancy_animations=false in ssh_session test (#1433) Pass 1 correctness review found the SSH test asserted low_motion but not fancy_animations. Both fields are always co-set in every env-override path; add the missing assertion to complete the contract verification. Signed-off-by: CrepuscularIRIS --- crates/tui/src/settings.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/tui/src/settings.rs b/crates/tui/src/settings.rs index 57da1eb4..7afb672a 100644 --- a/crates/tui/src/settings.rs +++ b/crates/tui/src/settings.rs @@ -1035,6 +1035,10 @@ mod tests { s.low_motion, "{var}={val:?} must enable low_motion to prevent flickering in SSH sessions (#1433)" ); + assert!( + !s.fancy_animations, + "{var}={val:?} must disable fancy_animations in SSH sessions (#1433)" + ); } // SAFETY: cleanup under the guard. unsafe {