繁体   English   中英

在没有 ncurses 的情况下用 C/C++ 编写“真正的”交互式终端程序,如 vim、htop 等

[英]Writing a “real” interactive terminal program like vim, htop, … in C/C++ without ncurses

不,我不想使用ncurses ,因为我想了解终端的工作原理并在自己的编程中获得乐趣。 :) 它不必是可移植的,它只能在基于 linux xterm 的终端仿真器上工作。

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

  1. 如何获得鼠标交互,例如单击字符并滚动鼠标滚轮(当鼠标位于特定字符上时)以实现滚动[编辑:当然是在终端模拟器中],以及

  2. 如何完全保存和恢复父进程的输出并将我的打印与其输出分开,所以在离开我的应用程序之后,除了我在 shell 中输入的命令之外,应该在那里,就像运行 htop 并再次退出时一样:什么都不可见从这个应用程序了。

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

为了操作终端,您必须使用控制序列 不幸的是,这些代码取决于您使用的特定终端。 这就是terminfo (以前称为termcap )首先存在的原因。

您不会说是否要使用 terminfo 。 所以:

  • 如果您将使用 terminfo,它将为您的终端支持的每个操作提供正确的控制序列。
  • 如果您不使用 terminfo... 好吧,您必须手动编码您想要支持的每种终端类型中的每个操作。

由于您希望将其用于学习目的,我将在第二部分详细说明。

您可以从环境变量$TERM发现您正在使用的终端类型。 在 linux 中,最常见的是用于终端仿真器(XTerm、gnome-terminal、konsole)的xterm和用于虚拟终端(X 未运行时的那些)的linux

您可以使用命令tput轻松发现控制序列。 但是当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清除屏幕,您必须在xterm中输出ESC [ H ESC [ 2J ,而在 linux 终端中输出ESC [ H ESC [ J

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

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

此代码将 stdin 设置为原始模式,切换到备用缓冲区屏幕(在启动之前保存终端的状态),启用鼠标跟踪并在用户单击某处时打印按钮和坐标。 使用Ctrl + C退出后,程序将恢复终端配置。

#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 列的终端,这将无法正常工作。

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

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

如果您谈论的是在xterm中运行的真实终端应用程序,那么需要注意的重要一点是,许多可移植性问题都与终端有关,而不是操作系统。 通过发送不同的转义序列来控制终端。 哪些做什么取决于终端; ANSI 转义码现在相当普遍,但是,请参阅http://en.wikipedia.org/wiki/ANSI_escape_code 例如,这些通常被xterm理解。

您可能必须在开始和结束时输出额外的序列才能进入和离开“全屏”模式; 这对于xterm是必要的。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM