在C / C ++中编写“真正的”交互式terminal程序,如vim,htop,…不带ncurses

不,我不想使用ncurses ,因为我想了解terminal是如何工作的,并且乐于自己编程。 :)它不必是便携式的,它只能在基于Linux xterm的terminal仿真器上工作。

我想要做的是编写一个像htop和vim这样的交互式terminal应用程序。 我的意思不是看起来像盒子或设定颜色的字符的输出,这是微不足道的; 也使内容适合窗口大小。 我需要的是

  1. 如何让鼠标交互像点击一个字符和滚动鼠标滚轮(当鼠标是在一个特定的字符)来实现滚动[ 编辑:当然在terminal模拟器 ],和

  2. 如何完全保存和恢复父进程的输出,并从输出中分离我的打印,所以在离开我的应用程序之后,除了我在shell中input的命令之外,应该在那里,就像运行htop并退出时一样:没有任何东西可见从这个应用程序了。

我真的不想使用ncurses。 但是,当然,如果你知道ncurses的哪个部分负责这些任务,欢迎告诉我在源代码的哪里可以find它,所以我会研究它。

我有点困惑。 你说的是“terminal应用程序”,比如vim; terminal应用程序不会获取鼠标事件,也不会响应鼠标。

如果你正在讨论在xterm中运行的真正的terminal应用程序,需要注意的重要问题是很多可移植性问题都与terminal有关,而不是操作系统。 terminal通过发送不同的转义序列来控制。 哪些人依靠terminal做什么? ANSI转义代码现在相当普遍,但请参阅http://en.wikipedia.org/wiki/ANSI_escape_code 。 例如,这些通常被xterm所理解。

您可能需要在开始和结束时输出附加的序列以进入和退出“全屏”模式; 这是xterm所必需的。

最后,你必须在input/输出级别做一些特殊的事情,以确保你的输出驱动不会添加任何字符(例如,将简单的LF转换为CRLF),并确保input不回显,是透明的,并立即返回。 在Linux下,这是使用ioctl完成的。 (再次,不要忘了在完成时恢复它。)

为了操纵terminal,你必须使用控制装置 。 不幸的是,这些代码取决于您正在使用的特定terminal。 这就是为什么terminfo(以前是termcap)首先存在的原因。

你不会说如果你想使用terminfo或不。 所以:

  • 如果您将使用terminfo,它将为您提供terminal支持的每个操作的正确控制顺序。
  • 如果您不使用terminfo …那么您必须手动编码您想要支持的每种terminaltypes的每个操作。

正如你想要学习的目的,我会在第二个详细说明。

您可以从环境variables$TERM发现您正在使用的terminaltypes。 在linux中,最常用的是terminal模拟器(XTerm,gnome-terminal,konsole)的xterm和虚拟terminal的linux (当X没有运行的时候)。

您可以使用命令tput轻松发现控制序列。 但是当输出在控制台上打印时,他们会立即应用,所以如果你想真的看到他们,使用:

 $ TERM=xterm tput clear | hd 00000000 1b 5b 48 1b 5b 32 4a |.[H.[2J| $ TERM=linux tput clear | hd 00000000 1b 5b 48 1b 5b 4a |.[H.[J| 

也就是说,要清除xterm的屏幕,必须输出ESC [ H ESC [ 2J在xterm中,但ESC [ H ESC [ J在linuxterminal中。

关于你询问的特定命令,你应该仔细阅读man 5 terminfo 。 那里有很多信息。

虽然这个问题有点老,但我想我应该分享一个简单的例子,说明如何在不使用ncurses的情况下做到这一点,这并不难,但我相信它不会像移植那样简单。

这段代码在原始模式下设置stdin,切换到备用缓冲区屏幕(在启动之前保存terminal的状态),启用鼠标跟踪并在用户点击某处时打印button和坐标。 用Ctrl + C退出后,程序将恢复terminalconfiguration。

 #include <stdio.h> #include <unistd.h> #include <termios.h> int main (void) { unsigned char buff [6]; unsigned int x, y, btn; struct termios original, raw; // Save original serial communication configuration for stdin tcgetattr( STDIN_FILENO, &original); // Put stdin in raw mode so keys get through directly without // requiring pressing enter. cfmakeraw (&raw); tcsetattr (STDIN_FILENO, TCSANOW, &raw); // Switch to the alternate buffer screen write (STDOUT_FILENO, "\e[?47h", 6); // Enable mouse tracking write (STDOUT_FILENO, "\e[?9h", 5); while (1) { read (STDIN_FILENO, &buff, 1); if (buff[0] == 3) { // User pressd Ctr+C break; } else if (buff[0] == '\x1B') { // We assume all escape sequences received // are mouse coordinates read (STDIN_FILENO, &buff, 5); btn = buff[2] - 32; x = buff[3] - 32; y = buff[4] - 32; printf ("button:%u\n\rx:%u\n\ry:%u\n\n\r", btn, x, y); } } // Revert the terminal back to its original state write (STDOUT_FILENO, "\e[?9l", 5); write (STDOUT_FILENO, "\e[?47l", 6); tcsetattr (STDIN_FILENO, TCSANOW, &original); return 0; } 

注意:这对于超过255列的terminal将无法正常工作。

我发现的转义序列的最佳参考是这个和这个 。