Terminal: - cache font variants + single reusable brush in render hot path; resolve cell colors once per cell; dispose GDI resources - scrollback: List+RemoveRange -> O(1) ring buffer - follow new output only when already at bottom (no more yank-to-bottom while reading history) - alt-screen mouse wheel -> arrow keys (vim/htop scroll) - answer DSR (ESC[5n/6n) and DA (ESC[c) queries so TUIs no longer hang - remove dead code in OnKeyPress Encoding correctness (garbled CJK across chunk boundaries): - stateful UTF-8 Decoder in TTLInterpreter.OnData, SerialBridgeServer rx forwarding, and SessionLogger.Write Sessions: - ShellChannel: free proc-thread attribute list, close hProcess, notify '[ETTerms] shell process exited' in the tab - SshChannel: surface ErrorOccurred / ShellStream.Closed in the tab; Write no longer throws into the UI thread on a dead connection PDU: - new shared ETTerms.PduCore project replaces the two drifted copies of PduController (GUI + PduMcp); logging via injected delegates - batched SNMP GET (GetAllPortsStatus): 12-port poll is 3 UDP round-trips instead of 36 (StatusView polling + pdu_status tool) Scripting: - cap TTL receive buffer at 1MB; skip re-scan in wait when buffer length unchanged - merge ScriptRunner.RunAsync/RunGroupAsync; new TtlScript helper dedups script picking + group-command checks (3 copies -> 1) Misc: - ConnectionStore: parse LastUsedUtc with InvariantCulture/RoundtripKind - version 0.4.0; About changelog; CLAUDE.md notes (intentional group barrier behavior, SSH.NET reflection resize caveat) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
62 lines
2.3 KiB
C#
62 lines
2.3 KiB
C#
using System.Text;
|
|
using System.Windows.Forms;
|
|
|
|
namespace ETTerms.Terminal;
|
|
|
|
/// <summary>把特殊鍵(方向鍵 / Fn / Home/End… / Enter/Tab/Backspace/Esc)對應成終端機 byte 序列。
|
|
/// 一般可列印字元回傳 null,交給 KeyPress 處理。</summary>
|
|
public static class TerminalInput
|
|
{
|
|
public static byte[]? Map(KeyEventArgs e, bool appCursor)
|
|
{
|
|
// Enter:一般送 CR(\r) 視為「送出」;Shift+Enter 送 LF(\n),讓 PSReadLine / Kiro 等
|
|
// 視為「插入換行」以便輸入多行指令,而不是直接送出。
|
|
if (e.KeyCode == Keys.Enter)
|
|
return new[] { (byte)(e.Shift ? '\n' : '\r') };
|
|
|
|
string? seq = e.KeyCode switch
|
|
{
|
|
Keys.Up => appCursor ? "\x1bOA" : "\x1b[A",
|
|
Keys.Down => appCursor ? "\x1bOB" : "\x1b[B",
|
|
Keys.Right => appCursor ? "\x1bOC" : "\x1b[C",
|
|
Keys.Left => appCursor ? "\x1bOD" : "\x1b[D",
|
|
Keys.Home => "\x1b[H",
|
|
Keys.End => "\x1b[F",
|
|
Keys.Insert => "\x1b[2~",
|
|
Keys.Delete => "\x1b[3~",
|
|
Keys.PageUp => "\x1b[5~",
|
|
Keys.PageDown => "\x1b[6~",
|
|
Keys.Tab => "\t",
|
|
Keys.Escape => "\x1b",
|
|
Keys.Back => "\x7f",
|
|
Keys.F1 => "\x1bOP",
|
|
Keys.F2 => "\x1bOQ",
|
|
Keys.F3 => "\x1bOR",
|
|
Keys.F4 => "\x1bOS",
|
|
Keys.F5 => "\x1b[15~",
|
|
Keys.F6 => "\x1b[17~",
|
|
Keys.F7 => "\x1b[18~",
|
|
Keys.F8 => "\x1b[19~",
|
|
Keys.F9 => "\x1b[20~",
|
|
Keys.F10 => "\x1b[21~",
|
|
Keys.F11 => "\x1b[23~",
|
|
Keys.F12 => "\x1b[24~",
|
|
_ => null
|
|
};
|
|
return seq is null ? null : Encoding.ASCII.GetBytes(seq);
|
|
}
|
|
|
|
/// <summary>滾輪 → 上/下方向鍵序列(alt screen 用,每格 3 行)。notches 正值往上。</summary>
|
|
public static byte[]? WheelArrows(int notches, bool appCursor)
|
|
{
|
|
if (notches == 0) return null;
|
|
string one = notches > 0
|
|
? (appCursor ? "\x1bOA" : "\x1b[A")
|
|
: (appCursor ? "\x1bOB" : "\x1b[B");
|
|
int count = Math.Abs(notches) * 3;
|
|
var sb = new StringBuilder(one.Length * count);
|
|
for (int i = 0; i < count; i++) sb.Append(one);
|
|
return Encoding.ASCII.GetBytes(sb.ToString());
|
|
}
|
|
}
|