=============================================================================== * 像 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 函数中更新窗口显示 ===============================================================================