===============================================================================
* 像 CCE, xterm, vcongui 等程序, 需要截获字符应用程序的输出, 以便在自己的设备上
绘制终端输出.
* 像 xterm, vcongui 等程序, 还要将自己的输入转换成字符应用程序能够理解的输入.
* 利用伪终端, 上述这些程序就可以对字符应用程序的终端输入和输出进行过虑, 从而
实现与普通终端一样的输入和输出处理.
===============================================================================
===============================================================================
* 伪终端由两部分组成: 主控终端和从属终端, 分别由两个进程处理, 这两个进程往往
是父子进程.
* 父进程打开伪终端的主控终端, 然后调用 fork 派生子进程.
* 子进程建立新的会话, 并打开对应的从属终端, 并将该从属终端复制为标准输入,
标准输出和标准错误.
* 子进程调用 exec 执行新的程序, 该从属终端就形成了新会话的控制终端.
* 对于子进程来说, 从属终端就是它们的标准输入, 标准输出和标准错误, 同时也是
一个终端设备. 因此, 可以采用 6.6 小节中的终端属性进行控制, 但因为并不是
真正的终端, 因此波特率, 线路控制函数等等将被忽略.
* 任何写入主控终端的数据将成为从属终端的输入; 任何写入从属终端的数据将成为
主控终端的输入. 这样, 主控终端上的进程 (父进程) 就能够为从属终端生成输入,
而且还能够处理从属终端上的输出.
===============================================================================
===============================================================================
int TermFork (PCONINFO con, PCHILDINFO pChildInfo)
{
-------------------------------------------------------------------------------
* vcongui 的窗口线程打开主控终端:
char ls, ln;
// 1. Looking for unused PTY/TTY Master/Slave
/* Open PTY(master) from [pqrs][5-F], in fact p-z is ok? */
/* MasterPty: pty[p-z][5-f] pty[a-e][5-f] 16*16=256
SlaveTty: tty[p-z][5-f] tty[a-e][5-f] 16*16=256 */
for (ls = 'p'; ls <= 's'; ls ++) {
for (ln = 5; ln <= 0xF; ln ++) {
sprintf(con->ptyName, "/dev/pty%1c%1x", ls, ln);
if ((con->masterPty = open (con->ptyName, O_RDWR)) >= 0)
break;
}
if (con->masterPty >= 0)
break;
}
if (con->masterPty < 0) {
myMessage ("can not get master pty!\n");
Perror (con->ptyName);
return -1;
}
con->ptyName[5] = 't'; /* slave tty */
-------------------------------------------------------------------------------
* 调用 fork 派生子进程
if ((con->childPid = fork()) < 0) {
Perror ("fork");
return -1;
}
else if (con->childPid == 0) {
// in child process
int errfd, slavePty;
FILE *errfp;
struct winsize twinsz;
errfd = dup (2);
errfp = fdopen (errfd, "w");
/* I'm child, make me process leader */
setsid ();
// close any no used fd here!!
close (con->masterPty);
/* Open TTY(slave) */
if ((slavePty = open (con->ptyName, O_RDWR)) < 0)
PerrorExit (con->ptyName);
// Set new TTY's termio with parent's termio.
tcsetattr (slavePty, TCSANOW, (struct termios*)GetOriginalTermIO ());
// Set new terminal window size
twinsz.ws_row = con->rows;
twinsz.ws_col = con->cols;
ioctl (slavePty, TIOCSWINSZ, &twinsz);
/* Set std??? to pty, dup2 (oldfd, newfd) */
dup2 (slavePty, 0);
dup2 (slavePty, 1);
dup2 (slavePty, 2);
close (slavePty);
// execute the shell
ChildStart (con, errfp, pChildInfo->startupMessage,
pChildInfo->startupStr,
pChildInfo->execProg,
pChildInfo->execArgs);
}
return 1; // parent process
}
-------------------------------------------------------------------------------
* 窗口欧线程在适当的时候读取主控终端上的数据, 即子进程写入从属终端的数据.
void ReadMasterPty (PCONINFO con)
{
BYTE buff [BUFSIZ + 1];
int nRead;
struct pollfd fd = {con->masterPty, POLLIN, 0};
int ret;
ret = poll (&fd, 1, 10); // about 0.01s
if (ret == 0) {
return;
}
else if (ret > 0) {
if ((nRead = read (con->masterPty, buff, BUFSIZ)) > 0) {
VtWrite (con, buff, nRead); /* 后面讲解 */
TextRefresh (con, true); /* 后面讲解 */
}
}
}
-------------------------------------------------------------------------------
* 在窗口的主消息循环中读取主控终端, 并处理窗口消息
...
// Enter message loop.
do {
// It is time to read from master pty, and output.
ReadMasterPty (pConInfo);
if (pConInfo->terminate)
break;
while (HavePendingMessage (hMainWnd)) {
if (!GetMessage (&Msg, hMainWnd))
break;
DispatchMessage (&Msg);
}
} while (TRUE);
...
-------------------------------------------------------------------------------
* 将用户在窗口中的输入写入主控终端.
void HandleInputChar (PCONINFO con, WPARAM wParam, LPARAM lParam)
{
u_char ch;
if (HIBYTE (wParam)) {
u_char buff [2];
buff [0] = LOBYTE (wParam);
buff [1] = HIBYTE (wParam);
write (con->masterPty, buff, 2);
}
else {
ch = LOBYTE (wParam);
write (con->masterPty, &ch, 1);
}
}
void HandleInputKeyDown (PCONINFO con, WPARAM wParam, LPARAM lParam)
{
char buff [50];
con->kinfo.state = lParam;
con->kinfo.buff = buff;
con->kinfo.pos = 0;
handle_scancode_on_keydown (wParam, &con->kinfo);
if (con->kinfo.pos != 0)
write (con->masterPty, buff, con->kinfo.pos);
}
void HandleInputKeyUp (PCONINFO con, WPARAM wParam, LPARAM lParam)
{
char buff [50];
con->kinfo.state = lParam;
con->kinfo.buff = buff;
con->kinfo.pos = 0;
handle_scancode_on_keyup (wParam, &con->kinfo);
if (con->kinfo.pos != 0)
write (con->masterPty, buff, con->kinfo.pos);
con->kinfo.oldstate = con->kinfo.state;
}
-------------------------------------------------------------------------------
* 在窗口的消息处理函数中处理用户输入
...
case MSG_KEYUP:
HandleInputKeyUp (pConInfo, wParam, lParam);
break;
case MSG_KEYDOWN:
HandleInputKeyDown (pConInfo, wParam, lParam);
break;
case MSG_CHAR:
HandleInputChar (pConInfo, wParam, lParam);
break;
...
-------------------------------------------------------------------------------
* 在 VtWrite 函数中处理终端的 ESC 序列
o 解释 ESC 序列含义: 位置, 写入字符, 滚屏, 删除字符等.
o 更新终端窗口的数据, 包括指定位置 (行, 列) 上的字符及其属性 (颜色等)
-------------------------------------------------------------------------------
* 在 TextRefresh 函数中更新窗口显示
===============================================================================
![]()