From 5cb9ffaf9db406898d782c5c4f78c41b50f741cf Mon Sep 17 00:00:00 2001 From: Jaap-Jan de Wit | DodoTech Date: Sat, 1 Aug 2026 22:45:11 +0200 Subject: [PATCH] Stop the phone's terminal being a page you can pinch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The terminal on Android could be pinch-zoomed and double-tap zoomed, which a terminal must not be: the grid is sized to the window by the fit addon and the column count is told to the remote, so zooming makes the visible width disagree with what the far end is wrapping to, and puts the cell under your thumb somewhere other than where you tapped. Three things, and the first is the one that made it look worst. The page had no viewport meta tag at all. An Android WebView with no viewport lays out at a notional 980 CSS pixels and scales the result down to fit, so a terminal built to fill the window was drawn small and then offered as something to zoom around in. width=device-width makes one CSS pixel one layout pixel, which is what the fit addon has been assuming all along and what a desktop WebView gives without being asked. Second, the gestures. touch-action: pan-y keeps the only one a terminal wants — dragging the scrollback — and refuses pinch-zoom and the double-tap zoom that fired on every attempt to place a cursor. text-size-adjust stops Android's own font inflation, which resizes text it judges too small without telling the page and leaves the characters no longer matching the grid that was measured. Third, the knob that actually disables zoom: BuiltInZoomControls on the WebView. user-scalable=no is in the viewport tag for completeness and does nothing on its own — Blink has ignored it since Chrome 48 and WebView follows Blink. That is worth knowing before somebody removes the C# and trusts the meta tag. The page and stylesheet are shared with the desktop head, deliberately, and none of it costs anything there: a desktop WebView already lays out at device width, and a Windows touchscreen should not be pinch-zooming a terminal either. This does not settle whether the phone should keep a browser-based terminal at all, which is the question actually asked. It does remove the reason it was asked, and every complaint about this renderer so far has turned out to be configuration rather than the approach. Verified by building the head in Debug and Release and by the desktop layout suite, which draws the same assets. Not verified on a device — nothing in this head is; see docs/android-port.md. Co-Authored-By: Claude Opus 5 (1M context) --- .../Views/TerminalScreen.axaml.cs | 31 ++++++++++++++++++- .../WebAssets/terminal.css | 14 +++++++++ .../WebAssets/terminal.html | 17 ++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/DodoSSH.Client.Android/Views/TerminalScreen.axaml.cs b/src/DodoSSH.Client.Android/Views/TerminalScreen.axaml.cs index 9af3e31..40b562d 100644 --- a/src/DodoSSH.Client.Android/Views/TerminalScreen.axaml.cs +++ b/src/DodoSSH.Client.Android/Views/TerminalScreen.axaml.cs @@ -5,6 +5,7 @@ using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Markup.Xaml; using Avalonia.Media; +using Avalonia.Platform; using DodoSSH.Client.Shell.ViewModels; @@ -70,16 +71,44 @@ internal sealed partial class TerminalScreen : UserControl BuildAccessoryRow(); + var renderer = this.FindControl("Renderer")!; + + renderer.EnvironmentRequested += OnRendererEnvironmentRequested; + // The URL is only known once the loopback listener has bound a port, so it cannot be set in XAML. DataContextChanged += (_, _) => { if (DataContext is MainWindowViewModel shell) { - this.FindControl("Renderer")!.Source = shell.TerminalPageUrl; + renderer.Source = shell.TerminalPageUrl; } }; } + /// + /// Takes the browser gestures back off a surface that is not a web page. + /// + /// + /// + /// A terminal is a fixed grid that the fit addon sizes to the window. Pinch-zoom breaks that in both + /// directions at once: the visible width stops matching the column count the remote was told about, so + /// wrapping goes wrong, and the part of the grid under the thumb is no longer the part that gets the + /// tap. It is the WebView's own zoom rather than anything the page asked for. + /// + /// + /// This is the knob that works. user-scalable=no in the page's viewport tag does not: Blink has + /// ignored it since Chrome 48 for accessibility reasons and WebView follows Blink. The page still + /// carries the viewport tag, for the layout width rather than the zoom — see WebAssets/terminal.html. + /// + /// + private static void OnRendererEnvironmentRequested(object? sender, EventArgs e) + { + if (e is AndroidWebViewEnvironmentRequestedEventArgs android) + { + android.BuiltInZoomControls = false; + } + } + private void BuildAccessoryRow() { var row = this.FindControl("AccessoryKeys")!; diff --git a/src/DodoSSH.Client.Shell/WebAssets/terminal.css b/src/DodoSSH.Client.Shell/WebAssets/terminal.css index 27d776f..06c5a47 100644 --- a/src/DodoSSH.Client.Shell/WebAssets/terminal.css +++ b/src/DodoSSH.Client.Shell/WebAssets/terminal.css @@ -23,6 +23,20 @@ body { background: var(--dodo-background); color: var(--dodo-foreground); font-family: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace; + + /* + Touch rules, and they only ever matter on the phone — a desktop WebView has no gestures to give + away. pan-y keeps the one gesture a terminal wants, dragging the scrollback, and refuses the two + it must not have: pinch-zoom, and the double-tap zoom that would otherwise fire on every attempt + to place the cursor. + + text-size-adjust stops Android's own font inflation. It resizes text it judges too small without + telling the page, which in a terminal means characters that no longer match the cell grid the fit + addon measured — the rows stop lining up and the columns are wrong by a character or two. + */ + touch-action: pan-y; + -webkit-text-size-adjust: 100%; + text-size-adjust: 100%; } #root { diff --git a/src/DodoSSH.Client.Shell/WebAssets/terminal.html b/src/DodoSSH.Client.Shell/WebAssets/terminal.html index 8003722..ff0b9b0 100644 --- a/src/DodoSSH.Client.Shell/WebAssets/terminal.html +++ b/src/DodoSSH.Client.Shell/WebAssets/terminal.html @@ -4,6 +4,23 @@ DodoSSH terminal + + +