当退出终端原始模式时,我的内容保留在屏幕上

时间:2017-04-04 09:08:20

标签: c terminal

我使用这个小编辑器作为我正在做的项目的基础:https://github.com/antirez/kilo

编辑器使用rawmode中的终端并使用VT100转义序列进行写入,但是当退出程序时,显示的内容会保持显示状态。 在退出之前...... Before exiting

退出后...... After exiting

正如您所看到的那样,提示再次出现,但编辑器剩下的内容会一直存在,直到写完为止。

// Low level terminal handling
 void disable_raw_mode(int fd)
{
        // dont bother checking the return value as its too late
        if (Editor.rawmode) {
                tcsetattr(fd, TCSAFLUSH, &orig_termios);
                Editor.rawmode = 0;
        }
}


void editor_at_exit(void) 
{
        disable_raw_mode(STDIN_FILENO);
}

int enable_raw_mode(int fd) 
{
        struct termios raw;

        if(Editor.rawmode) return 0; //already enabled
        if(!isatty(STDIN_FILENO)) goto fatal;
        atexit(editor_at_exit);
        if(tcgetattr(fd, &orig_termios) == -1) goto fatal;

        raw = orig_termios; // modify the original mode
        /* input modes: no break, no CR to NL, no parity check, no strip char,
         *      * no start/stop output control. */
        raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);

        // output modes - disable post processing
        raw.c_oflag &= ~(OPOST);

        //control modes - set 8 bit chars
        raw.c_cflag |= (CS8);

        //local modes, choing off, canonical off, no extended functions, no signal chars (, etc)
        raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);

        //control chars - set return condition: min number of bytes and a timer
        raw.c_cc[VMIN] = 0; // return each byte, or zero for a timeout
        raw.c_cc[VTIME] = 1; //100ms timeout

        //put terminal in raw mode after flushing
        if(tcsetattr(fd, TCSAFLUSH, &raw) < 0) goto fatal;
        Editor.rawmode = 1;
        return 0;

fatal:
        errno = ENOTTY;
        return -1;
}

根据我的理解,当程序退出时,调用atexit(editor_at_exit)函数并在该函数中禁用原始模式。在编辑器打开之前,我将丢失的终端恢复到原来的样子。我不是只想clear整个终端。

1 个答案:

答案 0 :(得分:5)

您正在寻找的功能称为“备用屏幕缓冲区”,它起源于xterm,但现在大多数终端都支持。

备用屏幕缓冲区旨在为全屏终端程序提供此功能。在正常操作中,输出被添加到回滚缓冲区(大多数终端允许用户滚动回到前一行)。切换到备用屏幕缓冲区时,回滚缓冲区将保持不变,并且备用屏幕缓冲区输出不会添加到回滚缓冲区。从备用屏幕缓冲区返回时,将恢复原始回滚缓冲区状态。这就是像nano这样的全屏应用程序。

要切换到备用屏幕缓冲区,我建议写(C字符串)
"\033[?1049h\033[2J\033[H"(15个字符)
到终点站。如果终端仿真器支持备用屏幕缓冲区,则会更改它,清除它并将光标移动到左上角。如果终端仿真器不支持它,则会清除屏幕并将光标移动到左上角。

要从备用屏幕缓冲区返回,我建议写(C字符串)
"\033[2J\033[H\033[?1049l"(15个字符)
到终点站。如果终端仿真器支持备用屏幕缓冲区,则首先清除备用屏幕缓冲区,然后返回到原始回滚缓冲区(例如nano)。如果终端仿真器不支持它,则会清除屏幕并将光标移动到左上角。

我推荐这对("\033[?1049h\033[2J\033[H""\033[2J\033[H\033[?1049l"),因为无论终端模拟器是否支持备用屏幕缓冲区,它都以合理的方式工作,而不是离开全屏应用程序状态之后可见。

如果标准输入是终端,我还建议使用例如

int write_term(const char *p)
{
    const char *q = p;
    ssize_t     n;
    int         retval = 0, saved_errno;

    /* Nothing to write? */        
    if (!q || !*q)
        return 0;

    saved_errno = errno;

    /* async-signal safe version of q = p + strlen(p) */
    while (*q)
        q++;

    while (p < q) {
        n = write(STDIN_FILENO, p, (size_t)(q - p));
        if (n > 0) {
            p += n;
        } else
        if (n != -1) {
            retval = EIO;
            break;
        } else
        if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
            retval = errno;
            break;
        }
    }

    errno = saved_errno;
    return retval;
}

将字符串写入终端,因为标准C I / O函数可能无法写入标准输入(毕竟只用于读取)。上述功能非常小心,忽略了信号传递(如果标准输入是非阻塞的话,甚至在必要时进行繁忙循环),甚至保持errno完整无缺;它也是异步信号安全的,这意味着它可以安全地用在信号处理程序中(尽管我建议不要改变终端缓冲模式或信号处理程序中的设置,因为这样做变得非常复杂)。

(OP的代码可能已经实现了一个合适的低级I / O功能,但问题中没有显示。)