七亿批量远程管理工具(RdpManager)更新到 v1.6.4。这次就干了一件事:把 SSH 终端从原来的 RichTextBox 换成自己写的一个终端模拟器。

起因是有个用户提了一句「SSH 里没法用 vi 编辑文件,你这终端不完整」。我一开始还有点不服气,自己连上去试——敲命令、看输出都正常,但一进 vi / nano / top / htop 这种全屏程序,界面直接花掉,光标到处乱跳,根本没法用。确实是我理亏。

为什么 RichTextBox 做不出全屏程序

老终端本质上是「一个能打字的富文本框」:服务器回什么就往框里追加什么,再自己画一行假的输入行。看日志、敲命令没毛病,但它压根不理解终端控制序列。

vi 这种程序不是「一行一行往下打」,而是直接把整块屏幕画出来:

  • 往某个坐标写字符(ESC[row;colH
  • 移动光标、擦掉一块区域
  • 切换「备用屏幕」(ESC[?1049h)——全屏画面其实画在另一张画布上,退出时再切回来
  • 改颜色、加粗、反显

这些控制码丢进 RichTextBox 全是天书,vi 一进去自然就废了。

方案:xterm.js 还是自己写

做真终端有两条路:

  • 引 xterm.js + WebView2,现成的终端模拟器,功能最全
  • 自己写一个 VT100 模拟器,用 WinForms 原生画

第一条五分钟就能跑起来,代价是得往里塞一个几百 MB 的 WebView2 运行时。我们这软件一直主打「绿色免安装、单文件、解压即用」,为一个终端把体积撑到几百 MB,等于把定位给改了。纠结了一阵,还是选了第二条。

自己写就拆成两块,刻意把「逻辑」和「界面」分开:

  • TerminalBuffer.cs:纯逻辑的屏幕缓冲,维护一张字符网格(每格存字符 + 前景色 + 背景色 + 属性),负责解析 ANSI/VT100 控制序列——SGR 颜色、光标移动、清屏、滚动、备用屏切换、DSR 应答这些
  • TerminalView.cs:自绘控件,把缓冲里的字符网格画到屏幕上,处理键盘、鼠标、粘贴

分开的好处是逻辑那层能脱离界面直接跑单元测试。我拿真实服务器的原始字节流(就是格式化磁盘那种带进度动画的)喂进去,19 个用例全绿了才敢合。

最大的一个坑:键敲进去了,服务器收不到

终端在本地测都正常,一连真机——敲键盘没反应,粘贴也没反应。

先确认「键盘 → 控件」这段。用 Xvfb + xdotool 模拟按键,在控件里打日志,看到事件确实进来了。那就是「控件 → SSH 通道」这段的问题。

一路查到 SSH.NET 的 ShellStream:

_shell.Write(data, 0, data.Length); // 只写进内部缓冲区!

Write(byte[]) 只往内部缓冲里写,缓冲区满了才真正发出去;而 Write(string) 内部会自动 Flush。(旧代码用的是 WriteLine,所以这个问题一直没露头。)

加一行就好:

_shell.Write(data, 0, data.Length);
_shell.Flush(); // 立即发出去

就这么一行,前前后后对比了好几次才定位到——因为「发送层」不像 UI 那样能直接截图看见,只能靠日志一点点试。

顺手改的几个小地方

  • 字号能调了:终端工具栏加了个「字号」下拉框,嫌字小嫌字大自己挑
  • 加了个小企鹅图标:SSH 终端窗口的标题栏和任务栏,在 root@ 前面挂了个企鹅,一眼能认出这是 SSH 会话
  • 系统托盘:SSH 终端最小化后收进托盘(还是那只企鹅),连接不断,双击图标还原窗口

已知的边界

自研终端毕竟不是 xterm 的完整实现,有几个已知限制,先记在这:

  • 窗口缩放不会通知远端改列宽,远端还以为屏幕是原来那么宽(SIGWINCH 那套没做)
  • 鼠标事件没转发给远端,所以 vi 的鼠标模式、htop 里点击排序这些用不了
  • 256 色会近似映射到 16 色

这些后面慢慢补,常用的 vi / nano / top / htop 已经能正常干活了。

小结

引 xterm.js 是省事,但会把「绿色单文件」这个卖点弄没了;自己写多花了点时间,换回来的是一个干净、可控、不依赖外部运行时的终端。写完整终端模拟器不算小事,磨了挺久。至少「SSH 打不开 vi」这个被念叨了很久的问题,算是翻篇了——当然,下一个版本又会被挑出什么毛病,那就不好说了。

下载:https://rdp.7ehl.com

v1.6.4 更新日志:

SSH 终端重写为真正的终端模拟器:支持 vi / vim / nano / top / htop 等全屏程序,整屏刷新、方向键与功能键正常
SSH 终端新增「字号」下拉框,可自选终端字体大小
SSH 终端窗口标题栏 / 任务栏显示小企鹅图标;新增系统托盘图标,最小化后收进托盘、连接不中断,双击企鹅图标还原窗口
bt.jpg