Files
DodoSSH/src/DodoSSH.Client.Shell/Keyboard/TerminalKey.cs
T
jaap-jan 367cebae9b
ci / build and test (push) Successful in 4m52s
ci / desktop nightly (push) Failing after 58s
ci / android head (push) Successful in 5m59s
ci / api image (push) Successful in 8s
Android: a customisable key bar, a quick-access keyboard, a long-press cursor gesture, and a side panel on tablets
The key bar above the software keyboard is now the user's own: which keys, in what order, up to sixteen
in two rows that never scroll. Ctrl and Alt latch across the bar and the phone's keyboard, and Ctrl or Alt
with a cursor key sends the modified CSI form. Seven predefined groups (essentials, cursor, control,
symbols, tmux, vim, function) and keys of one's own, written in \e / ^C notation, are arranged on a
customise sheet that saves as it goes.

A grid button swaps the software keyboard for a quick-access keyboard of the same height, with KEYS,
SNIPPETS, HISTORY and APPEARANCE tabs; it reopens to the tab last used. Snippets go through the shell's
existing InsertSnippet. History keeps a typed command only once the remote has echoed it, so nothing
typed at a password prompt is ever recorded; it is memory-only and LOCK clears it. Each session pump now
keeps its last kilobyte of output and the alternate-screen state for that test (OutputTail).

Long-press and drag on the terminal sends arrow keys, as a joystick with three speeds by distance;
scrolling is unchanged and there is no mode switch. It takes the touch before Chromium's own long press,
so a held finger no longer pastes; paste is on the bar by default instead. A switch in APPEARANCE turns
the gesture off. The text-size buttons moved from the bar to APPEARANCE.

On wide screens the same tabs open as a side panel beside the terminal, toggled from the top bar, with
the bar's own keys repeated at the top of KEYS while the bar stands down.

The model, gesture arithmetic, echo reading and history are in DodoSSH.Client.Shell and covered by
TerminalKeyboardTests and OutputTailTests. Builds clean; not yet exercised on a device, because the
emulator's install is locked behind a passphrase that was not recorded.
2026-10-03 14:29:22 +02:00

56 lines
2.6 KiB
C#

namespace DodoSSH.Client.Shell.Keyboard;
/// <summary>What pressing a terminal key does.</summary>
internal enum TerminalKeyKind
{
/// <summary>Sends its bytes, after any latched modifier has had its say.</summary>
Send,
/// <summary>Latches Ctrl for the next key, and releases itself after it.</summary>
Control,
/// <summary>Latches Alt for the next key, and releases itself after it.</summary>
Alt,
/// <summary>Pastes the clipboard, through the same path a right-click on the desktop takes.</summary>
Paste,
}
/// <summary>
/// One key the phone's own keyboard does not have.
/// </summary>
/// <remarks>
/// <para>
/// A record for the convenience of construction and nothing else: two keys are the same key when their
/// <see cref="Id"/> is, which is what the bar stores. The byte array would make record equality reference
/// equality anyway, so nothing here leans on it.
/// </para>
/// <para>
/// <see cref="IsWide"/> exists for the labels that are words — <c>ctrl+B %</c> — which want two cells on
/// the quick-access keyboard where an arrow wants one. It is a fact about the label rather than a layout
/// choice made per screen, so it lives here.
/// </para>
/// </remarks>
/// <param name="Id">Stable across releases; the bar and the group order are stored by it.</param>
/// <param name="Label">What the key says.</param>
/// <param name="Kind">What pressing it does.</param>
/// <param name="Bytes">What it sends, for <see cref="TerminalKeyKind.Send"/>. Empty otherwise.</param>
/// <param name="Hint">What it is for, for the customise screen and for accessibility.</param>
internal sealed record TerminalKey(string Id, string Label, TerminalKeyKind Kind, byte[] Bytes, string Hint)
{
/// <summary>Whether the label is long enough to want a double-width cell.</summary>
internal bool IsWide => Label.Length > 4;
/// <summary>Whether this is one of the two latching modifiers.</summary>
internal bool IsModifier => Kind is TerminalKeyKind.Control or TerminalKeyKind.Alt;
/// <summary>Whether this key was added by the person using it rather than shipped.</summary>
internal bool IsCustom => Id.StartsWith(TerminalKeyCatalog.CustomPrefix, StringComparison.Ordinal);
}
/// <summary>A named set of keys on the quick-access keyboard.</summary>
/// <param name="Id">Stable across releases; the order and the hidden set are stored by it.</param>
/// <param name="Title">The caption above the keys.</param>
/// <param name="Keys">The keys, in the order they are drawn.</param>
internal sealed record TerminalKeyGroup(string Id, string Title, IReadOnlyList<TerminalKey> Keys);