编写一个"真正的"交互式终端程序,如vim,htop,...在C/C++中没有ncurses

lee*_*mes 48 c c++ terminal interactive

不,我不想使用ncurses,因为我想了解终端是如何工作的,并且可以自己编程.:)它不必是可移植的,它只能在基于linux xterm的终端模拟器上工作.

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

  1. 如何进行鼠标交互,比如单击一个字符并滚动鼠标滚轮(当鼠标处于特定字符时)以实现滚动[ 编辑:当然在终端模拟器中 ],以及

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

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

rod*_*igo 18

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

您没有说是否要使用terminfo.所以:

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

正如你想要这个用于学习目的,我将详细说明.

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

您可以使用命令轻松发现控制序列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|
Run Code Online (Sandbox Code Playgroud)

也就是说,要清除屏幕,xterm你必须ESC [ H ESC [ 2J在xterm但ESC [ H ESC [ J在linux终端输出.

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


san*_*tiz 8

虽然这个问题有点陈旧,但我认为我应该分享一个如何在不使用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;
}
Run Code Online (Sandbox Code Playgroud)

注意:对于列数超过255的终端,这将无法正常工作.

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

  • 对于任何想知道为什么 buff[0] == 3 意味着 Ctrl+C 被按下的人来说,这是因为在 ASCII 中 0x03 是 ETX 符号,代表“文本结束”。 (2认同)

Jam*_*nze 3

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

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

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

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

  • 这个答案的开头完全是假的。有一个协议可以通知终端您想要鼠标单击事件,还有一个协议可以将它们从终端发送到应用程序,所有这些都作为转义序列。Linux 控制台不支持这一点,而是使用可怕的 GPM 方法,但“xterm”和其他人确实正确支持它。 (21认同)
  • 终端应用程序中有针对鼠标事件的 GPM,不是吗? (2认同)