原标题:开源一个Flutter编写的完整终端模拟器
上次开源了一个简易的终端模拟器,我也知道并不是标准的,但自己也一直在用,然后就发现了一些棘手的问题,就又跑去研究了一些完整终端的源码,termux,Android Terminal,最后成功的将他们的原理在Flutter实现
其实这个源也可能会是你学习使用dart:ffi的一个例子,其中用到的char **,也就是二级指针的传递在也很少能在官方的example中也很难找到直接的例子,也是我处理这种类型遇见的比较麻烦的坑,主要就是没有案例。我将termux的C语言部分完全重构以供Flutter使用,由于UI框架使用的Flutter经过测试可以在Macos上跑起来!!!
Process类的stdout是哪里来的?
自己在使用中遇见了这个棘手的问题,还是由于经验不够,还去知乎上提了我遇见的问题,经过与同学的探讨后(死皮赖脸问人家),可以知道Process中的stdout是来自于pipe(管道),也可以看到stdout也有pipe这个方法,而管道是存在缓冲的,举个
使用
cp-rv sourceDir targetDir
命令,由于开启了-v参数,所以在标准终端中,cp命令会一行一行打印出正在复制的文件,而当用dart的Process去执行这样的操作,你在对stdout的监听中并不会收到一次一行的回调,而是一次一堆的回调,那就是由于管道是存在缓冲机制的,达到缓冲上限后才能拿到一次,或者程序结束后,缓冲区未满也能拿到。
我们再切换到标准终端模拟器
cp -rv sourceDir targetDir | xargs echo
我们在终端中也使用管道,通过xargs将其打印出来,这个时候会发现,打印的东西跟次数,跟dart中stdout的回调是一样的,不止dart,包括java中runtime拿到的输入流,也无法拿到无缓冲的输出.
终端与管道的缓冲差别
终端也具有缓冲,终端为行缓冲,管道为全缓冲,行缓冲中,遇见换行符n即可向终端中输出一次,或者主动在C语言中调用fflush方法,会将已经在缓冲区的内容输出一次,如果没有以上两个条件,就只能等到缓冲区满1024个字节,才能输出一次
标准终端又是怎么做到拿到行缓冲的输出的?
我能想到的最快的方法就是去看一些标准终端的开源库,现在比较优秀有termux,跟Android Terminal,termux可以说是目前安卓上最强大的终端了,有大量的可扩展资源,我就直接clone下来,从manifest中找到主类,从Activity中oncreate中一点一点看,还是花了挺多时间,毕竟termux还是比较大型的储存库,也有注释,但始终找不到关键的地方,能够在Flutter实现的地方,最后定位到了UI中获取输入,包括将输出同步到屏幕,这一系列都指向了JNI,也就是一个java到c/c++的一个通道,我也是从这才开始知道项目中的那个C语言是什么时候用的了。
标准终端实现原理
这种终端称伪终端(pty)
必须先看一波来自互联网的科普
伪终端(pseudo terminal,有时也被称为 pty)是指伪终端 master 和伪终端 slave 这一对字符设备。其中的 slave 对应 /dev/pts/ 目录下的一个文件,而 master 则在内存中标识为一个文件描述符(fd)。伪终端由终端模拟器提供,终端模拟器是一个运行在用户态的应用程序。
Master 端是更接近用户显示器、键盘的一端,slave 端是在虚拟终端上运行的 CLI(Command Line Interface,命令行接口)程序。Linux 的伪终端驱动程序,会把